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,
491 res * avcharWidth + avcharHeight / 4,
494 { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
495 ypos - (avcharHeight / 2) + 8 },
500 if (g.getClip() == null)
502 g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
505 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
508 if (av.isShowAnnotation())
510 g.translate(0, cHeight + ypos + 4);
511 if (annotations == null)
513 annotations = new AnnotationPanel(av);
516 annotations.drawComponent(g, startRes, endx + 1);
517 g.translate(0, -cHeight - ypos - 4);
519 g.translate(-LABEL_WEST, 0);
521 ypos += cHeight + getAnnotationHeight() + hgap;
528 AnnotationPanel annotations;
530 int getAnnotationHeight()
532 if (!av.isShowAnnotation())
537 if (annotations == null)
539 annotations = new AnnotationPanel(av);
542 return annotations.adjustPanelHeight();
545 private void drawPanel(Graphics g1, final int startRes, final int endRes,
546 final int startSeq, final int endSeq, final int offset)
549 if (!av.hasHiddenColumns())
551 draw(g1, startRes, endRes, startSeq, endSeq, offset);
559 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
560 VisibleContigsIterator regions = hidden
561 .getVisContigsIterator(startRes, endRes + 1, true);
563 while (regions.hasNext())
565 int[] region = regions.next();
566 blockEnd = region[1];
567 blockStart = region[0];
570 * draw up to just before the next hidden region, or the end of
571 * the visible region, whichever comes first
573 g1.translate(screenY * avcharWidth, 0);
575 draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
578 * draw the downline of the hidden column marker (ScalePanel draws the
579 * triangle on top) if we reached it
581 if (av.getShowHiddenMarkers()
582 && (regions.hasNext() || regions.endsAtHidden()))
584 g1.setColor(Color.blue);
585 g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
586 0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
587 (endSeq - startSeq + 1) * avcharHeight + offset);
590 g1.translate(-screenY * avcharWidth, 0);
591 screenY += blockEnd - blockStart + 1;
596 // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
597 // int x1, int x2, int y1, int y2, int startx, int starty,
598 void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
601 g.setFont(av.getFont());
602 sr.prepare(g, av.isRenderGaps());
606 // / First draw the sequences
607 // ///////////////////////////
608 for (int i = startSeq; i <= endSeq; i++)
610 nextSeq = av.getAlignment().getSequenceAt(i);
617 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
618 startRes, endRes, offset + ((i - startSeq) * avcharHeight));
620 if (av.isShowSequenceFeatures())
622 fr.drawSequence(g, nextSeq, startRes, endRes,
623 offset + ((i - startSeq) * avcharHeight), false);
626 // / Highlight search Results once all sequences have been drawn
627 // ////////////////////////////////////////////////////////
628 if (av.hasSearchResults())
630 int[] visibleResults = av.getSearchResults().getResults(nextSeq,
632 if (visibleResults != null)
634 for (int r = 0; r < visibleResults.length; r += 2)
636 sr.drawHighlightedText(nextSeq, visibleResults[r],
637 visibleResults[r + 1],
638 (visibleResults[r] - startRes) * avcharWidth,
639 offset + ((i - startSeq) * avcharHeight));
644 if (av.cursorMode && cursorY == i && cursorX >= startRes
645 && cursorX <= endRes)
647 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
648 offset + ((i - startSeq) * avcharHeight));
652 if (av.getSelectionGroup() != null
653 || av.getAlignment().getGroups().size() > 0)
655 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
660 private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
661 int startSeq, int endSeq, int offset)
664 // ///////////////////////////////////
665 // Now outline any areas if necessary
666 // ///////////////////////////////////
667 SequenceGroup group = av.getSelectionGroup();
674 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
676 group = av.getAlignment().getGroups().get(0);
686 boolean inGroup = false;
689 int alHeight = av.getAlignment().getHeight() - 1;
691 for (i = startSeq; i <= endSeq; i++)
693 sx = (group.getStartRes() - startRes) * avcharWidth;
694 sy = offset + ((i - startSeq) * avcharHeight);
695 ex = (((group.getEndRes() + 1) - group.getStartRes())
698 if (sx + ex < 0 || sx > imgWidth)
703 if ((sx <= (endRes - startRes) * avcharWidth)
704 && group.getSequences(null)
705 .contains(av.getAlignment().getSequenceAt(i)))
708 && (i >= alHeight || !group.getSequences(null).contains(
709 av.getAlignment().getSequenceAt(i + 1))))
711 bottom = sy + avcharHeight;
716 if (((top == -1) && (i == 0)) || !group.getSequences(null)
717 .contains(av.getAlignment().getSequenceAt(i - 1)))
725 if (group == av.getSelectionGroup())
727 g.setColor(Color.red);
731 g.setColor(group.getOutlineColour());
739 if (sx >= 0 && sx < imgWidth)
741 g.drawLine(sx, oldY, sx, sy);
744 if (sx + ex < imgWidth)
746 g.drawLine(sx + ex, oldY, sx + ex, sy);
755 if (sx + ex > imgWidth)
760 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
762 ex = (endRes - startRes + 1) * avcharWidth;
767 g.drawLine(sx, top, sx + ex, top);
773 g.drawLine(sx, bottom, sx + ex, bottom);
784 sy = offset + ((i - startSeq) * avcharHeight);
785 if (sx >= 0 && sx < imgWidth)
787 g.drawLine(sx, oldY, sx, sy);
790 if (sx + ex < imgWidth)
792 g.drawLine(sx + ex, oldY, sx + ex, sy);
801 if (sx + ex > imgWidth)
805 else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
807 ex = (endRes - startRes + 1) * avcharWidth;
812 g.drawLine(sx, top, sx + ex, top);
818 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
827 if (groupIndex >= av.getAlignment().getGroups().size())
832 group = av.getAlignment().getGroups().get(groupIndex);
833 } while (groupIndex < av.getAlignment().getGroups().size());
838 public void highlightSearchResults(SearchResultsI results)
840 av.setSearchResults(results);
845 public void propertyChange(PropertyChangeEvent evt)
847 String eventName = evt.getPropertyName();
849 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
855 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
862 if (!av.getWrapAlignment())
865 if (eventName.equals(ViewportRanges.STARTRES)
866 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
868 // Make sure we're not trying to draw a panel
869 // larger than the visible window
870 if (eventName.equals(ViewportRanges.STARTRES))
872 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
876 scrollX = ((int[]) evt.getNewValue())[0]
877 - ((int[]) evt.getOldValue())[0];
879 ViewportRanges vpRanges = av.getRanges();
880 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
885 else if (scrollX < -range)
891 // Both scrolling and resizing change viewport ranges: scrolling changes
892 // both start and end points, but resize only changes end values.
893 // Here we only want to fastpaint on a scroll, with resize using a normal
894 // paint, so scroll events are identified as changes to the horizontal or
895 // vertical start value.
896 if (eventName.equals(ViewportRanges.STARTRES))
898 // scroll - startres and endres both change
899 fastPaint(scrollX, 0);
901 else if (eventName.equals(ViewportRanges.STARTSEQ))
904 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
906 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
908 fastPaint(scrollX, 0);
914 * Ensure that a full paint is done next, for whatever reason. This was
915 * necessary for JavaScript; apparently in Java the timing is just right on
916 * multiple threads (EventQueue-0, Consensus, Conservation) that we can get
917 * away with one fast paint before the others, but this ensures that in the
918 * end we get a full paint. Problem arose in relation to copy/paste, where the
919 * paste was not finalized with a full paint.
921 * @author hansonr 2019.04.17
923 public void clearFastPaint()