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, ypos
117 - (avcharHeight / 2));
119 g.drawLine((mpos * avcharWidth) + (avcharWidth / 2), (ypos + 2)
120 - (avcharHeight / 2), (mpos * avcharWidth)
121 + (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, (ypos + (i * avcharHeight))
171 - (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, (ypos + (i * avcharHeight))
211 - (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, imgWidth
241 - horizontal * avcharWidth,
242 imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
243 -vertical * avcharHeight);
245 int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges
246 .getStartSeq(), es = ranges
247 .getEndSeq(), transX = 0, transY = 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) || (getSize().height != g
311 .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());
423 if (av.getScaleRightWrapped())
425 LABEL_EAST = fm.stringWidth(getMask());
428 if (av.getScaleLeftWrapped())
430 LABEL_WEST = fm.stringWidth(getMask());
433 int hgap = avcharHeight;
434 if (av.getScaleAboveWrapped())
436 hgap += avcharHeight;
439 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
440 int cHeight = av.getAlignment().getHeight() * avcharHeight;
442 av.setWrappedWidth(cWidth);
444 av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
449 int maxwidth = av.getAlignment().getWidth() - 1;
451 if (av.hasHiddenColumns())
453 maxwidth = av.getAlignment().getHiddenColumns()
454 .findColumnPosition(maxwidth) - 1;
457 while ((ypos <= canvasHeight) && (startRes < maxwidth))
459 endx = startRes + cWidth - 1;
466 g.setColor(Color.black);
468 if (av.getScaleLeftWrapped())
470 drawWestScale(g, startRes, endx, ypos);
473 if (av.getScaleRightWrapped())
475 g.translate(canvasWidth - LABEL_EAST, 0);
476 drawEastScale(g, startRes, endx, ypos);
477 g.translate(-(canvasWidth - LABEL_EAST), 0);
480 g.translate(LABEL_WEST, 0);
482 if (av.getScaleAboveWrapped())
484 drawNorthScale(g, startRes, endx, ypos);
486 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
488 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
489 g.setColor(Color.blue);
491 List<Integer> positions = hidden.findHiddenRegionPositions();
492 for (int pos : positions)
494 res = pos - startRes;
496 if (res < 0 || res > endx - startRes)
501 gg.fillPolygon(new int[] { res * avcharWidth - avcharHeight / 4,
502 res * avcharWidth + avcharHeight / 4, res * avcharWidth },
503 new int[] { ypos - (avcharHeight / 2),
504 ypos - (avcharHeight / 2),
505 ypos - (avcharHeight / 2) + 8 }, 3);
510 if (g.getClip() == null)
512 g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
515 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
518 if (av.isShowAnnotation())
520 g.translate(0, cHeight + ypos + 4);
521 if (annotations == null)
523 annotations = new AnnotationPanel(av);
526 annotations.drawComponent(g, startRes, endx + 1);
527 g.translate(0, -cHeight - ypos - 4);
529 g.translate(-LABEL_WEST, 0);
531 ypos += cHeight + getAnnotationHeight() + hgap;
538 AnnotationPanel annotations;
540 int getAnnotationHeight()
542 if (!av.isShowAnnotation())
547 if (annotations == null)
549 annotations = new AnnotationPanel(av);
552 return annotations.adjustPanelHeight();
555 private void drawPanel(Graphics g1, final int startRes, final int endRes,
556 final int startSeq, final int endSeq, final int offset)
559 if (!av.hasHiddenColumns())
561 draw(g1, startRes, endRes, startSeq, endSeq, offset);
566 final int screenYMax = endRes - startRes;
567 int blockStart = startRes;
568 int blockEnd = endRes;
570 if (av.hasHiddenColumns())
572 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
573 for (int[] region : hidden.getHiddenColumnsCopy())
575 int hideStart = region[0];
576 int hideEnd = region[1];
578 if (hideStart <= blockStart)
580 blockStart += (hideEnd - hideStart) + 1;
585 * draw up to just before the next hidden region, or the end of
586 * the visible region, whichever comes first
588 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
591 g1.translate(screenY * avcharWidth, 0);
593 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
596 * draw the downline of the hidden column marker (ScalePanel draws the
597 * triangle on top) if we reached it
599 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
601 g1.setColor(Color.blue);
602 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
603 0 + offset, (blockEnd - blockStart + 1) * avcharWidth
604 - 1, (endSeq - startSeq + 1) * avcharHeight
608 g1.translate(-screenY * avcharWidth, 0);
609 screenY += blockEnd - blockStart + 1;
610 blockStart = hideEnd + 1;
612 if (screenY > screenYMax)
614 // already rendered last block
619 if (screenY <= screenYMax)
621 // remaining visible region to render
622 blockEnd = blockStart + (endRes - startRes) - screenY;
623 g1.translate(screenY * avcharWidth, 0);
624 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
626 g1.translate(-screenY * avcharWidth, 0);
632 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
633 // int x1, int x2, int y1, int y2, int startx, int starty,
634 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
637 g.setFont(av.getFont());
638 sr.prepare(g, av.isRenderGaps());
642 // / First draw the sequences
643 // ///////////////////////////
644 for (int i = startSeq; i <= endSeq; i++)
646 nextSeq = av.getAlignment().getSequenceAt(i);
653 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
654 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
656 if (av.isShowSequenceFeatures())
658 fr.drawSequence(g, nextSeq, startRes, endRes, offset
659 + ((i - startSeq) * avcharHeight), false);
662 // / Highlight search Results once all sequences have been drawn
663 // ////////////////////////////////////////////////////////
664 if (av.hasSearchResults())
666 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
669 if (visibleResults != null)
671 for (int r = 0; r < visibleResults.length; r += 2)
673 sr.drawHighlightedText(nextSeq, visibleResults[r],
674 visibleResults[r + 1], (visibleResults[r] - startRes)
675 * avcharWidth, offset
676 + ((i - startSeq) * avcharHeight));
681 if (av.cursorMode && cursorY == i && cursorX >= startRes
682 && cursorX <= endRes)
684 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
685 offset + ((i - startSeq) * avcharHeight));
689 if (av.getSelectionGroup() != null
690 || av.getAlignment().getGroups().size() > 0)
692 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
697 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
698 int startSeq, int endSeq, int offset)
701 // ///////////////////////////////////
702 // Now outline any areas if necessary
703 // ///////////////////////////////////
704 SequenceGroup group = av.getSelectionGroup();
711 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
713 group = av.getAlignment().getGroups().get(0);
723 boolean inGroup = false;
726 int alHeight = av.getAlignment().getHeight() - 1;
728 for (i = startSeq; i <= endSeq; i++)
730 sx = (group.getStartRes() - startRes) * avcharWidth;
731 sy = offset + ((i - startSeq) * avcharHeight);
732 ex = (((group.getEndRes() + 1) - group.getStartRes()) * avcharWidth) - 1;
734 if (sx + ex < 0 || sx > imgWidth)
739 if ((sx <= (endRes - startRes) * avcharWidth)
740 && group.getSequences(null).contains(
741 av.getAlignment().getSequenceAt(i)))
744 && (i >= alHeight || !group.getSequences(null)
746 av.getAlignment().getSequenceAt(i + 1))))
748 bottom = sy + avcharHeight;
753 if (((top == -1) && (i == 0))
754 || !group.getSequences(null).contains(
755 av.getAlignment().getSequenceAt(i - 1)))
763 if (group == av.getSelectionGroup())
765 g.setColor(Color.red);
769 g.setColor(group.getOutlineColour());
777 if (sx >= 0 && sx < imgWidth)
779 g.drawLine(sx, oldY, sx, sy);
782 if (sx + ex < imgWidth)
784 g.drawLine(sx + ex, oldY, sx + ex, sy);
793 if (sx + ex > imgWidth)
798 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
800 ex = (endRes - startRes + 1) * avcharWidth;
805 g.drawLine(sx, top, sx + ex, top);
811 g.drawLine(sx, bottom, sx + ex, bottom);
822 sy = offset + ((i - startSeq) * avcharHeight);
823 if (sx >= 0 && sx < imgWidth)
825 g.drawLine(sx, oldY, sx, sy);
828 if (sx + ex < imgWidth)
830 g.drawLine(sx + ex, oldY, sx + ex, sy);
839 if (sx + ex > imgWidth)
843 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
845 ex = (endRes - startRes + 1) * avcharWidth;
850 g.drawLine(sx, top, sx + ex, top);
856 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
865 if (groupIndex >= av.getAlignment().getGroups().size())
870 group = av.getAlignment().getGroups().get(groupIndex);
871 } while (groupIndex < av.getAlignment().getGroups().size());
876 public void highlightSearchResults(SearchResultsI results)
878 av.setSearchResults(results);
883 public void propertyChange(PropertyChangeEvent evt)
885 String eventName = evt.getPropertyName();
887 if (!av.getWrapAlignment())
890 if (eventName.equals(ViewportRanges.STARTRES))
892 // Make sure we're not trying to draw a panel
893 // larger than the visible window
894 ViewportRanges vpRanges = av.getRanges();
895 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
896 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
901 else if (scrollX < -range)
907 // Both scrolling and resizing change viewport ranges: scrolling changes
908 // both start and end points, but resize only changes end values.
909 // Here we only want to fastpaint on a scroll, with resize using a normal
910 // paint, so scroll events are identified as changes to the horizontal or
911 // vertical start value.
912 if (eventName.equals(ViewportRanges.STARTRES))
914 // scroll - startres and endres both change
915 fastPaint(scrollX, 0);
917 else if (eventName.equals(ViewportRanges.STARTSEQ))
920 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());