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.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.viewmodel.AlignmentViewport;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
34 import java.awt.Color;
35 import java.awt.FontMetrics;
36 import java.awt.Graphics;
37 import java.awt.Image;
38 import java.awt.Panel;
39 import java.beans.PropertyChangeEvent;
40 import java.util.List;
42 public class SeqCanvas extends Panel implements ViewportListenerI
58 boolean fastPaint = false;
64 public SeqCanvas(AlignViewport av)
67 fr = new FeatureRenderer(av);
68 sr = new SequenceRenderer(av);
69 PaintRefresher.Register(this, av.getSequenceSetId());
72 av.getRanges().addPropertyChangeListener(this);
75 int avcharHeight = 0, avcharWidth = 0;
77 private void updateViewport()
79 avcharHeight = av.getCharHeight();
80 avcharWidth = av.getCharWidth();
83 public AlignmentViewport getViewport()
88 public FeatureRenderer getFeatureRenderer()
93 public SequenceRenderer getSequenceRenderer()
98 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
101 g.setColor(Color.black);
102 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
105 int mpos = mark.column; // (i - startx - 1)
110 String mstring = mark.text;
116 g.drawString(mstring, mpos * avcharWidth,
117 ypos - (avcharHeight / 2));
119 g.drawLine((mpos * avcharWidth) + (avcharWidth / 2),
120 (ypos + 2) - (avcharHeight / 2),
121 (mpos * avcharWidth) + (avcharWidth / 2), ypos - 2);
126 private void drawWestScale(Graphics g, int startx, int endx, int ypos)
128 FontMetrics fm = getFontMetrics(av.getFont());
129 ypos += avcharHeight;
130 if (av.hasHiddenColumns())
132 startx = av.getAlignment().getHiddenColumns()
133 .adjustForHiddenColumns(startx);
134 endx = av.getAlignment().getHiddenColumns()
135 .adjustForHiddenColumns(endx);
138 int maxwidth = av.getAlignment().getWidth();
139 if (av.hasHiddenColumns())
141 maxwidth = av.getAlignment().getHiddenColumns()
142 .findColumnPosition(maxwidth) - 1;
146 for (int i = 0; i < av.getAlignment().getHeight(); i++)
148 SequenceI seq = av.getAlignment().getSequenceAt(i);
154 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
161 value = av.getAlignment().getSequenceAt(i).findPosition(index);
168 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
170 g.drawString(value + "", x,
171 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
176 private void drawEastScale(Graphics g, int startx, int endx, int ypos)
178 ypos += avcharHeight;
180 if (av.hasHiddenColumns())
182 endx = av.getAlignment().getHiddenColumns()
183 .adjustForHiddenColumns(endx);
188 for (int i = 0; i < av.getAlignment().getHeight(); i++)
190 seq = av.getAlignment().getSequenceAt(i);
194 while (index > startx)
196 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
203 value = seq.findPosition(index);
210 g.drawString(String.valueOf(value), 0,
211 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
218 void fastPaint(int horizontal, int vertical)
220 if (fastPaint || gg == null)
225 ViewportRanges ranges = av.getRanges();
229 // Its possible on certain browsers that the call to fastpaint
230 // is faster than it can paint, so this check here catches
232 if (lastsr + horizontal != ranges.getStartRes())
234 horizontal = ranges.getStartRes() - lastsr;
237 lastsr = ranges.getStartRes();
240 gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight,
241 imgWidth - horizontal * avcharWidth,
242 imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
243 -vertical * avcharHeight);
245 int sr = ranges.getStartRes(), er = ranges.getEndRes(),
246 ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transX = 0,
249 if (horizontal > 0) // scrollbar pulled right, image to the left
251 transX = (er - sr - horizontal) * avcharWidth;
252 sr = er - horizontal;
254 else if (horizontal < 0)
256 er = sr - horizontal;
259 else if (vertical > 0) // scroll down
262 if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
266 ss = ranges.getStartSeq();
270 transY = imgHeight - ((vertical + 1) * avcharHeight);
273 else if (vertical < 0)
276 if (es > ranges.getEndSeq())
278 es = ranges.getEndSeq();
282 gg.translate(transX, transY);
284 drawPanel(gg, sr, er, ss, es, 0);
285 gg.translate(-transX, -transY);
292 * Definitions of startx and endx (hopefully): SMJS This is what I'm working
293 * towards! startx is the first residue (starting at 0) to display. endx is
294 * the last residue to display (starting at 0). starty is the first sequence
295 * to display (starting at 0). endy is the last sequence to display (starting
296 * at 0). NOTE 1: The av limits are set in setFont in this class and in the
297 * adjustment listener in SeqPanel when the scrollbars move.
300 public void update(Graphics g)
306 public void paint(Graphics g)
310 && (fastPaint || (getSize().width != g.getClipBounds().width)
311 || (getSize().height != g.getClipBounds().height)))
313 g.drawImage(img, 0, 0, this);
320 g.drawImage(img, 0, 0, this);
326 // this draws the whole of the alignment
327 imgWidth = this.getSize().width;
328 imgHeight = this.getSize().height;
330 imgWidth -= imgWidth % avcharWidth;
331 imgHeight -= imgHeight % avcharHeight;
333 if (imgWidth < 1 || imgHeight < 1)
338 if (img == null || imgWidth != img.getWidth(this)
339 || imgHeight != img.getHeight(this))
341 img = createImage(imgWidth, imgHeight);
342 gg = img.getGraphics();
343 gg.setFont(av.getFont());
346 gg.setColor(Color.white);
347 gg.fillRect(0, 0, imgWidth, imgHeight);
349 ViewportRanges ranges = av.getRanges();
351 if (av.getWrapAlignment())
353 drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
357 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
358 ranges.getStartSeq(), ranges.getEndSeq(), 0);
361 g.drawImage(img, 0, 0, this);
365 int LABEL_WEST, LABEL_EAST;
367 public int getWrappedCanvasWidth(int cwidth)
369 cwidth -= cwidth % av.getCharWidth();
371 FontMetrics fm = getFontMetrics(av.getFont());
376 if (av.getScaleRightWrapped())
378 LABEL_EAST = fm.stringWidth(getMask());
381 if (av.getScaleLeftWrapped())
383 LABEL_WEST = fm.stringWidth(getMask());
386 return (cwidth - LABEL_EAST - LABEL_WEST) / av.getCharWidth();
390 * Generates a string of zeroes.
399 AlignmentI alignment = av.getAlignment();
400 for (int i = 0; i < alignment.getHeight(); i++)
402 tmp = alignment.getSequenceAt(i).getEnd();
409 for (int i = maxWidth; i > 0; i /= 10)
416 private void drawWrappedPanel(Graphics g, int canvasWidth,
417 int canvasHeight, int startRes)
419 AlignmentI al = av.getAlignment();
421 FontMetrics fm = getFontMetrics(av.getFont());
426 if (av.getScaleRightWrapped())
428 LABEL_EAST = fm.stringWidth(getMask());
431 if (av.getScaleLeftWrapped())
433 LABEL_WEST = fm.stringWidth(getMask());
436 int hgap = avcharHeight;
437 if (av.getScaleAboveWrapped())
439 hgap += avcharHeight;
442 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
443 int cHeight = av.getAlignment().getHeight() * avcharHeight;
445 av.setWrappedWidth(cWidth);
447 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
452 int maxwidth = av.getAlignment().getWidth();
454 if (av.hasHiddenColumns())
456 maxwidth = av.getAlignment().getHiddenColumns()
457 .findColumnPosition(maxwidth);
460 while ((ypos <= canvasHeight) && (startRes < maxwidth))
462 endx = startRes + cWidth - 1;
469 g.setColor(Color.black);
471 if (av.getScaleLeftWrapped())
473 drawWestScale(g, startRes, endx, ypos);
476 if (av.getScaleRightWrapped())
478 g.translate(canvasWidth - LABEL_EAST, 0);
479 drawEastScale(g, startRes, endx, ypos);
480 g.translate(-(canvasWidth - LABEL_EAST), 0);
483 g.translate(LABEL_WEST, 0);
485 if (av.getScaleAboveWrapped())
487 drawNorthScale(g, startRes, endx, ypos);
489 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
491 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
492 g.setColor(Color.blue);
494 List<Integer> positions = hidden.findHiddenRegionPositions();
495 for (int pos : positions)
497 res = pos - startRes;
499 if (res < 0 || res > endx - startRes)
506 { res * avcharWidth - avcharHeight / 4,
507 res * avcharWidth + avcharHeight / 4,
510 { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
511 ypos - (avcharHeight / 2) + 8 },
517 if (g.getClip() == null)
519 g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
522 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
525 if (av.isShowAnnotation())
527 g.translate(0, cHeight + ypos + 4);
528 if (annotations == null)
530 annotations = new AnnotationPanel(av);
533 annotations.drawComponent(g, startRes, endx + 1);
534 g.translate(0, -cHeight - ypos - 4);
536 g.translate(-LABEL_WEST, 0);
538 ypos += cHeight + getAnnotationHeight() + hgap;
545 AnnotationPanel annotations;
547 int getAnnotationHeight()
549 if (!av.isShowAnnotation())
554 if (annotations == null)
556 annotations = new AnnotationPanel(av);
559 return annotations.adjustPanelHeight();
562 private void drawPanel(Graphics g1, final int startRes, final int endRes,
563 final int startSeq, final int endSeq, final int offset)
566 if (!av.hasHiddenColumns())
568 draw(g1, startRes, endRes, startSeq, endSeq, offset);
573 final int screenYMax = endRes - startRes;
574 int blockStart = startRes;
575 int blockEnd = endRes;
577 if (av.hasHiddenColumns())
579 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
580 for (int[] region : hidden.getHiddenColumnsCopy())
582 int hideStart = region[0];
583 int hideEnd = region[1];
585 if (hideStart <= blockStart)
587 blockStart += (hideEnd - hideStart) + 1;
592 * draw up to just before the next hidden region, or the end of
593 * the visible region, whichever comes first
595 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
598 g1.translate(screenY * avcharWidth, 0);
600 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
603 * draw the downline of the hidden column marker (ScalePanel draws the
604 * triangle on top) if we reached it
606 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
608 g1.setColor(Color.blue);
609 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
611 (blockEnd - blockStart + 1) * avcharWidth - 1,
612 (endSeq - startSeq + 1) * avcharHeight + offset);
615 g1.translate(-screenY * avcharWidth, 0);
616 screenY += blockEnd - blockStart + 1;
617 blockStart = hideEnd + 1;
619 if (screenY > screenYMax)
621 // already rendered last block
626 if (screenY <= screenYMax)
628 // remaining visible region to render
629 blockEnd = blockStart + (endRes - startRes) - screenY;
630 g1.translate(screenY * avcharWidth, 0);
631 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
633 g1.translate(-screenY * avcharWidth, 0);
639 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
640 // int x1, int x2, int y1, int y2, int startx, int starty,
641 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
644 g.setFont(av.getFont());
645 sr.prepare(g, av.isRenderGaps());
649 // / First draw the sequences
650 // ///////////////////////////
651 for (int i = startSeq; i <= endSeq; i++)
653 nextSeq = av.getAlignment().getSequenceAt(i);
660 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
661 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
663 if (av.isShowSequenceFeatures())
665 fr.drawSequence(g, nextSeq, startRes, endRes,
666 offset + ((i - startSeq) * avcharHeight), false);
669 // / Highlight search Results once all sequences have been drawn
670 // ////////////////////////////////////////////////////////
671 if (av.hasSearchResults())
673 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
675 if (visibleResults != null)
677 for (int r = 0; r < visibleResults.length; r += 2)
679 sr.drawHighlightedText(nextSeq, visibleResults[r],
680 visibleResults[r + 1],
681 (visibleResults[r] - startRes) * avcharWidth,
682 offset + ((i - startSeq) * avcharHeight));
687 if (av.cursorMode && cursorY == i && cursorX >= startRes
688 && cursorX <= endRes)
690 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
691 offset + ((i - startSeq) * avcharHeight));
695 if (av.getSelectionGroup() != null
696 || av.getAlignment().getGroups().size() > 0)
698 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
703 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
704 int startSeq, int endSeq, int offset)
707 // ///////////////////////////////////
708 // Now outline any areas if necessary
709 // ///////////////////////////////////
710 SequenceGroup group = av.getSelectionGroup();
717 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
719 group = av.getAlignment().getGroups().get(0);
729 boolean inGroup = false;
732 int alHeight = av.getAlignment().getHeight() - 1;
734 for (i = startSeq; i <= endSeq; i++)
736 sx = (group.getStartRes() - startRes) * avcharWidth;
737 sy = offset + ((i - startSeq) * avcharHeight);
738 ex = (((group.getEndRes() + 1) - group.getStartRes())
741 if (sx + ex < 0 || sx > imgWidth)
746 if ((sx <= (endRes - startRes) * avcharWidth)
747 && group.getSequences(null)
748 .contains(av.getAlignment().getSequenceAt(i)))
751 && (i >= alHeight || !group.getSequences(null).contains(
752 av.getAlignment().getSequenceAt(i + 1))))
754 bottom = sy + avcharHeight;
759 if (((top == -1) && (i == 0)) || !group.getSequences(null)
760 .contains(av.getAlignment().getSequenceAt(i - 1)))
768 if (group == av.getSelectionGroup())
770 g.setColor(Color.red);
774 g.setColor(group.getOutlineColour());
782 if (sx >= 0 && sx < imgWidth)
784 g.drawLine(sx, oldY, sx, sy);
787 if (sx + ex < imgWidth)
789 g.drawLine(sx + ex, oldY, sx + ex, sy);
798 if (sx + ex > imgWidth)
803 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
805 ex = (endRes - startRes + 1) * avcharWidth;
810 g.drawLine(sx, top, sx + ex, top);
816 g.drawLine(sx, bottom, sx + ex, bottom);
827 sy = offset + ((i - startSeq) * avcharHeight);
828 if (sx >= 0 && sx < imgWidth)
830 g.drawLine(sx, oldY, sx, sy);
833 if (sx + ex < imgWidth)
835 g.drawLine(sx + ex, oldY, sx + ex, sy);
844 if (sx + ex > imgWidth)
848 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
850 ex = (endRes - startRes + 1) * avcharWidth;
855 g.drawLine(sx, top, sx + ex, top);
861 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
870 if (groupIndex >= av.getAlignment().getGroups().size())
875 group = av.getAlignment().getGroups().get(groupIndex);
876 } while (groupIndex < av.getAlignment().getGroups().size());
881 public void highlightSearchResults(SearchResultsI results)
883 av.setSearchResults(results);
888 public void propertyChange(PropertyChangeEvent evt)
890 String eventName = evt.getPropertyName();
892 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
898 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
905 if (!av.getWrapAlignment())
908 if (eventName.equals(ViewportRanges.STARTRES)
909 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
911 // Make sure we're not trying to draw a panel
912 // larger than the visible window
913 if (eventName.equals(ViewportRanges.STARTRES))
915 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
919 scrollX = ((int[]) evt.getNewValue())[0]
920 - ((int[]) evt.getOldValue())[0];
922 ViewportRanges vpRanges = av.getRanges();
923 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
928 else if (scrollX < -range)
934 // Both scrolling and resizing change viewport ranges: scrolling changes
935 // both start and end points, but resize only changes end values.
936 // Here we only want to fastpaint on a scroll, with resize using a normal
937 // paint, so scroll events are identified as changes to the horizontal or
938 // vertical start value.
939 if (eventName.equals(ViewportRanges.STARTRES))
941 // scroll - startres and endres both change
942 fastPaint(scrollX, 0);
944 else if (eventName.equals(ViewportRanges.STARTSEQ))
947 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
949 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
951 fastPaint(scrollX, 0);