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.viewmodel.ViewportListenerI;
31 import jalview.viewmodel.ViewportRanges;
33 import java.awt.BasicStroke;
34 import java.awt.BorderLayout;
35 import java.awt.Color;
36 import java.awt.FontMetrics;
37 import java.awt.Graphics;
38 import java.awt.Graphics2D;
39 import java.awt.RenderingHints;
40 import java.awt.Shape;
41 import java.awt.image.BufferedImage;
42 import java.beans.PropertyChangeEvent;
43 import java.util.List;
45 import javax.swing.JComponent;
53 public class SeqCanvas extends JComponent implements ViewportListenerI
55 final FeatureRenderer fr;
57 final SequenceRenderer sr;
69 boolean fastPaint = false;
80 * Creates a new SeqCanvas object.
85 public SeqCanvas(AlignmentPanel ap)
89 fr = new FeatureRenderer(ap);
90 sr = new SequenceRenderer(av);
91 setLayout(new BorderLayout());
92 PaintRefresher.Register(this, av.getSequenceSetId());
93 setBackground(Color.white);
95 av.getRanges().addPropertyChangeListener(this);
98 public SequenceRenderer getSequenceRenderer()
103 public FeatureRenderer getFeatureRenderer()
108 int charHeight = 0, charWidth = 0;
110 private void updateViewport()
112 charHeight = av.getCharHeight();
113 charWidth = av.getCharWidth();
128 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
131 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
134 int mpos = mark.column; // (i - startx - 1)
139 String mstring = mark.text;
145 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
147 g.drawLine((mpos * charWidth) + (charWidth / 2), (ypos + 2)
148 - (charHeight / 2), (mpos * charWidth) + (charWidth / 2),
166 void drawWestScale(Graphics g, int startx, int endx, int ypos)
168 FontMetrics fm = getFontMetrics(av.getFont());
171 if (av.hasHiddenColumns())
173 startx = av.getAlignment().getHiddenColumns()
174 .adjustForHiddenColumns(startx);
175 endx = av.getAlignment().getHiddenColumns()
176 .adjustForHiddenColumns(endx);
179 int maxwidth = av.getAlignment().getWidth();
180 if (av.hasHiddenColumns())
182 maxwidth = av.getAlignment().getHiddenColumns()
183 .findColumnPosition(maxwidth) - 1;
187 for (int i = 0; i < av.getAlignment().getHeight(); i++)
189 SequenceI seq = av.getAlignment().getSequenceAt(i);
195 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
202 value = av.getAlignment().getSequenceAt(i).findPosition(index);
209 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
211 g.drawString(value + "", x, (ypos + (i * charHeight))
229 void drawEastScale(Graphics g, int startx, int endx, int ypos)
233 if (av.hasHiddenColumns())
235 endx = av.getAlignment().getHiddenColumns()
236 .adjustForHiddenColumns(endx);
241 for (int i = 0; i < av.getAlignment().getHeight(); i++)
243 seq = av.getAlignment().getSequenceAt(i);
247 while (index > startx)
249 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
256 value = seq.findPosition(index);
263 g.drawString(String.valueOf(value), 0, (ypos + (i * charHeight))
269 boolean fastpainting = false;
272 * need to make this thread safe move alignment rendering in response to
278 * shift up or down in repaint
280 public void fastPaint(int horizontal, int vertical)
282 if (fastpainting || gg == null)
289 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
290 imgHeight, -horizontal * charWidth, -vertical * charHeight);
292 ViewportRanges ranges = av.getRanges();
293 int startRes = ranges.getStartRes();
294 int endRes = ranges.getEndRes();
295 int startSeq = ranges.getStartSeq();
296 int endSeq = ranges.getEndSeq();
300 if (horizontal > 0) // scrollbar pulled right, image to the left
302 transX = (endRes - startRes - horizontal) * charWidth;
303 startRes = endRes - horizontal;
305 else if (horizontal < 0)
307 endRes = startRes - horizontal;
309 else if (vertical > 0) // scroll down
311 startSeq = endSeq - vertical;
313 if (startSeq < ranges.getStartSeq())
314 { // ie scrolling too fast, more than a page at a time
315 startSeq = ranges.getStartSeq();
319 transY = imgHeight - ((vertical + 1) * charHeight);
322 else if (vertical < 0)
324 endSeq = startSeq - vertical;
326 if (endSeq > ranges.getEndSeq())
328 endSeq = ranges.getEndSeq();
332 gg.translate(transX, transY);
333 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
334 gg.translate(-transX, -transY);
337 fastpainting = false;
341 public void paintComponent(Graphics g)
344 BufferedImage lcimg = img; // take reference since other threads may null
345 // img and call later.
346 super.paintComponent(g);
350 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
351 .getClipBounds().height)))
353 g.drawImage(lcimg, 0, 0, this);
358 // this draws the whole of the alignment
359 imgWidth = getWidth();
360 imgHeight = getHeight();
362 imgWidth -= (imgWidth % charWidth);
363 imgHeight -= (imgHeight % charHeight);
365 if ((imgWidth < 1) || (imgHeight < 1))
370 if (lcimg == null || imgWidth != lcimg.getWidth()
371 || imgHeight != lcimg.getHeight())
375 lcimg = img = new BufferedImage(imgWidth, imgHeight,
376 BufferedImage.TYPE_INT_RGB);
377 gg = (Graphics2D) img.getGraphics();
378 gg.setFont(av.getFont());
379 } catch (OutOfMemoryError er)
382 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
383 new OOMWarning("Creating alignment image for display", er);
391 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
392 RenderingHints.VALUE_ANTIALIAS_ON);
395 gg.setColor(Color.white);
396 gg.fillRect(0, 0, imgWidth, imgHeight);
398 ViewportRanges ranges = av.getRanges();
399 if (av.getWrapAlignment())
401 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
405 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
406 ranges.getStartSeq(), ranges.getEndSeq(), 0);
409 g.drawImage(lcimg, 0, 0, this);
419 * @return DOCUMENT ME!
421 public int getWrappedCanvasWidth(int cwidth)
423 FontMetrics fm = getFontMetrics(av.getFont());
428 if (av.getScaleRightWrapped())
430 LABEL_EAST = fm.stringWidth(getMask());
433 if (av.getScaleLeftWrapped())
435 LABEL_WEST = fm.stringWidth(getMask());
438 return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
442 * Generates a string of zeroes.
451 for (int i = 0; i < av.getAlignment().getHeight(); i++)
453 tmp = av.getAlignment().getSequenceAt(i).getEnd();
460 for (int i = maxWidth; i > 0; i /= 10)
474 * @param canvasHeight
479 public void drawWrappedPanel(Graphics g, int canvasWidth,
480 int canvasHeight, int startRes)
483 AlignmentI al = av.getAlignment();
485 FontMetrics fm = getFontMetrics(av.getFont());
487 if (av.getScaleRightWrapped())
489 LABEL_EAST = fm.stringWidth(getMask());
492 if (av.getScaleLeftWrapped())
494 LABEL_WEST = fm.stringWidth(getMask());
497 int hgap = charHeight;
498 if (av.getScaleAboveWrapped())
503 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
504 int cHeight = av.getAlignment().getHeight() * charHeight;
506 av.setWrappedWidth(cWidth);
508 av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
512 int maxwidth = av.getAlignment().getWidth() - 1;
514 if (av.hasHiddenColumns())
516 maxwidth = av.getAlignment().getHiddenColumns()
517 .findColumnPosition(maxwidth) - 1;
520 while ((ypos <= canvasHeight) && (startRes < maxwidth))
522 endx = startRes + cWidth - 1;
529 g.setFont(av.getFont());
530 g.setColor(Color.black);
532 if (av.getScaleLeftWrapped())
534 drawWestScale(g, startRes, endx, ypos);
537 if (av.getScaleRightWrapped())
539 g.translate(canvasWidth - LABEL_EAST, 0);
540 drawEastScale(g, startRes, endx, ypos);
541 g.translate(-(canvasWidth - LABEL_EAST), 0);
544 g.translate(LABEL_WEST, 0);
546 if (av.getScaleAboveWrapped())
548 drawNorthScale(g, startRes, endx, ypos);
551 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
553 g.setColor(Color.blue);
555 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
556 for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
558 res = hidden.findHiddenRegionPosition(i) - startRes;
560 if (res < 0 || res > endx - startRes)
566 new int[] { res * charWidth - charHeight / 4,
567 res * charWidth + charHeight / 4, res * charWidth },
568 new int[] { ypos - (charHeight / 2),
569 ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
575 // When printing we have an extra clipped region,
576 // the Printable page which we need to account for here
577 Shape clip = g.getClip();
581 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
585 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
586 (int) clip.getBounds().getHeight());
589 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
591 if (av.isShowAnnotation())
593 g.translate(0, cHeight + ypos + 3);
594 if (annotations == null)
596 annotations = new AnnotationPanel(av);
599 annotations.renderer.drawComponent(annotations, av, g, -1,
601 g.translate(0, -cHeight - ypos - 3);
604 g.translate(-LABEL_WEST, 0);
606 ypos += cHeight + getAnnotationHeight() + hgap;
612 AnnotationPanel annotations;
614 int getAnnotationHeight()
616 if (!av.isShowAnnotation())
621 if (annotations == null)
623 annotations = new AnnotationPanel(av);
626 return annotations.adjustPanelHeight();
630 * Draws the visible region of the alignment on the graphics context. If there
631 * are hidden column markers in the visible region, then each sub-region
632 * between the markers is drawn separately, followed by the hidden column
637 * offset of the first column in the visible region (0..)
639 * offset of the last column in the visible region (0..)
641 * offset of the first sequence in the visible region (0..)
643 * offset of the last sequence in the visible region (0..)
645 * vertical offset at which to draw (for wrapped alignments)
647 public void drawPanel(Graphics g1, final int startRes, final int endRes,
648 final int startSeq, final int endSeq, final int yOffset)
651 if (!av.hasHiddenColumns())
653 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
657 List<int[]> regions = av.getAlignment().getHiddenColumns()
661 final int screenYMax = endRes - startRes;
662 int blockStart = startRes;
663 int blockEnd = endRes;
665 for (int[] region : regions)
667 int hideStart = region[0];
668 int hideEnd = region[1];
670 if (hideStart <= blockStart)
672 blockStart += (hideEnd - hideStart) + 1;
677 * draw up to just before the next hidden region, or the end of
678 * the visible region, whichever comes first
680 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
683 g1.translate(screenY * charWidth, 0);
685 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
688 * draw the downline of the hidden column marker (ScalePanel draws the
689 * triangle on top) if we reached it
691 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
693 g1.setColor(Color.blue);
695 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
696 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
697 (endSeq - startSeq + 1) * charHeight + yOffset);
700 g1.translate(-screenY * charWidth, 0);
701 screenY += blockEnd - blockStart + 1;
702 blockStart = hideEnd + 1;
704 if (screenY > screenYMax)
706 // already rendered last block
711 if (screenY <= screenYMax)
713 // remaining visible region to render
714 blockEnd = blockStart + screenYMax - screenY;
715 g1.translate(screenY * charWidth, 0);
716 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
718 g1.translate(-screenY * charWidth, 0);
725 * Draws a region of the visible alignment
729 * offset of the first column in the visible region (0..)
731 * offset of the last column in the visible region (0..)
733 * offset of the first sequence in the visible region (0..)
735 * offset of the last sequence in the visible region (0..)
737 * vertical offset at which to draw (for wrapped alignments)
739 private void draw(Graphics g, int startRes, int endRes, int startSeq,
740 int endSeq, int offset)
742 g.setFont(av.getFont());
743 sr.prepare(g, av.isRenderGaps());
747 // / First draw the sequences
748 // ///////////////////////////
749 for (int i = startSeq; i <= endSeq; i++)
751 nextSeq = av.getAlignment().getSequenceAt(i);
754 // occasionally, a race condition occurs such that the alignment row is
758 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
759 startRes, endRes, offset + ((i - startSeq) * charHeight));
761 if (av.isShowSequenceFeatures())
763 fr.drawSequence(g, nextSeq, startRes, endRes, offset
764 + ((i - startSeq) * charHeight), false);
768 * highlight search Results once sequence has been drawn
770 if (av.hasSearchResults())
772 SearchResultsI searchResults = av.getSearchResults();
773 int[] visibleResults = searchResults.getResults(nextSeq,
775 if (visibleResults != null)
777 for (int r = 0; r < visibleResults.length; r += 2)
779 sr.drawHighlightedText(nextSeq, visibleResults[r],
780 visibleResults[r + 1], (visibleResults[r] - startRes)
782 + ((i - startSeq) * charHeight));
787 if (av.cursorMode && cursorY == i && cursorX >= startRes
788 && cursorX <= endRes)
790 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
791 offset + ((i - startSeq) * charHeight));
795 if (av.getSelectionGroup() != null
796 || av.getAlignment().getGroups().size() > 0)
798 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
803 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
804 int startSeq, int endSeq, int offset)
806 Graphics2D g = (Graphics2D) g1;
808 // ///////////////////////////////////
809 // Now outline any areas if necessary
810 // ///////////////////////////////////
811 SequenceGroup group = av.getSelectionGroup();
817 int visWidth = (endRes - startRes + 1) * charWidth;
819 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
821 group = av.getAlignment().getGroups().get(0);
831 boolean inGroup = false;
835 for (i = startSeq; i <= endSeq; i++)
837 sx = (group.getStartRes() - startRes) * charWidth;
838 sy = offset + ((i - startSeq) * charHeight);
839 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
841 if (sx + ex < 0 || sx > visWidth)
846 if ((sx <= (endRes - startRes) * charWidth)
847 && group.getSequences(null).contains(
848 av.getAlignment().getSequenceAt(i)))
851 && !group.getSequences(null).contains(
852 av.getAlignment().getSequenceAt(i + 1)))
854 bottom = sy + charHeight;
859 if (((top == -1) && (i == 0))
860 || !group.getSequences(null).contains(
861 av.getAlignment().getSequenceAt(i - 1)))
869 if (group == av.getSelectionGroup())
871 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
872 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
874 g.setColor(Color.RED);
878 g.setStroke(new BasicStroke());
879 g.setColor(group.getOutlineColour());
887 if (sx >= 0 && sx < visWidth)
889 g.drawLine(sx, oldY, sx, sy);
892 if (sx + ex < visWidth)
894 g.drawLine(sx + ex, oldY, sx + ex, sy);
903 if (sx + ex > visWidth)
908 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
910 ex = (endRes - startRes + 1) * charWidth;
915 g.drawLine(sx, top, sx + ex, top);
921 g.drawLine(sx, bottom, sx + ex, bottom);
932 sy = offset + ((i - startSeq) * charHeight);
933 if (sx >= 0 && sx < visWidth)
935 g.drawLine(sx, oldY, sx, sy);
938 if (sx + ex < visWidth)
940 g.drawLine(sx + ex, oldY, sx + ex, sy);
949 if (sx + ex > visWidth)
953 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
955 ex = (endRes - startRes + 1) * charWidth;
960 g.drawLine(sx, top, sx + ex, top);
966 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
975 g.setStroke(new BasicStroke());
977 if (groupIndex >= av.getAlignment().getGroups().size())
982 group = av.getAlignment().getGroups().get(groupIndex);
984 } while (groupIndex < av.getAlignment().getGroups().size());
991 * Highlights search results in the visible region by rendering as white text
992 * on a black background. Any previous highlighting is removed.
996 public void highlightSearchResults(SearchResultsI results)
1001 * for now, don't attempt fastpaint if wrapped format
1003 if (av.getWrapAlignment())
1006 av.setSearchResults(results);
1011 fastpainting = true;
1017 * to avoid redrawing the whole visible region, we instead
1018 * redraw just the minimal regions to remove previous highlights
1021 SearchResultsI previous = av.getSearchResults();
1022 av.setSearchResults(results);
1023 boolean redrawn = drawMappedPositions(previous);
1024 redrawn |= drawMappedPositions(results);
1031 fastpainting = false;
1036 * Redraws the minimal rectangle in the visible region (if any) that includes
1037 * mapped positions of the given search results. Whether or not positions are
1038 * highlighted depends on the SearchResults set on the Viewport. This allows
1039 * this method to be called to either clear or set highlighting. Answers true
1040 * if any positions were drawn (in which case a repaint is still required),
1046 protected boolean drawMappedPositions(SearchResultsI results)
1048 if (results == null)
1054 * calculate the minimal rectangle to redraw that
1055 * includes both new and existing search results
1057 int firstSeq = Integer.MAX_VALUE;
1059 int firstCol = Integer.MAX_VALUE;
1061 boolean matchFound = false;
1063 ViewportRanges ranges = av.getRanges();
1064 int firstVisibleColumn = ranges.getStartRes();
1065 int lastVisibleColumn = ranges.getEndRes();
1066 AlignmentI alignment = av.getAlignment();
1067 if (av.hasHiddenColumns())
1069 firstVisibleColumn = alignment.getHiddenColumns()
1070 .adjustForHiddenColumns(firstVisibleColumn);
1071 lastVisibleColumn = alignment.getHiddenColumns()
1072 .adjustForHiddenColumns(lastVisibleColumn);
1075 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1076 .getEndSeq(); seqNo++)
1078 SequenceI seq = alignment.getSequenceAt(seqNo);
1080 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1082 if (visibleResults != null)
1084 for (int i = 0; i < visibleResults.length - 1; i += 2)
1086 int firstMatchedColumn = visibleResults[i];
1087 int lastMatchedColumn = visibleResults[i + 1];
1088 if (firstMatchedColumn <= lastVisibleColumn
1089 && lastMatchedColumn >= firstVisibleColumn)
1092 * found a search results match in the visible region -
1093 * remember the first and last sequence matched, and the first
1094 * and last visible columns in the matched positions
1097 firstSeq = Math.min(firstSeq, seqNo);
1098 lastSeq = Math.max(lastSeq, seqNo);
1099 firstMatchedColumn = Math.max(firstMatchedColumn,
1100 firstVisibleColumn);
1101 lastMatchedColumn = Math.min(lastMatchedColumn,
1103 firstCol = Math.min(firstCol, firstMatchedColumn);
1104 lastCol = Math.max(lastCol, lastMatchedColumn);
1112 if (av.hasHiddenColumns())
1114 firstCol = alignment.getHiddenColumns()
1115 .findColumnPosition(firstCol);
1116 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1118 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1119 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1120 gg.translate(transX, transY);
1121 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1122 gg.translate(-transX, -transY);
1129 public void propertyChange(PropertyChangeEvent evt)
1131 if (!av.getWrapAlignment())
1133 if (evt.getPropertyName().equals("startres")
1134 || evt.getPropertyName().equals("endres"))
1136 // Make sure we're not trying to draw a panel
1137 // larger than the visible window
1138 ViewportRanges vpRanges = av.getRanges();
1139 int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1140 if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
1142 scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
1144 else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
1146 scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
1148 fastPaint(scrollX, 0);
1150 else if (evt.getPropertyName().equals("startseq")
1151 || evt.getPropertyName().equals("endseq"))
1153 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());