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 .visibleToAbsoluteColumn(startx);
135 endx = av.getAlignment().getHiddenColumns()
136 .visibleToAbsoluteColumn(endx);
140 for (int i = 0; i < av.getAlignment().getHeight(); i++)
142 SequenceI seq = av.getAlignment().getSequenceAt(i);
148 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
155 value = av.getAlignment().getSequenceAt(i).findPosition(index);
162 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
164 g.drawString(value + "", x,
165 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
170 private void drawEastScale(Graphics g, int startx, int endx, int ypos)
172 ypos += avcharHeight;
174 if (av.hasHiddenColumns())
176 endx = av.getAlignment().getHiddenColumns()
177 .visibleToAbsoluteColumn(endx);
182 for (int i = 0; i < av.getAlignment().getHeight(); i++)
184 seq = av.getAlignment().getSequenceAt(i);
188 while (index > startx)
190 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
197 value = seq.findPosition(index);
204 g.drawString(String.valueOf(value), 0,
205 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
212 void fastPaint(int horizontal, int vertical)
214 if (fastPaint || gg == null)
219 ViewportRanges ranges = av.getRanges();
223 // Its possible on certain browsers that the call to fastpaint
224 // is faster than it can paint, so this check here catches
226 if (lastsr + horizontal != ranges.getStartRes())
228 horizontal = ranges.getStartRes() - lastsr;
231 lastsr = ranges.getStartRes();
234 gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight,
235 imgWidth - horizontal * avcharWidth,
236 imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
237 -vertical * avcharHeight);
239 int sr = ranges.getStartRes(), er = ranges.getEndRes(),
240 ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transX = 0,
243 if (horizontal > 0) // scrollbar pulled right, image to the left
245 transX = (er - sr - horizontal) * avcharWidth;
246 sr = er - horizontal;
248 else if (horizontal < 0)
250 er = sr - horizontal;
253 else if (vertical > 0) // scroll down
256 if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
260 ss = ranges.getStartSeq();
264 transY = imgHeight - ((vertical + 1) * avcharHeight);
267 else if (vertical < 0)
270 if (es > ranges.getEndSeq())
272 es = ranges.getEndSeq();
276 gg.translate(transX, transY);
278 drawPanel(gg, sr, er, ss, es, 0);
279 gg.translate(-transX, -transY);
286 * Definitions of startx and endx (hopefully): SMJS This is what I'm working
287 * towards! startx is the first residue (starting at 0) to display. endx is
288 * the last residue to display (starting at 0). starty is the first sequence
289 * to display (starting at 0). endy is the last sequence to display (starting
290 * at 0). NOTE 1: The av limits are set in setFont in this class and in the
291 * adjustment listener in SeqPanel when the scrollbars move.
294 public void update(Graphics g)
300 public void paint(Graphics g)
304 && (fastPaint || (getSize().width != g.getClipBounds().width)
305 || (getSize().height != g.getClipBounds().height)))
307 g.drawImage(img, 0, 0, this);
314 g.drawImage(img, 0, 0, this);
320 // this draws the whole of the alignment
321 imgWidth = this.getSize().width;
322 imgHeight = this.getSize().height;
324 imgWidth -= imgWidth % avcharWidth;
325 imgHeight -= imgHeight % avcharHeight;
327 if (imgWidth < 1 || imgHeight < 1)
332 if (img == null || imgWidth != img.getWidth(this)
333 || imgHeight != img.getHeight(this))
335 img = createImage(imgWidth, imgHeight);
336 gg = img.getGraphics();
337 gg.setFont(av.getFont());
340 gg.setColor(Color.white);
341 gg.fillRect(0, 0, imgWidth, imgHeight);
343 ViewportRanges ranges = av.getRanges();
345 if (av.getWrapAlignment())
347 drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
351 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
352 ranges.getStartSeq(), ranges.getEndSeq(), 0);
355 g.drawImage(img, 0, 0, this);
359 int LABEL_WEST, LABEL_EAST;
361 public int getWrappedCanvasWidth(int cwidth)
363 cwidth -= cwidth % av.getCharWidth();
365 FontMetrics fm = getFontMetrics(av.getFont());
370 if (av.getScaleRightWrapped())
372 LABEL_EAST = fm.stringWidth(getMask());
375 if (av.getScaleLeftWrapped())
377 LABEL_WEST = fm.stringWidth(getMask());
380 return (cwidth - LABEL_EAST - LABEL_WEST) / av.getCharWidth();
384 * Generates a string of zeroes.
393 AlignmentI alignment = av.getAlignment();
394 for (int i = 0; i < alignment.getHeight(); i++)
396 tmp = alignment.getSequenceAt(i).getEnd();
403 for (int i = maxWidth; i > 0; i /= 10)
410 private void drawWrappedPanel(Graphics g, int canvasWidth,
411 int canvasHeight, int startRes)
413 AlignmentI al = av.getAlignment();
415 FontMetrics fm = getFontMetrics(av.getFont());
420 if (av.getScaleRightWrapped())
422 LABEL_EAST = fm.stringWidth(getMask());
425 if (av.getScaleLeftWrapped())
427 LABEL_WEST = fm.stringWidth(getMask());
430 int hgap = avcharHeight;
431 if (av.getScaleAboveWrapped())
433 hgap += avcharHeight;
436 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
437 int cHeight = av.getAlignment().getHeight() * avcharHeight;
439 av.setWrappedWidth(cWidth);
441 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
446 int maxwidth = av.getAlignment().getVisibleWidth();
448 while ((ypos <= canvasHeight) && (startRes < maxwidth))
450 endx = startRes + cWidth - 1;
457 g.setColor(Color.black);
459 if (av.getScaleLeftWrapped())
461 drawWestScale(g, startRes, endx, ypos);
464 if (av.getScaleRightWrapped())
466 g.translate(canvasWidth - LABEL_EAST, 0);
467 drawEastScale(g, startRes, endx, ypos);
468 g.translate(-(canvasWidth - LABEL_EAST), 0);
471 g.translate(LABEL_WEST, 0);
473 if (av.getScaleAboveWrapped())
475 drawNorthScale(g, startRes, endx, ypos);
477 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
479 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
480 g.setColor(Color.blue);
482 Iterator<Integer> it = hidden.getStartRegionIterator(startRes,
486 res = it.next() - startRes;
489 { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth },
491 { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3);
495 if (g.getClip() == null)
497 g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
500 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
503 if (av.isShowAnnotation())
505 g.translate(0, cHeight + ypos + 4);
506 if (annotations == null)
508 annotations = new AnnotationPanel(av);
511 annotations.drawComponent(g, startRes, endx + 1);
512 g.translate(0, -cHeight - ypos - 4);
514 g.translate(-LABEL_WEST, 0);
516 ypos += cHeight + getAnnotationHeight() + hgap;
523 AnnotationPanel annotations;
525 int getAnnotationHeight()
527 if (!av.isShowAnnotation())
532 if (annotations == null)
534 annotations = new AnnotationPanel(av);
537 return annotations.adjustPanelHeight();
540 private void drawPanel(Graphics g1, final int startRes, final int endRes,
541 final int startSeq, final int endSeq, final int offset)
544 if (!av.hasHiddenColumns())
546 draw(g1, startRes, endRes, startSeq, endSeq, offset);
554 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
555 VisibleContigsIterator regions = hidden
556 .getVisContigsIterator(startRes, endRes + 1, true);
558 while (regions.hasNext())
560 int[] region = regions.next();
561 blockEnd = region[1];
562 blockStart = region[0];
565 * draw up to just before the next hidden region, or the end of
566 * the visible region, whichever comes first
568 g1.translate(screenY * avcharWidth, 0);
570 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
573 * draw the downline of the hidden column marker (ScalePanel draws the
574 * triangle on top) if we reached it
576 if (av.getShowHiddenMarkers()
577 && (regions.hasNext() || regions.endsAtHidden()))
579 g1.setColor(Color.blue);
580 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
581 0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
582 (endSeq - startSeq + 1) * avcharHeight + offset);
585 g1.translate(-screenY * avcharWidth, 0);
586 screenY += blockEnd - blockStart + 1;
591 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
592 // int x1, int x2, int y1, int y2, int startx, int starty,
593 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
596 g.setFont(av.getFont());
597 sr.prepare(g, av.isRenderGaps());
601 // / First draw the sequences
602 // ///////////////////////////
603 for (int i = startSeq; i <= endSeq; i++)
605 nextSeq = av.getAlignment().getSequenceAt(i);
612 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
613 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
615 if (av.isShowSequenceFeatures())
617 fr.drawSequence(g, nextSeq, startRes, endRes,
618 offset + ((i - startSeq) * avcharHeight), false);
621 // / Highlight search Results once all sequences have been drawn
622 // ////////////////////////////////////////////////////////
623 if (av.hasSearchResults())
625 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
627 if (visibleResults != null)
629 for (int r = 0; r < visibleResults.length; r += 2)
631 sr.drawHighlightedText(nextSeq, visibleResults[r],
632 visibleResults[r + 1],
633 (visibleResults[r] - startRes) * avcharWidth,
634 offset + ((i - startSeq) * avcharHeight));
639 if (av.cursorMode && cursorY == i && cursorX >= startRes
640 && cursorX <= endRes)
642 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
643 offset + ((i - startSeq) * avcharHeight));
647 if (av.getSelectionGroup() != null
648 || av.getAlignment().getGroups().size() > 0)
650 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
655 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
656 int startSeq, int endSeq, int offset)
659 // ///////////////////////////////////
660 // Now outline any areas if necessary
661 // ///////////////////////////////////
662 SequenceGroup group = av.getSelectionGroup();
669 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
671 group = av.getAlignment().getGroups().get(0);
681 boolean inGroup = false;
684 int alHeight = av.getAlignment().getHeight() - 1;
686 for (i = startSeq; i <= endSeq; i++)
688 sx = (group.getStartRes() - startRes) * avcharWidth;
689 sy = offset + ((i - startSeq) * avcharHeight);
690 ex = (((group.getEndRes() + 1) - group.getStartRes())
693 if (sx + ex < 0 || sx > imgWidth)
698 if ((sx <= (endRes - startRes) * avcharWidth)
699 && group.getSequences(null)
700 .contains(av.getAlignment().getSequenceAt(i)))
703 && (i >= alHeight || !group.getSequences(null).contains(
704 av.getAlignment().getSequenceAt(i + 1))))
706 bottom = sy + avcharHeight;
711 if (((top == -1) && (i == 0)) || !group.getSequences(null)
712 .contains(av.getAlignment().getSequenceAt(i - 1)))
720 if (group == av.getSelectionGroup())
722 g.setColor(Color.red);
726 g.setColor(group.getOutlineColour());
734 if (sx >= 0 && sx < imgWidth)
736 g.drawLine(sx, oldY, sx, sy);
739 if (sx + ex < imgWidth)
741 g.drawLine(sx + ex, oldY, sx + ex, sy);
750 if (sx + ex > imgWidth)
755 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
757 ex = (endRes - startRes + 1) * avcharWidth;
762 g.drawLine(sx, top, sx + ex, top);
768 g.drawLine(sx, bottom, sx + ex, bottom);
779 sy = offset + ((i - startSeq) * avcharHeight);
780 if (sx >= 0 && sx < imgWidth)
782 g.drawLine(sx, oldY, sx, sy);
785 if (sx + ex < imgWidth)
787 g.drawLine(sx + ex, oldY, sx + ex, sy);
796 if (sx + ex > imgWidth)
800 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
802 ex = (endRes - startRes + 1) * avcharWidth;
807 g.drawLine(sx, top, sx + ex, top);
813 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
822 if (groupIndex >= av.getAlignment().getGroups().size())
827 group = av.getAlignment().getGroups().get(groupIndex);
828 } while (groupIndex < av.getAlignment().getGroups().size());
833 public void highlightSearchResults(SearchResultsI results)
835 av.setSearchResults(results);
840 public void propertyChange(PropertyChangeEvent evt)
842 String eventName = evt.getPropertyName();
844 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
850 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
857 if (!av.getWrapAlignment())
860 if (eventName.equals(ViewportRanges.STARTRES)
861 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
863 // Make sure we're not trying to draw a panel
864 // larger than the visible window
865 if (eventName.equals(ViewportRanges.STARTRES))
867 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
871 scrollX = ((int[]) evt.getNewValue())[0]
872 - ((int[]) evt.getOldValue())[0];
874 ViewportRanges vpRanges = av.getRanges();
875 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
880 else if (scrollX < -range)
886 // Both scrolling and resizing change viewport ranges: scrolling changes
887 // both start and end points, but resize only changes end values.
888 // Here we only want to fastpaint on a scroll, with resize using a normal
889 // paint, so scroll events are identified as changes to the horizontal or
890 // vertical start value.
891 if (eventName.equals(ViewportRanges.STARTRES))
893 // scroll - startres and endres both change
894 fastPaint(scrollX, 0);
896 else if (eventName.equals(ViewportRanges.STARTSEQ))
899 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
901 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
903 fastPaint(scrollX, 0);