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.
21 package jalview.appletgui;
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.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.viewmodel.AlignmentViewport;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.Color;
36 import java.awt.FontMetrics;
37 import java.awt.Graphics;
38 import java.awt.Image;
39 import java.awt.Panel;
40 import java.beans.PropertyChangeEvent;
41 import java.util.Iterator;
43 public class SeqCanvas extends Panel implements ViewportListenerI
59 boolean fastPaint = false;
65 public SeqCanvas(AlignViewport av)
68 fr = new FeatureRenderer(av);
69 sr = new SequenceRenderer(av);
70 PaintRefresher.Register(this, av.getSequenceSetId());
73 av.getRanges().addPropertyChangeListener(this);
76 int avcharHeight = 0, avcharWidth = 0;
78 private void updateViewport()
80 avcharHeight = av.getCharHeight();
81 avcharWidth = av.getCharWidth();
84 public AlignmentViewport getViewport()
89 public FeatureRenderer getFeatureRenderer()
94 public SequenceRenderer getSequenceRenderer()
99 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
102 g.setColor(Color.black);
103 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
106 int mpos = mark.column; // (i - startx - 1)
111 String mstring = mark.text;
117 g.drawString(mstring, mpos * avcharWidth,
118 ypos - (avcharHeight / 2));
120 g.drawLine((mpos * avcharWidth) + (avcharWidth / 2),
121 (ypos + 2) - (avcharHeight / 2),
122 (mpos * avcharWidth) + (avcharWidth / 2), ypos - 2);
127 private void drawWestScale(Graphics g, int startx, int endx, int ypos)
129 FontMetrics fm = getFontMetrics(av.getFont());
130 ypos += avcharHeight;
131 if (av.hasHiddenColumns())
133 startx = av.getAlignment().getHiddenColumns()
134 .adjustForHiddenColumns(startx);
135 endx = av.getAlignment().getHiddenColumns()
136 .adjustForHiddenColumns(endx);
139 int maxwidth = av.getAlignment().getWidth();
140 if (av.hasHiddenColumns())
142 maxwidth = av.getAlignment().getHiddenColumns()
143 .findColumnPosition(maxwidth) - 1;
147 for (int i = 0; i < av.getAlignment().getHeight(); i++)
149 SequenceI seq = av.getAlignment().getSequenceAt(i);
155 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
162 value = av.getAlignment().getSequenceAt(i).findPosition(index);
169 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
171 g.drawString(value + "", x,
172 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
177 private void drawEastScale(Graphics g, int startx, int endx, int ypos)
179 ypos += avcharHeight;
181 if (av.hasHiddenColumns())
183 endx = av.getAlignment().getHiddenColumns()
184 .adjustForHiddenColumns(endx);
189 for (int i = 0; i < av.getAlignment().getHeight(); i++)
191 seq = av.getAlignment().getSequenceAt(i);
195 while (index > startx)
197 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
204 value = seq.findPosition(index);
211 g.drawString(String.valueOf(value), 0,
212 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
219 void fastPaint(int horizontal, int vertical)
221 if (fastPaint || gg == null)
226 ViewportRanges ranges = av.getRanges();
230 // Its possible on certain browsers that the call to fastpaint
231 // is faster than it can paint, so this check here catches
233 if (lastsr + horizontal != ranges.getStartRes())
235 horizontal = ranges.getStartRes() - lastsr;
238 lastsr = ranges.getStartRes();
241 gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight,
242 imgWidth - horizontal * avcharWidth,
243 imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
244 -vertical * avcharHeight);
246 int sr = ranges.getStartRes(), er = ranges.getEndRes(),
247 ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transX = 0,
250 if (horizontal > 0) // scrollbar pulled right, image to the left
252 transX = (er - sr - horizontal) * avcharWidth;
253 sr = er - horizontal;
255 else if (horizontal < 0)
257 er = sr - horizontal;
260 else if (vertical > 0) // scroll down
263 if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
267 ss = ranges.getStartSeq();
271 transY = imgHeight - ((vertical + 1) * avcharHeight);
274 else if (vertical < 0)
277 if (es > ranges.getEndSeq())
279 es = ranges.getEndSeq();
283 gg.translate(transX, transY);
285 drawPanel(gg, sr, er, ss, es, 0);
286 gg.translate(-transX, -transY);
293 * Definitions of startx and endx (hopefully): SMJS This is what I'm working
294 * towards! startx is the first residue (starting at 0) to display. endx is
295 * the last residue to display (starting at 0). starty is the first sequence
296 * to display (starting at 0). endy is the last sequence to display (starting
297 * at 0). NOTE 1: The av limits are set in setFont in this class and in the
298 * adjustment listener in SeqPanel when the scrollbars move.
301 public void update(Graphics g)
307 public void paint(Graphics g)
311 && (fastPaint || (getSize().width != g.getClipBounds().width)
312 || (getSize().height != g.getClipBounds().height)))
314 g.drawImage(img, 0, 0, this);
321 g.drawImage(img, 0, 0, this);
327 // this draws the whole of the alignment
328 imgWidth = this.getSize().width;
329 imgHeight = this.getSize().height;
331 imgWidth -= imgWidth % avcharWidth;
332 imgHeight -= imgHeight % avcharHeight;
334 if (imgWidth < 1 || imgHeight < 1)
339 if (img == null || imgWidth != img.getWidth(this)
340 || imgHeight != img.getHeight(this))
342 img = createImage(imgWidth, imgHeight);
343 gg = img.getGraphics();
344 gg.setFont(av.getFont());
347 gg.setColor(Color.white);
348 gg.fillRect(0, 0, imgWidth, imgHeight);
350 ViewportRanges ranges = av.getRanges();
352 if (av.getWrapAlignment())
354 drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
358 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
359 ranges.getStartSeq(), ranges.getEndSeq(), 0);
362 g.drawImage(img, 0, 0, this);
366 int LABEL_WEST, LABEL_EAST;
368 public int getWrappedCanvasWidth(int cwidth)
370 cwidth -= cwidth % av.getCharWidth();
372 FontMetrics fm = getFontMetrics(av.getFont());
377 if (av.getScaleRightWrapped())
379 LABEL_EAST = fm.stringWidth(getMask());
382 if (av.getScaleLeftWrapped())
384 LABEL_WEST = fm.stringWidth(getMask());
387 return (cwidth - LABEL_EAST - LABEL_WEST) / av.getCharWidth();
391 * Generates a string of zeroes.
400 AlignmentI alignment = av.getAlignment();
401 for (int i = 0; i < alignment.getHeight(); i++)
403 tmp = alignment.getSequenceAt(i).getEnd();
410 for (int i = maxWidth; i > 0; i /= 10)
417 private void drawWrappedPanel(Graphics g, int canvasWidth,
418 int canvasHeight, int startRes)
420 AlignmentI al = av.getAlignment();
422 FontMetrics fm = getFontMetrics(av.getFont());
427 if (av.getScaleRightWrapped())
429 LABEL_EAST = fm.stringWidth(getMask());
432 if (av.getScaleLeftWrapped())
434 LABEL_WEST = fm.stringWidth(getMask());
437 int hgap = avcharHeight;
438 if (av.getScaleAboveWrapped())
440 hgap += avcharHeight;
443 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
444 int cHeight = av.getAlignment().getHeight() * avcharHeight;
446 av.setWrappedWidth(cWidth);
448 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
453 int maxwidth = av.getAlignment().getWidth();
455 if (av.hasHiddenColumns())
457 maxwidth = av.getAlignment().getHiddenColumns()
458 .findColumnPosition(maxwidth);
461 while ((ypos <= canvasHeight) && (startRes < maxwidth))
463 endx = startRes + cWidth - 1;
470 g.setColor(Color.black);
472 if (av.getScaleLeftWrapped())
474 drawWestScale(g, startRes, endx, ypos);
477 if (av.getScaleRightWrapped())
479 g.translate(canvasWidth - LABEL_EAST, 0);
480 drawEastScale(g, startRes, endx, ypos);
481 g.translate(-(canvasWidth - LABEL_EAST), 0);
484 g.translate(LABEL_WEST, 0);
486 if (av.getScaleAboveWrapped())
488 drawNorthScale(g, startRes, endx, ypos);
490 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
492 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
493 g.setColor(Color.blue);
495 Iterator<Integer> it = hidden.getBoundedStartIterator(startRes,
499 res = it.next() - startRes;
502 { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth },
504 { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3);
508 if (g.getClip() == null)
510 g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
513 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
516 if (av.isShowAnnotation())
518 g.translate(0, cHeight + ypos + 4);
519 if (annotations == null)
521 annotations = new AnnotationPanel(av);
524 annotations.drawComponent(g, startRes, endx + 1);
525 g.translate(0, -cHeight - ypos - 4);
527 g.translate(-LABEL_WEST, 0);
529 ypos += cHeight + getAnnotationHeight() + hgap;
536 AnnotationPanel annotations;
538 int getAnnotationHeight()
540 if (!av.isShowAnnotation())
545 if (annotations == null)
547 annotations = new AnnotationPanel(av);
550 return annotations.adjustPanelHeight();
553 private void drawPanel(Graphics g1, final int startRes, final int endRes,
554 final int startSeq, final int endSeq, final int offset)
557 if (!av.hasHiddenColumns())
559 draw(g1, startRes, endRes, startSeq, endSeq, offset);
567 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
568 VisibleContigsIterator regions = (VisibleContigsIterator) hidden
569 .getVisibleBlocksIterator(startRes, endRes, true);
571 while (regions.hasNext())
573 int[] region = regions.next();
574 blockEnd = region[1];
575 blockStart = region[0];
578 * draw up to just before the next hidden region, or the end of
579 * the visible region, whichever comes first
581 g1.translate(screenY * avcharWidth, 0);
583 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
586 * draw the downline of the hidden column marker (ScalePanel draws the
587 * triangle on top) if we reached it
589 if (av.getShowHiddenMarkers()
590 && (regions.hasNext() || regions.endsAtHidden()))
592 g1.setColor(Color.blue);
593 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
594 0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
595 (endSeq - startSeq + 1) * avcharHeight + offset);
598 g1.translate(-screenY * avcharWidth, 0);
599 screenY += blockEnd - blockStart + 1;
604 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
605 // int x1, int x2, int y1, int y2, int startx, int starty,
606 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
609 g.setFont(av.getFont());
610 sr.prepare(g, av.isRenderGaps());
614 // / First draw the sequences
615 // ///////////////////////////
616 for (int i = startSeq; i <= endSeq; i++)
618 nextSeq = av.getAlignment().getSequenceAt(i);
625 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
626 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
628 if (av.isShowSequenceFeatures())
630 fr.drawSequence(g, nextSeq, startRes, endRes,
631 offset + ((i - startSeq) * avcharHeight), false);
634 // / Highlight search Results once all sequences have been drawn
635 // ////////////////////////////////////////////////////////
636 if (av.hasSearchResults())
638 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
640 if (visibleResults != null)
642 for (int r = 0; r < visibleResults.length; r += 2)
644 sr.drawHighlightedText(nextSeq, visibleResults[r],
645 visibleResults[r + 1],
646 (visibleResults[r] - startRes) * avcharWidth,
647 offset + ((i - startSeq) * avcharHeight));
652 if (av.cursorMode && cursorY == i && cursorX >= startRes
653 && cursorX <= endRes)
655 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
656 offset + ((i - startSeq) * avcharHeight));
660 if (av.getSelectionGroup() != null
661 || av.getAlignment().getGroups().size() > 0)
663 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
668 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
669 int startSeq, int endSeq, int offset)
672 // ///////////////////////////////////
673 // Now outline any areas if necessary
674 // ///////////////////////////////////
675 SequenceGroup group = av.getSelectionGroup();
682 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
684 group = av.getAlignment().getGroups().get(0);
694 boolean inGroup = false;
697 int alHeight = av.getAlignment().getHeight() - 1;
699 for (i = startSeq; i <= endSeq; i++)
701 sx = (group.getStartRes() - startRes) * avcharWidth;
702 sy = offset + ((i - startSeq) * avcharHeight);
703 ex = (((group.getEndRes() + 1) - group.getStartRes())
706 if (sx + ex < 0 || sx > imgWidth)
711 if ((sx <= (endRes - startRes) * avcharWidth)
712 && group.getSequences(null)
713 .contains(av.getAlignment().getSequenceAt(i)))
716 && (i >= alHeight || !group.getSequences(null).contains(
717 av.getAlignment().getSequenceAt(i + 1))))
719 bottom = sy + avcharHeight;
724 if (((top == -1) && (i == 0)) || !group.getSequences(null)
725 .contains(av.getAlignment().getSequenceAt(i - 1)))
733 if (group == av.getSelectionGroup())
735 g.setColor(Color.red);
739 g.setColor(group.getOutlineColour());
747 if (sx >= 0 && sx < imgWidth)
749 g.drawLine(sx, oldY, sx, sy);
752 if (sx + ex < imgWidth)
754 g.drawLine(sx + ex, oldY, sx + ex, sy);
763 if (sx + ex > imgWidth)
768 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
770 ex = (endRes - startRes + 1) * avcharWidth;
775 g.drawLine(sx, top, sx + ex, top);
781 g.drawLine(sx, bottom, sx + ex, bottom);
792 sy = offset + ((i - startSeq) * avcharHeight);
793 if (sx >= 0 && sx < imgWidth)
795 g.drawLine(sx, oldY, sx, sy);
798 if (sx + ex < imgWidth)
800 g.drawLine(sx + ex, oldY, sx + ex, sy);
809 if (sx + ex > imgWidth)
813 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
815 ex = (endRes - startRes + 1) * avcharWidth;
820 g.drawLine(sx, top, sx + ex, top);
826 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
835 if (groupIndex >= av.getAlignment().getGroups().size())
840 group = av.getAlignment().getGroups().get(groupIndex);
841 } while (groupIndex < av.getAlignment().getGroups().size());
846 public void highlightSearchResults(SearchResultsI results)
848 av.setSearchResults(results);
853 public void propertyChange(PropertyChangeEvent evt)
855 String eventName = evt.getPropertyName();
857 if (!av.getWrapAlignment())
860 if (eventName.equals(ViewportRanges.STARTRES))
862 // Make sure we're not trying to draw a panel
863 // larger than the visible window
864 ViewportRanges vpRanges = av.getRanges();
865 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
866 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
871 else if (scrollX < -range)
877 // Both scrolling and resizing change viewport ranges: scrolling changes
878 // both start and end points, but resize only changes end values.
879 // Here we only want to fastpaint on a scroll, with resize using a normal
880 // paint, so scroll events are identified as changes to the horizontal or
881 // vertical start value.
882 if (eventName.equals(ViewportRanges.STARTRES))
884 // scroll - startres and endres both change
885 fastPaint(scrollX, 0);
887 else if (eventName.equals(ViewportRanges.STARTSEQ))
890 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());