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 * draw scale value, justified, with half a character width
223 * separation from the sequence data
225 String valueAsString = String.valueOf(value);
226 int justify = fm.stringWidth(valueAsString) + charWidth;
227 int xpos = left ? labelWidthWest - justify + charWidth / 2
228 : getWidth() - justify - charWidth / 2;
230 g.setColor(Color.white);
231 int y = (ypos + (i * charHeight)) - (charHeight / 5);
232 y -= charHeight; // fillRect starts from top right of rectangle
233 g.fillRect(xpos, y, justify - charWidth, charHeight + 1);
234 y += charHeight; // drawString starts from bottom right of text
235 g.setColor(Color.black);
236 g.drawString(valueAsString, xpos, y);
242 * Does a fast paint of an alignment in response to a scroll. Most of the
243 * visible region is simply copied and shifted, and then any newly visible
244 * columns or rows are drawn. The scroll may be horizontal or vertical, but
245 * not both at once. Scrolling may be the result of
247 * <li>dragging a scroll bar</li>
248 * <li>clicking in the scroll bar</li>
249 * <li>scrolling by trackpad, middle mouse button, or other device</li>
250 * <li>by moving the box in the Overview window</li>
251 * <li>programmatically to make a highlighted position visible</li>
255 * columns to shift right (positive) or left (negative)
257 * rows to shift down (positive) or up (negative)
259 public void fastPaint(int horizontal, int vertical)
261 if (fastpainting || gg == null)
269 ViewportRanges ranges = av.getRanges();
270 int startRes = ranges.getStartRes();
271 int endRes = ranges.getEndRes();
272 int startSeq = ranges.getStartSeq();
273 int endSeq = ranges.getEndSeq();
277 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
278 imgHeight, -horizontal * charWidth, -vertical * charHeight);
280 if (horizontal > 0) // scrollbar pulled right, image to the left
282 transX = (endRes - startRes - horizontal) * charWidth;
283 startRes = endRes - horizontal;
285 else if (horizontal < 0)
287 endRes = startRes - horizontal;
289 else if (vertical > 0) // scroll down
291 startSeq = endSeq - vertical;
293 if (startSeq < ranges.getStartSeq())
294 { // ie scrolling too fast, more than a page at a time
295 startSeq = ranges.getStartSeq();
299 transY = imgHeight - ((vertical + 1) * charHeight);
302 else if (vertical < 0)
304 endSeq = startSeq - vertical;
306 if (endSeq > ranges.getEndSeq())
308 endSeq = ranges.getEndSeq();
312 gg.translate(transX, transY);
313 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
314 gg.translate(-transX, -transY);
317 fastpainting = false;
321 public void paintComponent(Graphics g)
324 BufferedImage lcimg = img; // take reference since other threads may null
325 // img and call later.
326 super.paintComponent(g);
330 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
331 .getClipBounds().height)))
333 g.drawImage(lcimg, 0, 0, this);
338 // this draws the whole of the alignment
339 imgWidth = getWidth();
340 imgHeight = getHeight();
342 imgWidth -= (imgWidth % charWidth);
343 imgHeight -= (imgHeight % charHeight);
345 if ((imgWidth < 1) || (imgHeight < 1))
350 if (lcimg == null || imgWidth != lcimg.getWidth()
351 || imgHeight != lcimg.getHeight())
355 lcimg = img = new BufferedImage(imgWidth, imgHeight,
356 BufferedImage.TYPE_INT_RGB);
357 gg = (Graphics2D) img.getGraphics();
358 gg.setFont(av.getFont());
359 } catch (OutOfMemoryError er)
362 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
363 new OOMWarning("Creating alignment image for display", er);
371 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
372 RenderingHints.VALUE_ANTIALIAS_ON);
375 gg.setColor(Color.white);
376 gg.fillRect(0, 0, imgWidth, imgHeight);
378 ViewportRanges ranges = av.getRanges();
379 if (av.getWrapAlignment())
381 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
385 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
386 ranges.getStartSeq(), ranges.getEndSeq(), 0);
389 g.drawImage(lcimg, 0, 0, this);
394 * Returns the visible width of the canvas in residues, after allowing for
395 * East or West scales (if shown)
398 * the width in pixels (possibly including scales)
402 public int getWrappedCanvasWidth(int canvasWidth)
404 FontMetrics fm = getFontMetrics(av.getFont());
409 if (av.getScaleRightWrapped())
411 labelWidthEast = getLabelWidth(fm);
414 if (av.getScaleLeftWrapped())
416 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
420 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
424 * Returns a pixel width suitable for showing the largest sequence coordinate
425 * (end position) in the alignment. Returns 2 plus the number of decimal
426 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
431 protected int getLabelWidth(FontMetrics fm)
434 * find the biggest sequence end position we need to show
435 * (note this is not necessarily the sequence length)
438 AlignmentI alignment = av.getAlignment();
439 for (int i = 0; i < alignment.getHeight(); i++)
441 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
445 for (int i = maxWidth; i > 0; i /= 10)
450 return fm.stringWidth(ZEROS.substring(0, length));
454 * Draws as many widths of a wrapped alignment as can fit in the visible
459 * available width in pixels
460 * @param canvasHeight
461 * available height in pixels
463 * the first visible column (0...) of the alignment to draw
465 public void drawWrappedPanel(Graphics g, int canvasWidth,
466 int canvasHeight, int startRes)
470 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
472 FontMetrics fm = getFontMetrics(av.getFont());
473 labelWidth = getLabelWidth(fm);
476 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
477 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
479 int hgap = charHeight;
480 if (av.getScaleAboveWrapped())
485 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
487 av.setWrappedWidth(cWidth);
489 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
492 int maxwidth = av.getAlignment().getWidth();
494 if (av.hasHiddenColumns())
496 maxwidth = av.getAlignment().getHiddenColumns()
497 .findColumnPosition(maxwidth);
500 int annotationHeight = getAnnotationHeight();
501 int sequencesHeight = av.getAlignment().getHeight() * charHeight;
504 * draw one width at a time (including any scales or annotation shown),
505 * until we have run out of alignment or vertical space available
507 while ((ypos <= canvasHeight) && (startRes < maxwidth))
509 drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
511 ypos += sequencesHeight + annotationHeight + hgap;
520 * @param canvasHeight
525 protected void drawWrappedWidth(Graphics g, int startRes,
526 int canvasHeight, int canvasWidth, int maxWidth, int ypos)
529 endx = startRes + canvasWidth - 1;
536 g.setFont(av.getFont());
537 g.setColor(Color.black);
539 if (av.getScaleLeftWrapped())
541 drawVerticalScale(g, startRes, endx, ypos, true);
544 if (av.getScaleRightWrapped())
546 drawVerticalScale(g, startRes, endx, ypos, false);
549 drawWrappedRegion(g, startRes, endx, canvasHeight, canvasWidth, ypos);
553 * Draws columns of a wrapped alignment from startRes to endRes, including
554 * scale above and annotations if shown, but not scale left or right.
559 * @param canvasHeight
563 protected void drawWrappedRegion(Graphics g, int startRes, int endRes,
564 int canvasHeight, int canvasWidth, int ypos)
566 g.translate(labelWidthWest, 0);
568 if (av.getScaleAboveWrapped())
570 drawNorthScale(g, startRes, endRes, ypos);
573 // todo can we let drawPanel() handle this?
574 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
576 g.setColor(Color.blue);
577 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
578 List<Integer> positions = hidden.findHiddenRegionPositions();
579 for (int pos : positions)
581 int res = pos - startRes;
583 if (res < 0 || res > endRes - startRes)
588 gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
589 res * charWidth + charHeight / 4, res * charWidth }, new int[] {
590 ypos - (charHeight / 2), ypos - (charHeight / 2),
591 ypos - (charHeight / 2) + 8 }, 3);
595 // When printing we have an extra clipped region,
596 // the Printable page which we need to account for here
597 Shape clip = g.getClip();
601 g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
605 g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
606 (int) clip.getBounds().getHeight());
609 drawPanel(g, startRes, endRes, 0, av.getAlignment().getHeight() - 1, ypos);
611 int cHeight = av.getAlignment().getHeight() * charHeight;
613 if (av.isShowAnnotation())
615 g.translate(0, cHeight + ypos + 3);
616 if (annotations == null)
618 annotations = new AnnotationPanel(av);
621 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
623 g.translate(0, -cHeight - ypos - 3);
626 g.translate(-labelWidthWest, 0);
629 AnnotationPanel annotations;
631 int getAnnotationHeight()
633 if (!av.isShowAnnotation())
638 if (annotations == null)
640 annotations = new AnnotationPanel(av);
643 return annotations.adjustPanelHeight();
647 * Draws the visible region of the alignment on the graphics context. If there
648 * are hidden column markers in the visible region, then each sub-region
649 * between the markers is drawn separately, followed by the hidden column
653 * the graphics context, positioned at the first residue to be drawn
655 * offset of the first column to draw (0..)
657 * offset of the last column to draw (0..)
659 * offset of the first sequence to draw (0..)
661 * offset of the last sequence to draw (0..)
663 * vertical offset at which to draw (for wrapped alignments)
665 public void drawPanel(Graphics g1, final int startRes, final int endRes,
666 final int startSeq, final int endSeq, final int yOffset)
669 if (!av.hasHiddenColumns())
671 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
676 final int screenYMax = endRes - startRes;
677 int blockStart = startRes;
678 int blockEnd = endRes;
680 for (int[] region : av.getAlignment().getHiddenColumns()
681 .getHiddenColumnsCopy())
683 int hideStart = region[0];
684 int hideEnd = region[1];
686 if (hideStart <= blockStart)
688 blockStart += (hideEnd - hideStart) + 1;
693 * draw up to just before the next hidden region, or the end of
694 * the visible region, whichever comes first
696 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
699 g1.translate(screenY * charWidth, 0);
701 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
704 * draw the downline of the hidden column marker (ScalePanel draws the
705 * triangle on top) if we reached it
707 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
709 g1.setColor(Color.blue);
711 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
712 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
713 (endSeq - startSeq + 1) * charHeight + yOffset);
716 g1.translate(-screenY * charWidth, 0);
717 screenY += blockEnd - blockStart + 1;
718 blockStart = hideEnd + 1;
720 if (screenY > screenYMax)
722 // already rendered last block
727 if (screenY <= screenYMax)
729 // remaining visible region to render
730 blockEnd = blockStart + screenYMax - screenY;
731 g1.translate(screenY * charWidth, 0);
732 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
734 g1.translate(-screenY * charWidth, 0);
741 * Draws a region of the visible alignment
745 * offset of the first column in the visible region (0..)
747 * offset of the last column in the visible region (0..)
749 * offset of the first sequence in the visible region (0..)
751 * offset of the last sequence in the visible region (0..)
753 * vertical offset at which to draw (for wrapped alignments)
755 private void draw(Graphics g, int startRes, int endRes, int startSeq,
756 int endSeq, int offset)
758 g.setFont(av.getFont());
759 sr.prepare(g, av.isRenderGaps());
763 // / First draw the sequences
764 // ///////////////////////////
765 for (int i = startSeq; i <= endSeq; i++)
767 nextSeq = av.getAlignment().getSequenceAt(i);
770 // occasionally, a race condition occurs such that the alignment row is
774 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
775 startRes, endRes, offset + ((i - startSeq) * charHeight));
777 if (av.isShowSequenceFeatures())
779 fr.drawSequence(g, nextSeq, startRes, endRes, offset
780 + ((i - startSeq) * charHeight), false);
784 * highlight search Results once sequence has been drawn
786 if (av.hasSearchResults())
788 SearchResultsI searchResults = av.getSearchResults();
789 int[] visibleResults = searchResults.getResults(nextSeq,
791 if (visibleResults != null)
793 for (int r = 0; r < visibleResults.length; r += 2)
795 sr.drawHighlightedText(nextSeq, visibleResults[r],
796 visibleResults[r + 1], (visibleResults[r] - startRes)
798 + ((i - startSeq) * charHeight));
803 if (av.cursorMode && cursorY == i && cursorX >= startRes
804 && cursorX <= endRes)
806 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
807 offset + ((i - startSeq) * charHeight));
811 if (av.getSelectionGroup() != null
812 || av.getAlignment().getGroups().size() > 0)
814 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
819 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
820 int startSeq, int endSeq, int offset)
822 Graphics2D g = (Graphics2D) g1;
824 // ///////////////////////////////////
825 // Now outline any areas if necessary
826 // ///////////////////////////////////
827 SequenceGroup group = av.getSelectionGroup();
833 int visWidth = (endRes - startRes + 1) * charWidth;
835 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
837 group = av.getAlignment().getGroups().get(0);
847 boolean inGroup = false;
851 for (i = startSeq; i <= endSeq; i++)
853 sx = (group.getStartRes() - startRes) * charWidth;
854 sy = offset + ((i - startSeq) * charHeight);
855 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
857 if (sx + ex < 0 || sx > visWidth)
862 if ((sx <= (endRes - startRes) * charWidth)
863 && group.getSequences(null).contains(
864 av.getAlignment().getSequenceAt(i)))
867 && !group.getSequences(null).contains(
868 av.getAlignment().getSequenceAt(i + 1)))
870 bottom = sy + charHeight;
875 if (((top == -1) && (i == 0))
876 || !group.getSequences(null).contains(
877 av.getAlignment().getSequenceAt(i - 1)))
885 if (group == av.getSelectionGroup())
887 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
888 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
890 g.setColor(Color.RED);
894 g.setStroke(new BasicStroke());
895 g.setColor(group.getOutlineColour());
903 if (sx >= 0 && sx < visWidth)
905 g.drawLine(sx, oldY, sx, sy);
908 if (sx + ex < visWidth)
910 g.drawLine(sx + ex, oldY, sx + ex, sy);
919 if (sx + ex > visWidth)
924 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
926 ex = (endRes - startRes + 1) * charWidth;
931 g.drawLine(sx, top, sx + ex, top);
937 g.drawLine(sx, bottom, sx + ex, bottom);
948 sy = offset + ((i - startSeq) * charHeight);
949 if (sx >= 0 && sx < visWidth)
951 g.drawLine(sx, oldY, sx, sy);
954 if (sx + ex < visWidth)
956 g.drawLine(sx + ex, oldY, sx + ex, sy);
965 if (sx + ex > visWidth)
969 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
971 ex = (endRes - startRes + 1) * charWidth;
976 g.drawLine(sx, top, sx + ex, top);
982 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
991 g.setStroke(new BasicStroke());
993 if (groupIndex >= av.getAlignment().getGroups().size())
998 group = av.getAlignment().getGroups().get(groupIndex);
1000 } while (groupIndex < av.getAlignment().getGroups().size());
1007 * Highlights search results in the visible region by rendering as white text
1008 * on a black background. Any previous highlighting is removed. Answers true
1009 * if any highlight was left on the visible alignment (so status bar should be
1010 * set to match), else false.
1012 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1013 * alignment had to be scrolled to show the highlighted region, then it should
1014 * be fully redrawn, otherwise a fast paint can be performed. This argument
1015 * could be removed if fast paint of scrolled wrapped alignment is coded in
1016 * future (JAL-2609).
1019 * @param noFastPaint
1022 public boolean highlightSearchResults(SearchResultsI results,
1023 boolean noFastPaint)
1029 boolean wrapped = av.getWrapAlignment();
1033 fastPaint = !noFastPaint;
1034 fastpainting = fastPaint;
1039 * to avoid redrawing the whole visible region, we instead
1040 * redraw just the minimal regions to remove previous highlights
1043 SearchResultsI previous = av.getSearchResults();
1044 av.setSearchResults(results);
1045 boolean redrawn = false;
1046 boolean drawn = false;
1049 redrawn = drawMappedPositionsWrapped(previous);
1050 drawn = drawMappedPositionsWrapped(results);
1055 redrawn = drawMappedPositions(previous);
1056 drawn = drawMappedPositions(results);
1061 * if highlights were either removed or added, repaint
1069 * return true only if highlights were added
1075 fastpainting = false;
1080 * Redraws the minimal rectangle in the visible region (if any) that includes
1081 * mapped positions of the given search results. Whether or not positions are
1082 * highlighted depends on the SearchResults set on the Viewport. This allows
1083 * this method to be called to either clear or set highlighting. Answers true
1084 * if any positions were drawn (in which case a repaint is still required),
1090 protected boolean drawMappedPositions(SearchResultsI results)
1092 if (results == null)
1098 * calculate the minimal rectangle to redraw that
1099 * includes both new and existing search results
1101 int firstSeq = Integer.MAX_VALUE;
1103 int firstCol = Integer.MAX_VALUE;
1105 boolean matchFound = false;
1107 ViewportRanges ranges = av.getRanges();
1108 int firstVisibleColumn = ranges.getStartRes();
1109 int lastVisibleColumn = ranges.getEndRes();
1110 AlignmentI alignment = av.getAlignment();
1111 if (av.hasHiddenColumns())
1113 firstVisibleColumn = alignment.getHiddenColumns()
1114 .adjustForHiddenColumns(firstVisibleColumn);
1115 lastVisibleColumn = alignment.getHiddenColumns()
1116 .adjustForHiddenColumns(lastVisibleColumn);
1119 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1120 .getEndSeq(); seqNo++)
1122 SequenceI seq = alignment.getSequenceAt(seqNo);
1124 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1126 if (visibleResults != null)
1128 for (int i = 0; i < visibleResults.length - 1; i += 2)
1130 int firstMatchedColumn = visibleResults[i];
1131 int lastMatchedColumn = visibleResults[i + 1];
1132 if (firstMatchedColumn <= lastVisibleColumn
1133 && lastMatchedColumn >= firstVisibleColumn)
1136 * found a search results match in the visible region -
1137 * remember the first and last sequence matched, and the first
1138 * and last visible columns in the matched positions
1141 firstSeq = Math.min(firstSeq, seqNo);
1142 lastSeq = Math.max(lastSeq, seqNo);
1143 firstMatchedColumn = Math.max(firstMatchedColumn,
1144 firstVisibleColumn);
1145 lastMatchedColumn = Math.min(lastMatchedColumn,
1147 firstCol = Math.min(firstCol, firstMatchedColumn);
1148 lastCol = Math.max(lastCol, lastMatchedColumn);
1156 if (av.hasHiddenColumns())
1158 firstCol = alignment.getHiddenColumns()
1159 .findColumnPosition(firstCol);
1160 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1162 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1163 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1164 gg.translate(transX, transY);
1165 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1166 gg.translate(-transX, -transY);
1173 public void propertyChange(PropertyChangeEvent evt)
1175 String eventName = evt.getPropertyName();
1177 // if (av.getWrapAlignment())
1179 // if (eventName.equals(ViewportRanges.STARTRES))
1187 if (eventName.equals(ViewportRanges.STARTRES))
1189 // Make sure we're not trying to draw a panel
1190 // larger than the visible window
1191 ViewportRanges vpRanges = av.getRanges();
1192 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1193 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1194 if (scrollX > range)
1198 else if (scrollX < -range)
1204 // Both scrolling and resizing change viewport ranges: scrolling changes
1205 // both start and end points, but resize only changes end values.
1206 // Here we only want to fastpaint on a scroll, with resize using a normal
1207 // paint, so scroll events are identified as changes to the horizontal or
1208 // vertical start value.
1209 if (eventName.equals(ViewportRanges.STARTRES))
1211 // scroll - startres and endres both change
1212 if (av.getWrapAlignment())
1214 fastPaintWrapped(scrollX);
1215 // fastPaintWrapped(scrollX > 0 ? 1 : -1); // to debug: 1 at a time
1219 fastPaint(scrollX, 0);
1222 else if (eventName.equals(ViewportRanges.STARTSEQ))
1224 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1230 * Does a minimal update of the image for a scroll movement. This method
1231 * handles scroll movements of up to one width of the wrapped alignment (one
1232 * click in the vertical scrollbar). Larger movements (for example after a
1233 * scroll to highlight a mapped position) trigger a full redraw instead.
1236 * number of positions scrolled (right if positive, left if negative)
1238 protected void fastPaintWrapped(int scrollX)
1240 if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
1243 * shift of more than one view width is
1244 * too complicated to handle in this method
1254 * relocate the regions of the alignment that are still visible
1256 shiftWrappedAlignment(-scrollX);
1259 * add new columns (scale above, sequence, annotation)
1260 * at top left if scrollX < 0 or bottom right if scrollX > 0
1261 * also West scale top left or East scale bottom right if shown
1265 fastPaintWrappedTopLeft(-scrollX);
1269 // fastPaintWrappedBottomRight(scrollX);
1276 * Draws the specified number of columns at the 'end' (bottom right) of a
1277 * wrapped alignment view, including scale above and right and annotations if
1282 protected void fastPaintWrappedBottomRight(int columns)
1289 int repeatHeight = getRepeatHeightWrapped();
1290 ViewportRanges ranges = av.getRanges();
1291 int visibleWidths = getHeight() / repeatHeight;
1292 if (getHeight() % repeatHeight > 0)
1296 int viewportWidth = ranges.getViewportWidth();
1297 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1299 int startRes = av.getRanges().getStartRes();
1300 int endx = startRes + columns - 1;
1301 int ypos = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1302 ypos += repeatHeight * (visibleWidths - 1);
1304 gg.setFont(av.getFont());
1305 gg.setColor(Color.black);
1307 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1309 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1311 if (av.getScaleRightWrapped())
1313 drawVerticalScale(gg, startRes, endx, ypos, false);
1318 * Draws the specified number of columns at the 'start' (top left) of a
1319 * wrapped alignment view, including scale above and left and annotations if
1324 protected void fastPaintWrappedTopLeft(int columns)
1326 int startRes = av.getRanges().getStartRes();
1327 int endx = startRes + columns - 1;
1331 * white fill the region to be drawn including scale left or above
1333 gg.setColor(Color.white);
1334 int height = getRepeatHeightWrapped();
1335 gg.fillRect(0, ypos, labelWidthWest + columns * charWidth, height);
1336 ypos += charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1338 gg.setFont(av.getFont());
1339 gg.setColor(Color.black);
1341 if (av.getScaleLeftWrapped())
1343 drawVerticalScale(gg, startRes, endx, ypos, true);
1346 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1348 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1352 * Shifts the visible alignment by the specified number of columns - left if
1353 * negative, right if positive. Includes scale above, left or right and
1354 * annotations (if shown). Does not draw newly visible columns.
1358 protected void shiftWrappedAlignment(int positions)
1365 int repeatHeight = getRepeatHeightWrapped();
1366 ViewportRanges ranges = av.getRanges();
1367 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1369 int visibleWidths = getHeight() / repeatHeight;
1370 if (getHeight() % repeatHeight > 0)
1374 int viewportWidth = ranges.getViewportWidth();
1375 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1380 * shift right (after scroll left)
1381 * for each wrapped width (starting with the last), copy (width-positions)
1382 * columns from the left margin to the right margin, and copy positions
1383 * columns from the right margin of the row above (if any) to the
1384 * left margin of the current row
1386 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1389 * get y-offset of last wrapped width
1391 int y = getHeight() / repeatHeight * repeatHeight;
1392 int copyFromLeftStart = labelWidthWest;
1393 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1397 // todo limit repeatHeight for a last part height width?
1398 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1399 positions * charWidth, 0);
1402 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1403 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1406 if (av.getScaleLeftWrapped())
1408 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1410 if (av.getScaleRightWrapped())
1412 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1416 xpos -= viewportWidth;
1422 * shift left (after scroll right)
1423 * for each wrapped width (starting with the first), copy (width-positions)
1424 * columns from the right margin to the left margin, and copy positions
1425 * columns from the left margin of the row below (if any) to the
1426 * right margin of the current row
1428 int xpos = ranges.getStartRes();
1430 int copyFromRightStart = labelWidthWest - positions * charWidth;
1432 while (y < getHeight())
1434 // todo limit repeatHeight for a last part height width?
1435 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1436 positions * charWidth, 0);
1437 if (y + repeatHeight < getHeight())
1439 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1440 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1443 if (av.getScaleLeftWrapped())
1445 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1447 if (av.getScaleRightWrapped())
1449 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1453 xpos += ranges.getViewportWidth();
1459 * Redraws any positions in the search results in the visible region of a
1460 * wrapped alignment. Any highlights are drawn depending on the search results
1461 * set on the Viewport, not the <code>results</code> argument. This allows
1462 * this method to be called either to clear highlights (passing the previous
1463 * search results), or to draw new highlights.
1468 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1470 if (results == null)
1475 boolean matchFound = false;
1477 int wrappedWidth = av.getWrappedWidth();
1478 int wrappedHeight = getRepeatHeightWrapped();
1480 ViewportRanges ranges = av.getRanges();
1481 int canvasHeight = getHeight();
1482 int repeats = canvasHeight / wrappedHeight;
1483 if (canvasHeight / wrappedHeight > 0)
1488 int firstVisibleColumn = ranges.getStartRes();
1489 int lastVisibleColumn = ranges.getStartRes() + repeats
1490 * ranges.getViewportWidth() - 1;
1492 AlignmentI alignment = av.getAlignment();
1493 if (av.hasHiddenColumns())
1495 firstVisibleColumn = alignment.getHiddenColumns()
1496 .adjustForHiddenColumns(firstVisibleColumn);
1497 lastVisibleColumn = alignment.getHiddenColumns()
1498 .adjustForHiddenColumns(lastVisibleColumn);
1501 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1503 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1504 .getEndSeq(); seqNo++)
1506 SequenceI seq = alignment.getSequenceAt(seqNo);
1508 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1510 if (visibleResults != null)
1512 for (int i = 0; i < visibleResults.length - 1; i += 2)
1514 int firstMatchedColumn = visibleResults[i];
1515 int lastMatchedColumn = visibleResults[i + 1];
1516 if (firstMatchedColumn <= lastVisibleColumn
1517 && lastMatchedColumn >= firstVisibleColumn)
1520 * found a search results match in the visible region
1522 firstMatchedColumn = Math.max(firstMatchedColumn,
1523 firstVisibleColumn);
1524 lastMatchedColumn = Math.min(lastMatchedColumn,
1528 * draw each mapped position separately (as contiguous positions may
1529 * wrap across lines)
1531 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1533 int displayColumn = mappedPos;
1534 if (av.hasHiddenColumns())
1536 displayColumn = alignment.getHiddenColumns()
1537 .findColumnPosition(displayColumn);
1541 * transX: offset from left edge of canvas to residue position
1543 int transX = labelWidthWest
1544 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1545 * av.getCharWidth();
1548 * transY: offset from top edge of canvas to residue position
1550 int transY = gapHeight;
1551 transY += (displayColumn - ranges.getStartRes())
1552 / wrappedWidth * wrappedHeight;
1553 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1556 * yOffset is from graphics origin to start of visible region
1558 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1559 if (transY < getHeight())
1562 gg.translate(transX, transY);
1563 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1565 gg.translate(-transX, -transY);
1577 * Answers the height in pixels of a repeating section of the wrapped
1578 * alignment, including space above, scale above if shown, sequences, and
1579 * annotation panel if shown
1583 protected int getRepeatHeightWrapped()
1585 // gap (and maybe scale) above
1586 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1589 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1591 // add annotations panel height if shown
1592 repeatHeight += getAnnotationHeight();
1594 return repeatHeight;