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())
1005 av.setSearchResults(results);
1010 fastpainting = true;
1016 * to avoid redrawing the whole visible region, we instead
1017 * redraw just the minimal regions to remove previous highlights
1020 SearchResultsI previous = av.getSearchResults();
1021 av.setSearchResults(results);
1022 boolean redrawn = drawMappedPositions(previous);
1023 redrawn |= drawMappedPositions(results);
1030 fastpainting = false;
1035 * Redraws the minimal rectangle in the visible region (if any) that includes
1036 * mapped positions of the given search results. Whether or not positions are
1037 * highlighted depends on the SearchResults set on the Viewport. This allows
1038 * this method to be called to either clear or set highlighting. Answers true
1039 * if any positions were drawn (in which case a repaint is still required),
1045 protected boolean drawMappedPositions(SearchResultsI results)
1047 if (results == null)
1053 * calculate the minimal rectangle to redraw that
1054 * includes both new and existing search results
1056 int firstSeq = Integer.MAX_VALUE;
1058 int firstCol = Integer.MAX_VALUE;
1060 boolean matchFound = false;
1062 ViewportRanges ranges = av.getRanges();
1063 int firstVisibleColumn = ranges.getStartRes();
1064 int lastVisibleColumn = ranges.getEndRes();
1065 AlignmentI alignment = av.getAlignment();
1066 if (av.hasHiddenColumns())
1068 firstVisibleColumn = alignment.getHiddenColumns()
1069 .adjustForHiddenColumns(firstVisibleColumn);
1070 lastVisibleColumn = alignment.getHiddenColumns()
1071 .adjustForHiddenColumns(lastVisibleColumn);
1074 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1075 .getEndSeq(); seqNo++)
1077 SequenceI seq = alignment.getSequenceAt(seqNo);
1079 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1081 if (visibleResults != null)
1083 for (int i = 0; i < visibleResults.length - 1; i += 2)
1085 int firstMatchedColumn = visibleResults[i];
1086 int lastMatchedColumn = visibleResults[i + 1];
1087 if (firstMatchedColumn <= lastVisibleColumn
1088 && lastMatchedColumn >= firstVisibleColumn)
1091 * found a search results match in the visible region -
1092 * remember the first and last sequence matched, and the first
1093 * and last visible columns in the matched positions
1096 firstSeq = Math.min(firstSeq, seqNo);
1097 lastSeq = Math.max(lastSeq, seqNo);
1098 firstMatchedColumn = Math.max(firstMatchedColumn,
1099 firstVisibleColumn);
1100 lastMatchedColumn = Math.min(lastMatchedColumn,
1102 firstCol = Math.min(firstCol, firstMatchedColumn);
1103 lastCol = Math.max(lastCol, lastMatchedColumn);
1111 if (av.hasHiddenColumns())
1113 firstCol = alignment.getHiddenColumns()
1114 .findColumnPosition(firstCol);
1115 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1117 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1118 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1119 gg.translate(transX, transY);
1120 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1121 gg.translate(-transX, -transY);
1128 public void propertyChange(PropertyChangeEvent evt)
1130 if (!av.getWrapAlignment())
1132 if (evt.getPropertyName().equals("startres")
1133 || evt.getPropertyName().equals("endres"))
1135 // Make sure we're not trying to draw a panel
1136 // larger than the visible window
1137 ViewportRanges vpRanges = av.getRanges();
1138 int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1139 if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
1141 scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
1143 else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
1145 scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
1147 fastPaint(scrollX, 0);
1149 else if (evt.getPropertyName().equals("startseq")
1150 || evt.getPropertyName().equals("endseq"))
1152 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());