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);
495 // BH 2020.03.19 avoiding g.setClip at all costs
497 if (g.getClip() == null)
499 g.clipRect(0, 0, cWidth * avcharWidth, canvasHeight);
502 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
506 if (av.isShowAnnotation())
508 g.translate(0, cHeight + ypos + 4);
509 if (annotations == null)
511 annotations = new AnnotationPanel(av);
514 annotations.drawComponent(g, startRes, endx + 1);
515 g.translate(0, -cHeight - ypos - 4);
517 g.translate(-LABEL_WEST, 0);
519 ypos += cHeight + getAnnotationHeight() + hgap;
526 AnnotationPanel annotations;
528 int getAnnotationHeight()
530 if (!av.isShowAnnotation())
535 if (annotations == null)
537 annotations = new AnnotationPanel(av);
540 return annotations.adjustPanelHeight();
543 private void drawPanel(Graphics g1, final int startRes, final int endRes,
544 final int startSeq, final int endSeq, final int offset)
547 if (!av.hasHiddenColumns())
549 draw(g1, startRes, endRes, startSeq, endSeq, offset);
557 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
558 VisibleContigsIterator regions = hidden
559 .getVisContigsIterator(startRes, endRes + 1, true);
561 while (regions.hasNext())
563 int[] region = regions.next();
564 blockEnd = region[1];
565 blockStart = region[0];
568 * draw up to just before the next hidden region, or the end of
569 * the visible region, whichever comes first
571 g1.translate(screenY * avcharWidth, 0);
573 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
576 * draw the downline of the hidden column marker (ScalePanel draws the
577 * triangle on top) if we reached it
579 if (av.getShowHiddenMarkers()
580 && (regions.hasNext() || regions.endsAtHidden()))
582 g1.setColor(Color.blue);
583 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
584 0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
585 (endSeq - startSeq + 1) * avcharHeight + offset);
588 g1.translate(-screenY * avcharWidth, 0);
589 screenY += blockEnd - blockStart + 1;
594 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
595 // int x1, int x2, int y1, int y2, int startx, int starty,
596 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
599 g.setFont(av.getFont());
600 sr.prepare(g, av.isRenderGaps());
604 // / First draw the sequences
605 // ///////////////////////////
606 for (int i = startSeq; i <= endSeq; i++)
608 nextSeq = av.getAlignment().getSequenceAt(i);
615 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
616 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
618 if (av.isShowSequenceFeatures())
620 fr.drawSequence(g, nextSeq, startRes, endRes,
621 offset + ((i - startSeq) * avcharHeight), false);
624 // / Highlight search Results once all sequences have been drawn
625 // ////////////////////////////////////////////////////////
626 if (av.hasSearchResults())
628 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
630 if (visibleResults != null)
632 for (int r = 0; r < visibleResults.length; r += 2)
634 sr.drawHighlightedText(nextSeq, visibleResults[r],
635 visibleResults[r + 1],
636 (visibleResults[r] - startRes) * avcharWidth,
637 offset + ((i - startSeq) * avcharHeight));
642 if (av.cursorMode && cursorY == i && cursorX >= startRes
643 && cursorX <= endRes)
645 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
646 offset + ((i - startSeq) * avcharHeight));
650 if (av.getSelectionGroup() != null
651 || av.getAlignment().getGroups().size() > 0)
653 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
658 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
659 int startSeq, int endSeq, int offset)
662 // ///////////////////////////////////
663 // Now outline any areas if necessary
664 // ///////////////////////////////////
665 SequenceGroup group = av.getSelectionGroup();
672 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
674 group = av.getAlignment().getGroups().get(0);
684 boolean inGroup = false;
687 int alHeight = av.getAlignment().getHeight() - 1;
689 for (i = startSeq; i <= endSeq; i++)
691 sx = (group.getStartRes() - startRes) * avcharWidth;
692 sy = offset + ((i - startSeq) * avcharHeight);
693 ex = (((group.getEndRes() + 1) - group.getStartRes())
696 if (sx + ex < 0 || sx > imgWidth)
701 if ((sx <= (endRes - startRes) * avcharWidth)
702 && group.getSequences(null)
703 .contains(av.getAlignment().getSequenceAt(i)))
706 && (i >= alHeight || !group.getSequences(null).contains(
707 av.getAlignment().getSequenceAt(i + 1))))
709 bottom = sy + avcharHeight;
714 if (((top == -1) && (i == 0)) || !group.getSequences(null)
715 .contains(av.getAlignment().getSequenceAt(i - 1)))
723 if (group == av.getSelectionGroup())
725 g.setColor(Color.red);
729 g.setColor(group.getOutlineColour());
737 if (sx >= 0 && sx < imgWidth)
739 g.drawLine(sx, oldY, sx, sy);
742 if (sx + ex < imgWidth)
744 g.drawLine(sx + ex, oldY, sx + ex, sy);
753 if (sx + ex > imgWidth)
758 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
760 ex = (endRes - startRes + 1) * avcharWidth;
765 g.drawLine(sx, top, sx + ex, top);
771 g.drawLine(sx, bottom, sx + ex, bottom);
782 sy = offset + ((i - startSeq) * avcharHeight);
783 if (sx >= 0 && sx < imgWidth)
785 g.drawLine(sx, oldY, sx, sy);
788 if (sx + ex < imgWidth)
790 g.drawLine(sx + ex, oldY, sx + ex, sy);
799 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 - 1, sx + ex, bottom - 1);
825 if (groupIndex >= av.getAlignment().getGroups().size())
830 group = av.getAlignment().getGroups().get(groupIndex);
831 } while (groupIndex < av.getAlignment().getGroups().size());
836 public void highlightSearchResults(SearchResultsI results)
838 av.setSearchResults(results);
843 public void propertyChange(PropertyChangeEvent evt)
845 String eventName = evt.getPropertyName();
847 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
853 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
860 if (!av.getWrapAlignment())
863 if (eventName.equals(ViewportRanges.STARTRES)
864 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
866 // Make sure we're not trying to draw a panel
867 // larger than the visible window
868 if (eventName.equals(ViewportRanges.STARTRES))
870 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
874 scrollX = ((int[]) evt.getNewValue())[0]
875 - ((int[]) evt.getOldValue())[0];
877 ViewportRanges vpRanges = av.getRanges();
878 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
883 else if (scrollX < -range)
889 // Both scrolling and resizing change viewport ranges: scrolling changes
890 // both start and end points, but resize only changes end values.
891 // Here we only want to fastpaint on a scroll, with resize using a normal
892 // paint, so scroll events are identified as changes to the horizontal or
893 // vertical start value.
894 if (eventName.equals(ViewportRanges.STARTRES))
896 // scroll - startres and endres both change
897 fastPaint(scrollX, 0);
899 else if (eventName.equals(ViewportRanges.STARTSEQ))
902 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
904 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
906 fastPaint(scrollX, 0);
912 * Ensure that a full paint is done next, for whatever reason. This was
913 * necessary for JavaScript; apparently in Java the timing is just right on
914 * multiple threads (EventQueue-0, Consensus, Conservation) that we can get
915 * away with one fast paint before the others, but this ensures that in the
916 * end we get a full paint. Problem arose in relation to copy/paste, where the
917 * paste was not finalized with a full paint.
919 * @author hansonr 2019.04.17
921 public void clearFastPaint()