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 sr = ranges.getStartRes();
294 int er = ranges.getEndRes();
295 int ss = ranges.getStartSeq();
296 int es = ranges.getEndSeq();
300 if (horizontal > 0) // scrollbar pulled right, image to the left
302 transX = (er - sr - horizontal) * charWidth;
303 sr = er - horizontal;
305 else if (horizontal < 0)
307 er = sr - horizontal;
309 else if (vertical > 0) // scroll down
313 if (ss < ranges.getStartSeq())
314 { // ie scrolling too fast, more than a page at a time
315 ss = ranges.getStartSeq();
319 transY = imgHeight - ((vertical + 1) * charHeight);
322 else if (vertical < 0)
326 if (es > ranges.getEndSeq())
328 es = ranges.getEndSeq();
332 gg.translate(transX, transY);
333 drawPanel(gg, sr, er, ss, es, 0);
334 gg.translate(-transX, -transY);
337 fastpainting = false;
341 * Definitions of startx and endx (hopefully): SMJS This is what I'm working
342 * towards! startx is the first residue (starting at 0) to display. endx is
343 * the last residue to display (starting at 0). starty is the first sequence
344 * to display (starting at 0). endy is the last sequence to display (starting
345 * at 0). NOTE 1: The av limits are set in setFont in this class and in the
346 * adjustment listener in SeqPanel when the scrollbars move.
349 // Set this to false to force a full panel paint
351 public void paintComponent(Graphics g)
354 BufferedImage lcimg = img; // take reference since other threads may null
355 // img and call later.
356 super.paintComponent(g);
360 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
361 .getClipBounds().height)))
363 g.drawImage(lcimg, 0, 0, this);
368 // this draws the whole of the alignment
369 imgWidth = getWidth();
370 imgHeight = getHeight();
372 imgWidth -= (imgWidth % charWidth);
373 imgHeight -= (imgHeight % charHeight);
375 if ((imgWidth < 1) || (imgHeight < 1))
380 if (lcimg == null || imgWidth != lcimg.getWidth()
381 || imgHeight != lcimg.getHeight())
385 lcimg = img = new BufferedImage(imgWidth, imgHeight,
386 BufferedImage.TYPE_INT_RGB);
387 gg = (Graphics2D) img.getGraphics();
388 gg.setFont(av.getFont());
389 } catch (OutOfMemoryError er)
392 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
393 new OOMWarning("Creating alignment image for display", er);
401 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
402 RenderingHints.VALUE_ANTIALIAS_ON);
405 gg.setColor(Color.white);
406 gg.fillRect(0, 0, imgWidth, imgHeight);
408 ViewportRanges ranges = av.getRanges();
409 if (av.getWrapAlignment())
411 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
415 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
416 ranges.getStartSeq(), ranges.getEndSeq(), 0);
419 g.drawImage(lcimg, 0, 0, this);
429 * @return DOCUMENT ME!
431 public int getWrappedCanvasWidth(int cwidth)
433 FontMetrics fm = getFontMetrics(av.getFont());
438 if (av.getScaleRightWrapped())
440 LABEL_EAST = fm.stringWidth(getMask());
443 if (av.getScaleLeftWrapped())
445 LABEL_WEST = fm.stringWidth(getMask());
448 return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
452 * Generates a string of zeroes.
461 for (int i = 0; i < av.getAlignment().getHeight(); i++)
463 tmp = av.getAlignment().getSequenceAt(i).getEnd();
470 for (int i = maxWidth; i > 0; i /= 10)
484 * @param canvasHeight
489 public void drawWrappedPanel(Graphics g, int canvasWidth,
490 int canvasHeight, int startRes)
493 AlignmentI al = av.getAlignment();
495 FontMetrics fm = getFontMetrics(av.getFont());
497 if (av.getScaleRightWrapped())
499 LABEL_EAST = fm.stringWidth(getMask());
502 if (av.getScaleLeftWrapped())
504 LABEL_WEST = fm.stringWidth(getMask());
507 int hgap = charHeight;
508 if (av.getScaleAboveWrapped())
513 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
514 int cHeight = av.getAlignment().getHeight() * charHeight;
516 av.setWrappedWidth(cWidth);
518 av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
522 int maxwidth = av.getAlignment().getWidth() - 1;
524 if (av.hasHiddenColumns())
526 maxwidth = av.getAlignment().getHiddenColumns()
527 .findColumnPosition(maxwidth) - 1;
530 while ((ypos <= canvasHeight) && (startRes < maxwidth))
532 endx = startRes + cWidth - 1;
539 g.setFont(av.getFont());
540 g.setColor(Color.black);
542 if (av.getScaleLeftWrapped())
544 drawWestScale(g, startRes, endx, ypos);
547 if (av.getScaleRightWrapped())
549 g.translate(canvasWidth - LABEL_EAST, 0);
550 drawEastScale(g, startRes, endx, ypos);
551 g.translate(-(canvasWidth - LABEL_EAST), 0);
554 g.translate(LABEL_WEST, 0);
556 if (av.getScaleAboveWrapped())
558 drawNorthScale(g, startRes, endx, ypos);
561 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
563 g.setColor(Color.blue);
565 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
566 for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
568 res = hidden.findHiddenRegionPosition(i) - startRes;
570 if (res < 0 || res > endx - startRes)
576 new int[] { res * charWidth - charHeight / 4,
577 res * charWidth + charHeight / 4, res * charWidth },
578 new int[] { ypos - (charHeight / 2),
579 ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
585 // When printing we have an extra clipped region,
586 // the Printable page which we need to account for here
587 Shape clip = g.getClip();
591 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
595 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
596 (int) clip.getBounds().getHeight());
599 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
601 if (av.isShowAnnotation())
603 g.translate(0, cHeight + ypos + 3);
604 if (annotations == null)
606 annotations = new AnnotationPanel(av);
609 annotations.renderer.drawComponent(annotations, av, g, -1,
611 g.translate(0, -cHeight - ypos - 3);
614 g.translate(-LABEL_WEST, 0);
616 ypos += cHeight + getAnnotationHeight() + hgap;
622 AnnotationPanel annotations;
624 int getAnnotationHeight()
626 if (!av.isShowAnnotation())
631 if (annotations == null)
633 annotations = new AnnotationPanel(av);
636 return annotations.adjustPanelHeight();
640 * Draws the visible region of the alignment on the graphics context. If there
641 * are hidden column markers in the visible region, then each sub-region
642 * between the markers is drawn separately, followed by the hidden column
647 * offset of the first column in the visible region (0..)
649 * offset of the last column in the visible region (0..)
651 * offset of the first sequence in the visible region (0..)
653 * offset of the last sequence in the visible region (0..)
655 * vertical offset at which to draw (for wrapped alignments)
657 public void drawPanel(Graphics g1, final int startRes, final int endRes,
658 final int startSeq, final int endSeq, final int yOffset)
661 if (!av.hasHiddenColumns())
663 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
667 List<int[]> regions = av.getAlignment().getHiddenColumns()
671 final int screenYMax = endRes - startRes;
672 int blockStart = startRes;
673 int blockEnd = endRes;
675 for (int[] region : regions)
677 int hideStart = region[0];
678 int hideEnd = region[1];
680 if (hideStart <= blockStart)
682 blockStart += (hideEnd - hideStart) + 1;
687 * draw up to just before the next hidden region, or the end of
688 * the visible region, whichever comes first
690 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
693 g1.translate(screenY * charWidth, 0);
695 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
698 * draw the downline of the hidden column marker (ScalePanel draws the
699 * triangle on top) if we reached it
701 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
703 g1.setColor(Color.blue);
705 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
706 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
707 (endSeq - startSeq + 1) * charHeight + yOffset);
710 g1.translate(-screenY * charWidth, 0);
711 screenY += blockEnd - blockStart + 1;
712 blockStart = hideEnd + 1;
714 if (screenY > screenYMax)
716 // already rendered last block
721 if (screenY <= screenYMax)
723 // remaining visible region to render
724 blockEnd = blockStart + screenYMax - screenY;
725 g1.translate(screenY * charWidth, 0);
726 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
728 g1.translate(-screenY * charWidth, 0);
735 * Draws a region of the visible alignment
739 * offset of the first column in the visible region (0..)
741 * offset of the last column in the visible region (0..)
743 * offset of the first sequence in the visible region (0..)
745 * offset of the last sequence in the visible region (0..)
747 * vertical offset at which to draw (for wrapped alignments)
749 private void draw(Graphics g, int startRes, int endRes, int startSeq,
750 int endSeq, int offset)
752 g.setFont(av.getFont());
753 sr.prepare(g, av.isRenderGaps());
757 // / First draw the sequences
758 // ///////////////////////////
759 for (int i = startSeq; i <= endSeq; i++)
761 nextSeq = av.getAlignment().getSequenceAt(i);
764 // occasionally, a race condition occurs such that the alignment row is
768 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
769 startRes, endRes, offset + ((i - startSeq) * charHeight));
771 if (av.isShowSequenceFeatures())
773 fr.drawSequence(g, nextSeq, startRes, endRes, offset
774 + ((i - startSeq) * charHeight), false);
777 // / Highlight search Results once all sequences have been drawn
778 // ////////////////////////////////////////////////////////
779 if (av.hasSearchResults())
781 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
783 if (visibleResults != null)
785 for (int r = 0; r < visibleResults.length; r += 2)
787 sr.drawHighlightedText(nextSeq, visibleResults[r],
788 visibleResults[r + 1], (visibleResults[r] - startRes)
790 + ((i - startSeq) * charHeight));
795 if (av.cursorMode && cursorY == i && cursorX >= startRes
796 && cursorX <= endRes)
798 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
799 offset + ((i - startSeq) * charHeight));
803 if (av.getSelectionGroup() != null
804 || av.getAlignment().getGroups().size() > 0)
806 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
811 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
812 int startSeq, int endSeq, int offset)
814 Graphics2D g = (Graphics2D) g1;
816 // ///////////////////////////////////
817 // Now outline any areas if necessary
818 // ///////////////////////////////////
819 SequenceGroup group = av.getSelectionGroup();
825 int visWidth = (endRes - startRes + 1) * charWidth;
827 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
829 group = av.getAlignment().getGroups().get(0);
839 boolean inGroup = false;
843 for (i = startSeq; i <= endSeq; i++)
845 sx = (group.getStartRes() - startRes) * charWidth;
846 sy = offset + ((i - startSeq) * charHeight);
847 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
849 if (sx + ex < 0 || sx > visWidth)
854 if ((sx <= (endRes - startRes) * charWidth)
855 && group.getSequences(null).contains(
856 av.getAlignment().getSequenceAt(i)))
859 && !group.getSequences(null).contains(
860 av.getAlignment().getSequenceAt(i + 1)))
862 bottom = sy + charHeight;
867 if (((top == -1) && (i == 0))
868 || !group.getSequences(null).contains(
869 av.getAlignment().getSequenceAt(i - 1)))
877 if (group == av.getSelectionGroup())
879 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
880 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
882 g.setColor(Color.RED);
886 g.setStroke(new BasicStroke());
887 g.setColor(group.getOutlineColour());
895 if (sx >= 0 && sx < visWidth)
897 g.drawLine(sx, oldY, sx, sy);
900 if (sx + ex < visWidth)
902 g.drawLine(sx + ex, oldY, sx + ex, sy);
911 if (sx + ex > visWidth)
916 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
918 ex = (endRes - startRes + 1) * charWidth;
923 g.drawLine(sx, top, sx + ex, top);
929 g.drawLine(sx, bottom, sx + ex, bottom);
940 sy = offset + ((i - startSeq) * charHeight);
941 if (sx >= 0 && sx < visWidth)
943 g.drawLine(sx, oldY, sx, sy);
946 if (sx + ex < visWidth)
948 g.drawLine(sx + ex, oldY, sx + ex, sy);
957 if (sx + ex > visWidth)
961 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
963 ex = (endRes - startRes + 1) * charWidth;
968 g.drawLine(sx, top, sx + ex, top);
974 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
983 g.setStroke(new BasicStroke());
985 if (groupIndex >= av.getAlignment().getGroups().size())
990 group = av.getAlignment().getGroups().get(groupIndex);
992 } while (groupIndex < av.getAlignment().getGroups().size());
1004 public void highlightSearchResults(SearchResultsI results)
1008 av.setSearchResults(results);
1014 public void propertyChange(PropertyChangeEvent evt)
1016 if (!av.getWrapAlignment())
1018 if (evt.getPropertyName().equals("startres")
1019 || evt.getPropertyName().equals("endres"))
1021 // Make sure we're not trying to draw a panel
1022 // larger than the visible window
1023 ViewportRanges vpRanges = av.getRanges();
1024 int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1025 if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
1027 scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
1029 else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
1031 scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
1033 fastPaint(scrollX, 0);
1035 else if (evt.getPropertyName().equals("startseq")
1036 || evt.getPropertyName().equals("endseq"))
1038 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());