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();
1329 * draw one extra column than strictly needed - this is a (harmless)
1330 * fudge to ensure scale marks get drawn (JAL-2636)
1332 int endx = startRes + columns;
1336 * white fill the region to be drawn including scale left or above
1338 gg.setColor(Color.white);
1339 int height = getRepeatHeightWrapped();
1340 gg.fillRect(0, ypos, labelWidthWest + columns * charWidth, height);
1341 ypos += charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1343 gg.setFont(av.getFont());
1344 gg.setColor(Color.black);
1346 if (av.getScaleLeftWrapped())
1348 drawVerticalScale(gg, startRes, endx, ypos, true);
1351 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1353 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1357 * Shifts the visible alignment by the specified number of columns - left if
1358 * negative, right if positive. Includes scale above, left or right and
1359 * annotations (if shown). Does not draw newly visible columns.
1363 protected void shiftWrappedAlignment(int positions)
1370 int repeatHeight = getRepeatHeightWrapped();
1371 ViewportRanges ranges = av.getRanges();
1372 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1374 int visibleWidths = getHeight() / repeatHeight;
1375 if (getHeight() % repeatHeight > 0)
1379 int viewportWidth = ranges.getViewportWidth();
1380 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1385 * shift right (after scroll left)
1386 * for each wrapped width (starting with the last), copy (width-positions)
1387 * columns from the left margin to the right margin, and copy positions
1388 * columns from the right margin of the row above (if any) to the
1389 * left margin of the current row
1391 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1394 * get y-offset of last wrapped width
1396 int y = getHeight() / repeatHeight * repeatHeight;
1397 int copyFromLeftStart = labelWidthWest;
1398 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1402 // todo limit repeatHeight for a last part height width?
1403 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1404 positions * charWidth, 0);
1407 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1408 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1411 if (av.getScaleLeftWrapped())
1413 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1415 if (av.getScaleRightWrapped())
1417 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1421 xpos -= viewportWidth;
1427 * shift left (after scroll right)
1428 * for each wrapped width (starting with the first), copy (width-positions)
1429 * columns from the right margin to the left margin, and copy positions
1430 * columns from the left margin of the row below (if any) to the
1431 * right margin of the current row
1433 int xpos = ranges.getStartRes();
1435 int copyFromRightStart = labelWidthWest - positions * charWidth;
1437 while (y < getHeight())
1439 // todo limit repeatHeight for a last part height width?
1440 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1441 positions * charWidth, 0);
1442 if (y + repeatHeight < getHeight())
1444 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1445 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1448 if (av.getScaleLeftWrapped())
1450 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1452 if (av.getScaleRightWrapped())
1454 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1458 xpos += ranges.getViewportWidth();
1464 * Redraws any positions in the search results in the visible region of a
1465 * wrapped alignment. Any highlights are drawn depending on the search results
1466 * set on the Viewport, not the <code>results</code> argument. This allows
1467 * this method to be called either to clear highlights (passing the previous
1468 * search results), or to draw new highlights.
1473 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1475 if (results == null)
1480 boolean matchFound = false;
1482 int wrappedWidth = av.getWrappedWidth();
1483 int wrappedHeight = getRepeatHeightWrapped();
1485 ViewportRanges ranges = av.getRanges();
1486 int canvasHeight = getHeight();
1487 int repeats = canvasHeight / wrappedHeight;
1488 if (canvasHeight / wrappedHeight > 0)
1493 int firstVisibleColumn = ranges.getStartRes();
1494 int lastVisibleColumn = ranges.getStartRes() + repeats
1495 * ranges.getViewportWidth() - 1;
1497 AlignmentI alignment = av.getAlignment();
1498 if (av.hasHiddenColumns())
1500 firstVisibleColumn = alignment.getHiddenColumns()
1501 .adjustForHiddenColumns(firstVisibleColumn);
1502 lastVisibleColumn = alignment.getHiddenColumns()
1503 .adjustForHiddenColumns(lastVisibleColumn);
1506 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1508 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1509 .getEndSeq(); seqNo++)
1511 SequenceI seq = alignment.getSequenceAt(seqNo);
1513 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1515 if (visibleResults != null)
1517 for (int i = 0; i < visibleResults.length - 1; i += 2)
1519 int firstMatchedColumn = visibleResults[i];
1520 int lastMatchedColumn = visibleResults[i + 1];
1521 if (firstMatchedColumn <= lastVisibleColumn
1522 && lastMatchedColumn >= firstVisibleColumn)
1525 * found a search results match in the visible region
1527 firstMatchedColumn = Math.max(firstMatchedColumn,
1528 firstVisibleColumn);
1529 lastMatchedColumn = Math.min(lastMatchedColumn,
1533 * draw each mapped position separately (as contiguous positions may
1534 * wrap across lines)
1536 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1538 int displayColumn = mappedPos;
1539 if (av.hasHiddenColumns())
1541 displayColumn = alignment.getHiddenColumns()
1542 .findColumnPosition(displayColumn);
1546 * transX: offset from left edge of canvas to residue position
1548 int transX = labelWidthWest
1549 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1550 * av.getCharWidth();
1553 * transY: offset from top edge of canvas to residue position
1555 int transY = gapHeight;
1556 transY += (displayColumn - ranges.getStartRes())
1557 / wrappedWidth * wrappedHeight;
1558 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1561 * yOffset is from graphics origin to start of visible region
1563 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1564 if (transY < getHeight())
1567 gg.translate(transX, transY);
1568 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1570 gg.translate(-transX, -transY);
1582 * Answers the height in pixels of a repeating section of the wrapped
1583 * alignment, including space above, scale above if shown, sequences, and
1584 * annotation panel if shown
1588 protected int getRepeatHeightWrapped()
1590 // gap (and maybe scale) above
1591 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1594 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1596 // add annotations panel height if shown
1597 repeatHeight += getAnnotationHeight();
1599 return repeatHeight;