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 @SuppressWarnings("serial")
44 public class SeqCanvas extends Panel implements ViewportListenerI
60 boolean fastPaint = false;
66 public SeqCanvas(AlignViewport av)
69 fr = new FeatureRenderer(av);
70 sr = new SequenceRenderer(av);
71 PaintRefresher.Register(this, av.getSequenceSetId());
74 av.getRanges().addPropertyChangeListener(this);
77 int avcharHeight = 0, avcharWidth = 0;
79 private void updateViewport()
81 avcharHeight = av.getCharHeight();
82 avcharWidth = av.getCharWidth();
85 public AlignmentViewport getViewport()
90 public FeatureRenderer getFeatureRenderer()
95 public SequenceRenderer getSequenceRenderer()
100 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
103 g.setColor(Color.black);
104 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
107 int mpos = mark.column; // (i - startx - 1)
112 String mstring = mark.text;
118 g.drawString(mstring, mpos * avcharWidth,
119 ypos - (avcharHeight / 2));
121 g.drawLine((mpos * avcharWidth) + (avcharWidth / 2),
122 (ypos + 2) - (avcharHeight / 2),
123 (mpos * avcharWidth) + (avcharWidth / 2), ypos - 2);
128 private void drawWestScale(Graphics g, int startx, int endx, int ypos)
130 FontMetrics fm = getFontMetrics(av.getFont());
131 ypos += avcharHeight;
132 if (av.hasHiddenColumns())
134 startx = av.getAlignment().getHiddenColumns()
135 .visibleToAbsoluteColumn(startx);
136 endx = av.getAlignment().getHiddenColumns()
137 .visibleToAbsoluteColumn(endx);
141 for (int i = 0; i < av.getAlignment().getHeight(); i++)
143 SequenceI seq = av.getAlignment().getSequenceAt(i);
149 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
156 value = av.getAlignment().getSequenceAt(i).findPosition(index);
163 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
165 g.drawString(value + "", x,
166 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
171 private void drawEastScale(Graphics g, int startx, int endx, int ypos)
173 ypos += avcharHeight;
175 if (av.hasHiddenColumns())
177 endx = av.getAlignment().getHiddenColumns()
178 .visibleToAbsoluteColumn(endx);
183 for (int i = 0; i < av.getAlignment().getHeight(); i++)
185 seq = av.getAlignment().getSequenceAt(i);
189 while (index > startx)
191 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
198 value = seq.findPosition(index);
205 g.drawString(String.valueOf(value), 0,
206 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
213 void fastPaint(int horizontal, int vertical)
215 if (fastPaint || gg == null)
220 ViewportRanges ranges = av.getRanges();
224 // Its possible on certain browsers that the call to fastpaint
225 // is faster than it can paint, so this check here catches
227 if (lastsr + horizontal != ranges.getStartRes())
229 horizontal = ranges.getStartRes() - lastsr;
232 lastsr = ranges.getStartRes();
235 gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight,
236 imgWidth - horizontal * avcharWidth,
237 imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
238 -vertical * avcharHeight);
240 int sr = ranges.getStartRes(), er = ranges.getEndRes(),
241 ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transX = 0,
244 if (horizontal > 0) // scrollbar pulled right, image to the left
246 transX = (er - sr - horizontal) * avcharWidth;
247 sr = er - horizontal;
249 else if (horizontal < 0)
251 er = sr - horizontal;
254 else if (vertical > 0) // scroll down
257 if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
261 ss = ranges.getStartSeq();
265 transY = imgHeight - ((vertical + 1) * avcharHeight);
268 else if (vertical < 0)
271 if (es > ranges.getEndSeq())
273 es = ranges.getEndSeq();
277 gg.translate(transX, transY);
279 drawPanel(gg, sr, er, ss, es, 0);
280 gg.translate(-transX, -transY);
287 * Definitions of startx and endx (hopefully): SMJS This is what I'm working
288 * towards! startx is the first residue (starting at 0) to display. endx is
289 * the last residue to display (starting at 0). starty is the first sequence
290 * to display (starting at 0). endy is the last sequence to display (starting
291 * at 0). NOTE 1: The av limits are set in setFont in this class and in the
292 * adjustment listener in SeqPanel when the scrollbars move.
295 public void update(Graphics g)
301 public void paint(Graphics g)
305 && (fastPaint || (getSize().width != g.getClipBounds().width)
306 || (getSize().height != g.getClipBounds().height)))
308 g.drawImage(img, 0, 0, this);
315 g.drawImage(img, 0, 0, this);
321 // this draws the whole of the alignment
322 imgWidth = this.getSize().width;
323 imgHeight = this.getSize().height;
325 imgWidth -= imgWidth % avcharWidth;
326 imgHeight -= imgHeight % avcharHeight;
328 if (imgWidth < 1 || imgHeight < 1)
333 if (img == null || imgWidth != img.getWidth(this)
334 || imgHeight != img.getHeight(this))
336 img = createImage(imgWidth, imgHeight);
337 gg = img.getGraphics();
338 gg.setFont(av.getFont());
341 gg.setColor(Color.white);
342 gg.fillRect(0, 0, imgWidth, imgHeight);
344 ViewportRanges ranges = av.getRanges();
346 if (av.getWrapAlignment())
348 drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
352 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
353 ranges.getStartSeq(), ranges.getEndSeq(), 0);
356 g.drawImage(img, 0, 0, this);
360 int LABEL_WEST, LABEL_EAST;
362 public int getWrappedCanvasWidth(int cwidth)
364 cwidth -= cwidth % av.getCharWidth();
366 FontMetrics fm = getFontMetrics(av.getFont());
371 if (av.getScaleRightWrapped())
373 LABEL_EAST = fm.stringWidth(getMask());
376 if (av.getScaleLeftWrapped())
378 LABEL_WEST = fm.stringWidth(getMask());
381 return (cwidth - LABEL_EAST - LABEL_WEST) / av.getCharWidth();
385 * Generates a string of zeroes.
394 AlignmentI alignment = av.getAlignment();
395 for (int i = 0; i < alignment.getHeight(); i++)
397 tmp = alignment.getSequenceAt(i).getEnd();
404 for (int i = maxWidth; i > 0; i /= 10)
411 private void drawWrappedPanel(Graphics g, int canvasWidth,
412 int canvasHeight, int startRes)
414 AlignmentI al = av.getAlignment();
416 FontMetrics fm = getFontMetrics(av.getFont());
421 if (av.getScaleRightWrapped())
423 LABEL_EAST = fm.stringWidth(getMask());
426 if (av.getScaleLeftWrapped())
428 LABEL_WEST = fm.stringWidth(getMask());
431 int hgap = avcharHeight;
432 if (av.getScaleAboveWrapped())
434 hgap += avcharHeight;
437 int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
438 int cHeight = av.getAlignment().getHeight() * avcharHeight;
440 av.setWrappedWidth(cWidth);
442 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
447 int maxwidth = av.getAlignment().getVisibleWidth();
449 while ((ypos <= canvasHeight) && (startRes < maxwidth))
451 endx = startRes + cWidth - 1;
458 g.setColor(Color.black);
460 if (av.getScaleLeftWrapped())
462 drawWestScale(g, startRes, endx, ypos);
465 if (av.getScaleRightWrapped())
467 g.translate(canvasWidth - LABEL_EAST, 0);
468 drawEastScale(g, startRes, endx, ypos);
469 g.translate(-(canvasWidth - LABEL_EAST), 0);
472 g.translate(LABEL_WEST, 0);
474 if (av.getScaleAboveWrapped())
476 drawNorthScale(g, startRes, endx, ypos);
478 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
480 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
481 g.setColor(Color.blue);
483 Iterator<Integer> it = hidden.getStartRegionIterator(startRes,
487 res = it.next() - startRes;
490 { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth },
492 { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3);
496 if (g.getClip() == null)
498 g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
501 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
504 if (av.isShowAnnotation())
506 g.translate(0, cHeight + ypos + 4);
507 if (annotations == null)
509 annotations = new AnnotationPanel(av);
512 annotations.drawComponent(g, startRes, endx + 1);
513 g.translate(0, -cHeight - ypos - 4);
515 g.translate(-LABEL_WEST, 0);
517 ypos += cHeight + getAnnotationHeight() + hgap;
524 AnnotationPanel annotations;
526 int getAnnotationHeight()
528 if (!av.isShowAnnotation())
533 if (annotations == null)
535 annotations = new AnnotationPanel(av);
538 return annotations.adjustPanelHeight();
541 private void drawPanel(Graphics g1, final int startRes, final int endRes,
542 final int startSeq, final int endSeq, final int offset)
545 if (!av.hasHiddenColumns())
547 draw(g1, startRes, endRes, startSeq, endSeq, offset);
555 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
556 VisibleContigsIterator regions = hidden
557 .getVisContigsIterator(startRes, endRes + 1, true);
559 while (regions.hasNext())
561 int[] region = regions.next();
562 blockEnd = region[1];
563 blockStart = region[0];
566 * draw up to just before the next hidden region, or the end of
567 * the visible region, whichever comes first
569 g1.translate(screenY * avcharWidth, 0);
571 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
574 * draw the downline of the hidden column marker (ScalePanel draws the
575 * triangle on top) if we reached it
577 if (av.getShowHiddenMarkers()
578 && (regions.hasNext() || regions.endsAtHidden()))
580 g1.setColor(Color.blue);
581 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
582 0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
583 (endSeq - startSeq + 1) * avcharHeight + offset);
586 g1.translate(-screenY * avcharWidth, 0);
587 screenY += blockEnd - blockStart + 1;
592 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
593 // int x1, int x2, int y1, int y2, int startx, int starty,
594 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
597 g.setFont(av.getFont());
598 sr.prepare(g, av.isRenderGaps());
602 // / First draw the sequences
603 // ///////////////////////////
604 for (int i = startSeq; i <= endSeq; i++)
606 nextSeq = av.getAlignment().getSequenceAt(i);
613 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
614 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
616 if (av.isShowSequenceFeatures())
618 fr.drawSequence(g, nextSeq, startRes, endRes,
619 offset + ((i - startSeq) * avcharHeight), false);
622 // / Highlight search Results once all sequences have been drawn
623 // ////////////////////////////////////////////////////////
624 if (av.hasSearchResults())
626 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
628 if (visibleResults != null)
630 for (int r = 0; r < visibleResults.length; r += 2)
632 sr.drawHighlightedText(nextSeq, visibleResults[r],
633 visibleResults[r + 1],
634 (visibleResults[r] - startRes) * avcharWidth,
635 offset + ((i - startSeq) * avcharHeight));
640 if (av.cursorMode && cursorY == i && cursorX >= startRes
641 && cursorX <= endRes)
643 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
644 offset + ((i - startSeq) * avcharHeight));
648 if (av.getSelectionGroup() != null
649 || av.getAlignment().getGroups().size() > 0)
651 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
656 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
657 int startSeq, int endSeq, int offset)
660 // ///////////////////////////////////
661 // Now outline any areas if necessary
662 // ///////////////////////////////////
663 SequenceGroup group = av.getSelectionGroup();
670 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
672 group = av.getAlignment().getGroups().get(0);
682 boolean inGroup = false;
685 int alHeight = av.getAlignment().getHeight() - 1;
687 for (i = startSeq; i <= endSeq; i++)
689 sx = (group.getStartRes() - startRes) * avcharWidth;
690 sy = offset + ((i - startSeq) * avcharHeight);
691 ex = (((group.getEndRes() + 1) - group.getStartRes())
694 if (sx + ex < 0 || sx > imgWidth)
699 if ((sx <= (endRes - startRes) * avcharWidth)
700 && group.getSequences(null)
701 .contains(av.getAlignment().getSequenceAt(i)))
704 && (i >= alHeight || !group.getSequences(null).contains(
705 av.getAlignment().getSequenceAt(i + 1))))
707 bottom = sy + avcharHeight;
712 if (((top == -1) && (i == 0)) || !group.getSequences(null)
713 .contains(av.getAlignment().getSequenceAt(i - 1)))
721 if (group == av.getSelectionGroup())
723 g.setColor(Color.red);
727 g.setColor(group.getOutlineColour());
735 if (sx >= 0 && sx < imgWidth)
737 g.drawLine(sx, oldY, sx, sy);
740 if (sx + ex < imgWidth)
742 g.drawLine(sx + ex, oldY, sx + ex, sy);
751 if (sx + ex > imgWidth)
756 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
758 ex = (endRes - startRes + 1) * avcharWidth;
763 g.drawLine(sx, top, sx + ex, top);
769 g.drawLine(sx, bottom, sx + ex, bottom);
780 sy = offset + ((i - startSeq) * avcharHeight);
781 if (sx >= 0 && sx < imgWidth)
783 g.drawLine(sx, oldY, sx, sy);
786 if (sx + ex < imgWidth)
788 g.drawLine(sx + ex, oldY, sx + ex, sy);
797 if (sx + ex > imgWidth)
801 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
803 ex = (endRes - startRes + 1) * avcharWidth;
808 g.drawLine(sx, top, sx + ex, top);
814 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
823 if (groupIndex >= av.getAlignment().getGroups().size())
828 group = av.getAlignment().getGroups().get(groupIndex);
829 } while (groupIndex < av.getAlignment().getGroups().size());
834 public void highlightSearchResults(SearchResultsI results)
836 av.setSearchResults(results);
841 public void propertyChange(PropertyChangeEvent evt)
843 String eventName = evt.getPropertyName();
845 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
851 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
858 if (!av.getWrapAlignment())
861 if (eventName.equals(ViewportRanges.STARTRES)
862 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
864 // Make sure we're not trying to draw a panel
865 // larger than the visible window
866 if (eventName.equals(ViewportRanges.STARTRES))
868 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
872 scrollX = ((int[]) evt.getNewValue())[0]
873 - ((int[]) evt.getOldValue())[0];
875 ViewportRanges vpRanges = av.getRanges();
876 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
881 else if (scrollX < -range)
887 // Both scrolling and resizing change viewport ranges: scrolling changes
888 // both start and end points, but resize only changes end values.
889 // Here we only want to fastpaint on a scroll, with resize using a normal
890 // paint, so scroll events are identified as changes to the horizontal or
891 // vertical start value.
892 if (eventName.equals(ViewportRanges.STARTRES))
894 // scroll - startres and endres both change
895 fastPaint(scrollX, 0);
897 else if (eventName.equals(ViewportRanges.STARTSEQ))
900 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
902 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
904 fastPaint(scrollX, 0);
910 * Ensure that a full paint is done next, for whatever reason. This was
911 * necessary for JavaScript; apparently in Java the timing is just right on
912 * multiple threads (EventQueue-0, Consensus, Conservation) that we can get
913 * away with one fast paint before the others, but this ensures that in the
914 * end we get a full paint. Problem arose in relation to copy/paste, where the
915 * paste was not finalized with a full paint.
917 * @author hansonr 2019.04.17
919 public void clearFastPaint()