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;
41 public class SeqCanvas extends Panel implements ViewportListenerI
57 boolean fastPaint = false;
63 public SeqCanvas(AlignViewport av)
66 fr = new FeatureRenderer(av);
67 sr = new SequenceRenderer(av);
68 PaintRefresher.Register(this, av.getSequenceSetId());
71 av.getRanges().addPropertyChangeListener(this);
74 int avcharHeight = 0, avcharWidth = 0;
76 private void updateViewport()
78 avcharHeight = av.getCharHeight();
79 avcharWidth = av.getCharWidth();
82 public AlignmentViewport getViewport()
87 public FeatureRenderer getFeatureRenderer()
92 public SequenceRenderer getSequenceRenderer()
97 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
100 g.setColor(Color.black);
101 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
104 int mpos = mark.column; // (i - startx - 1)
109 String mstring = mark.text;
115 g.drawString(mstring, mpos * avcharWidth, ypos
116 - (avcharHeight / 2));
118 g.drawLine((mpos * avcharWidth) + (avcharWidth / 2), (ypos + 2)
119 - (avcharHeight / 2), (mpos * avcharWidth)
120 + (avcharWidth / 2), ypos - 2);
125 private void drawWestScale(Graphics g, int startx, int endx, int ypos)
127 FontMetrics fm = getFontMetrics(av.getFont());
128 ypos += avcharHeight;
129 if (av.hasHiddenColumns())
131 startx = av.getAlignment().getHiddenColumns()
132 .adjustForHiddenColumns(startx);
133 endx = av.getAlignment().getHiddenColumns()
134 .adjustForHiddenColumns(endx);
137 int maxwidth = av.getAlignment().getWidth();
138 if (av.hasHiddenColumns())
140 maxwidth = av.getAlignment().getHiddenColumns()
141 .findColumnPosition(maxwidth) - 1;
145 for (int i = 0; i < av.getAlignment().getHeight(); i++)
147 SequenceI seq = av.getAlignment().getSequenceAt(i);
153 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
160 value = av.getAlignment().getSequenceAt(i).findPosition(index);
167 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
169 g.drawString(value + "", x, (ypos + (i * avcharHeight))
170 - (avcharHeight / 5));
175 private void drawEastScale(Graphics g, int startx, int endx, int ypos)
177 ypos += avcharHeight;
179 if (av.hasHiddenColumns())
181 endx = av.getAlignment().getHiddenColumns()
182 .adjustForHiddenColumns(endx);
187 for (int i = 0; i < av.getAlignment().getHeight(); i++)
189 seq = av.getAlignment().getSequenceAt(i);
193 while (index > startx)
195 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
202 value = seq.findPosition(index);
209 g.drawString(String.valueOf(value), 0, (ypos + (i * avcharHeight))
210 - (avcharHeight / 5));
217 void fastPaint(int horizontal, int vertical)
219 if (fastPaint || gg == null)
224 ViewportRanges ranges = av.getRanges();
228 // Its possible on certain browsers that the call to fastpaint
229 // is faster than it can paint, so this check here catches
231 if (lastsr + horizontal != ranges.getStartRes())
233 horizontal = ranges.getStartRes() - lastsr;
236 lastsr = ranges.getStartRes();
239 gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth
240 - horizontal * avcharWidth,
241 imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
242 -vertical * avcharHeight);
244 int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges
245 .getStartSeq(), es = ranges
246 .getEndSeq(), transX = 0, transY = 0;
248 if (horizontal > 0) // scrollbar pulled right, image to the left
250 transX = (er - sr - horizontal) * avcharWidth;
251 sr = er - horizontal;
253 else if (horizontal < 0)
255 er = sr - horizontal;
258 else if (vertical > 0) // scroll down
261 if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
265 ss = ranges.getStartSeq();
269 transY = imgHeight - ((vertical + 1) * avcharHeight);
272 else if (vertical < 0)
275 if (es > ranges.getEndSeq())
277 es = ranges.getEndSeq();
281 gg.translate(transX, transY);
283 drawPanel(gg, sr, er, ss, es, 0);
284 gg.translate(-transX, -transY);
291 * Definitions of startx and endx (hopefully): SMJS This is what I'm working
292 * towards! startx is the first residue (starting at 0) to display. endx is
293 * the last residue to display (starting at 0). starty is the first sequence
294 * to display (starting at 0). endy is the last sequence to display (starting
295 * at 0). NOTE 1: The av limits are set in setFont in this class and in the
296 * adjustment listener in SeqPanel when the scrollbars move.
299 public void update(Graphics g)
305 public void paint(Graphics g)
309 && (fastPaint || (getSize().width != g.getClipBounds().width) || (getSize().height != g
310 .getClipBounds().height)))
312 g.drawImage(img, 0, 0, this);
319 g.drawImage(img, 0, 0, this);
325 // this draws the whole of the alignment
326 imgWidth = this.getSize().width;
327 imgHeight = this.getSize().height;
329 imgWidth -= imgWidth % avcharWidth;
330 imgHeight -= imgHeight % avcharHeight;
332 if (imgWidth < 1 || imgHeight < 1)
337 if (img == null || imgWidth != img.getWidth(this)
338 || imgHeight != img.getHeight(this))
340 img = createImage(imgWidth, imgHeight);
341 gg = img.getGraphics();
342 gg.setFont(av.getFont());
345 gg.setColor(Color.white);
346 gg.fillRect(0, 0, imgWidth, imgHeight);
348 ViewportRanges ranges = av.getRanges();
350 if (av.getWrapAlignment())
352 drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
356 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
357 ranges.getStartSeq(), ranges.getEndSeq(), 0);
360 g.drawImage(img, 0, 0, this);
364 int LABEL_WEST, LABEL_EAST;
366 public int getWrappedCanvasWidth(int cwidth)
368 cwidth -= cwidth % av.getCharWidth();
370 FontMetrics fm = getFontMetrics(av.getFont());
375 if (av.getScaleRightWrapped())
377 LABEL_EAST = fm.stringWidth(getMask());
380 if (av.getScaleLeftWrapped())
382 LABEL_WEST = fm.stringWidth(getMask());
385 return (cwidth - LABEL_EAST - LABEL_WEST) / av.getCharWidth();
389 * Generates a string of zeroes.
398 AlignmentI alignment = av.getAlignment();
399 for (int i = 0; i < alignment.getHeight(); i++)
401 tmp = alignment.getSequenceAt(i).getEnd();
408 for (int i = maxWidth; i > 0; i /= 10)
415 private void drawWrappedPanel(Graphics g, int canvasWidth,
416 int canvasHeight, int startRes)
418 AlignmentI al = av.getAlignment();
420 FontMetrics fm = getFontMetrics(av.getFont());
422 if (av.getScaleRightWrapped())
424 LABEL_EAST = fm.stringWidth(getMask());
427 if (av.getScaleLeftWrapped())
429 LABEL_WEST = fm.stringWidth(getMask());
432 int hgap = avcharHeight;
433 if (av.getScaleAboveWrapped())
435 hgap += avcharHeight;
438 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
439 int cHeight = av.getAlignment().getHeight() * avcharHeight;
441 av.setWrappedWidth(cWidth);
443 av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
448 int maxwidth = av.getAlignment().getWidth() - 1;
450 if (av.hasHiddenColumns())
452 maxwidth = av.getAlignment().getHiddenColumns()
453 .findColumnPosition(maxwidth) - 1;
456 while ((ypos <= canvasHeight) && (startRes < maxwidth))
458 endx = startRes + cWidth - 1;
465 g.setColor(Color.black);
467 if (av.getScaleLeftWrapped())
469 drawWestScale(g, startRes, endx, ypos);
472 if (av.getScaleRightWrapped())
474 g.translate(canvasWidth - LABEL_EAST, 0);
475 drawEastScale(g, startRes, endx, ypos);
476 g.translate(-(canvasWidth - LABEL_EAST), 0);
479 g.translate(LABEL_WEST, 0);
481 if (av.getScaleAboveWrapped())
483 drawNorthScale(g, startRes, endx, ypos);
485 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
487 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
488 g.setColor(Color.blue);
490 for (int i = 0; i < hidden.getHiddenRegions()
493 res = hidden.findHiddenRegionPosition(i)
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.getHiddenRegions())
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 if (!av.getWrapAlignment())
887 if (evt.getPropertyName().equals("startres")
888 || evt.getPropertyName().equals("endres"))
890 // Make sure we're not trying to draw a panel
891 // larger than the visible window
892 ViewportRanges vpRanges = av.getRanges();
893 int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
894 if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
896 scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
898 else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
900 scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
902 fastPaint(scrollX, 0);
904 else if (evt.getPropertyName().equals("startseq")
905 || evt.getPropertyName().equals("endseq"))
907 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());