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.
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.ViewportListenerI;
31 import jalview.viewmodel.ViewportRanges;
33 import java.awt.AlphaComposite;
34 import java.awt.BasicStroke;
35 import java.awt.BorderLayout;
36 import java.awt.Color;
37 import java.awt.FontMetrics;
38 import java.awt.Graphics;
39 import java.awt.Graphics2D;
40 import java.awt.RenderingHints;
41 import java.awt.Shape;
42 import java.awt.image.BufferedImage;
43 import java.beans.PropertyChangeEvent;
44 import java.util.List;
46 import javax.swing.JComponent;
54 public class SeqCanvas extends JComponent implements ViewportListenerI
56 private static String ZEROS = "0000000000";
58 final FeatureRenderer fr;
60 final SequenceRenderer seqRdr;
68 boolean fastPaint = false;
82 boolean fastpainting = false;
84 AnnotationPanel annotations;
87 * Creates a new SeqCanvas object.
92 public SeqCanvas(AlignmentPanel ap)
96 fr = new FeatureRenderer(ap);
97 seqRdr = new SequenceRenderer(av);
98 setLayout(new BorderLayout());
99 PaintRefresher.Register(this, av.getSequenceSetId());
100 setBackground(Color.white);
102 av.getRanges().addPropertyChangeListener(this);
105 public SequenceRenderer getSequenceRenderer()
110 public FeatureRenderer getFeatureRenderer()
115 private void updateViewport()
117 charHeight = av.getCharHeight();
118 charWidth = av.getCharWidth();
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
139 int mpos = mark.column; // (i - startx - 1)
144 String mstring = mark.text;
150 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
152 g.drawLine((mpos * charWidth) + (charWidth / 2),
153 (ypos + 2) - (charHeight / 2),
154 (mpos * charWidth) + (charWidth / 2), ypos - 2);
171 void drawWestScale(Graphics g, int startx, int endx, int ypos)
173 FontMetrics fm = getFontMetrics(av.getFont());
176 if (av.hasHiddenColumns())
178 startx = av.getAlignment().getHiddenColumns()
179 .adjustForHiddenColumns(startx);
180 endx = av.getAlignment().getHiddenColumns()
181 .adjustForHiddenColumns(endx);
184 int maxwidth = av.getAlignment().getWidth();
185 if (av.hasHiddenColumns())
187 maxwidth = av.getAlignment().getHiddenColumns()
188 .findColumnPosition(maxwidth) - 1;
192 for (int i = 0; i < av.getAlignment().getHeight(); i++)
194 SequenceI seq = av.getAlignment().getSequenceAt(i);
200 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
207 value = av.getAlignment().getSequenceAt(i).findPosition(index);
214 int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
216 g.drawString(value + "", x,
217 (ypos + (i * charHeight)) - (charHeight / 5));
234 void drawEastScale(Graphics g, int startx, int endx, int ypos)
238 if (av.hasHiddenColumns())
240 endx = av.getAlignment().getHiddenColumns()
241 .adjustForHiddenColumns(endx);
246 for (int i = 0; i < av.getAlignment().getHeight(); i++)
248 seq = av.getAlignment().getSequenceAt(i);
252 while (index > startx)
254 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
261 value = seq.findPosition(index);
268 g.drawString(String.valueOf(value), 0,
269 (ypos + (i * charHeight)) - (charHeight / 5));
276 * need to make this thread safe move alignment rendering in response to
282 * shift up or down in repaint
284 public void fastPaint(int horizontal, int vertical)
286 if (fastpainting || gg == null)
294 ViewportRanges ranges = av.getRanges();
295 int startRes = ranges.getStartRes();
296 int endRes = ranges.getEndRes();
297 int startSeq = ranges.getStartSeq();
298 int endSeq = ranges.getEndSeq();
302 gg.copyArea(horizontal * charWidth, vertical * charHeight,
303 img.getWidth(), img.getHeight(), -horizontal * charWidth,
304 -vertical * charHeight);
306 if (horizontal > 0) // scrollbar pulled right, image to the left
308 transX = (endRes - startRes - horizontal) * charWidth;
309 startRes = endRes - horizontal;
311 else if (horizontal < 0)
313 endRes = startRes - horizontal;
315 else if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
343 fastpainting = false;
347 public void paintComponent(Graphics g)
349 super.paintComponent(g);
353 ViewportRanges ranges = av.getRanges();
355 int width = getWidth();
356 int height = getHeight();
358 width -= (width % charWidth);
359 height -= (height % charHeight);
361 // selectImage is the selection group outline image
362 BufferedImage selectImage = drawSelectionGroup(
363 ranges.getStartRes(), ranges.getEndRes(),
364 ranges.getStartSeq(), ranges.getEndSeq());
366 if ((img != null) && (fastPaint
367 || (getVisibleRect().width != g.getClipBounds().width)
368 || (getVisibleRect().height != g.getClipBounds().height)))
370 BufferedImage lcimg = buildLocalImage(selectImage);
371 g.drawImage(lcimg, 0, 0, this);
374 else if ((width > 0) && (height > 0))
376 // img is a cached version of the last view we drew, if any
377 // if we have no img or the size has changed, make a new one
378 if (img == null || width != img.getWidth()
379 || height != img.getHeight())
386 gg = (Graphics2D) img.getGraphics();
387 gg.setFont(av.getFont());
392 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
393 RenderingHints.VALUE_ANTIALIAS_ON);
396 gg.setColor(Color.white);
397 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
399 if (av.getWrapAlignment())
401 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
405 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
406 ranges.getStartSeq(), ranges.getEndSeq(), 0);
409 // lcimg is a local *copy* of img which we'll draw selectImage on top of
410 BufferedImage lcimg = buildLocalImage(selectImage);
411 g.drawImage(lcimg, 0, 0, this);
416 * Draw an alignment panel for printing
419 * Graphics object to draw with
421 * start residue of print area
423 * end residue of print area
425 * start sequence of print area
427 * end sequence of print area
429 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
430 int startSeq, int endSeq)
432 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
434 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
435 ((Graphics2D) g1).setComposite(
436 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
437 g1.drawImage(selectImage, 0, 0, this);
441 * Draw a wrapped alignment panel for printing
444 * Graphics object to draw with
446 * width of drawing area
447 * @param canvasHeight
448 * height of drawing area
450 * start residue of print area
452 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
453 int canvasHeight, int startRes)
455 SequenceGroup group = av.getSelectionGroup();
456 BufferedImage selectImage = new BufferedImage(canvasWidth, canvasHeight,
457 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
461 Graphics2D g2 = selectImage.createGraphics();
462 setupSelectionGroup(g2, selectImage);
463 drawWrappedSelection(g2, group, canvasWidth, canvasHeight, startRes);
464 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
465 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
466 g.drawImage(selectImage, 0, 0, this);
472 * Make a local image by combining the cached image img
475 private BufferedImage buildLocalImage(BufferedImage selectImage)
477 // clone the cached image
478 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
480 Graphics2D g2d = lcimg.createGraphics();
481 g2d.drawImage(img, 0, 0, null);
483 // overlay selection group on lcimg
484 if (selectImage != null)
487 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
488 g2d.drawImage(selectImage, 0, 0, this);
496 * Set up a buffered image of the correct height and size for the sequence canvas
498 private BufferedImage setupImage()
500 BufferedImage lcimg = null;
502 int width = getWidth();
503 int height = getHeight();
505 width -= (width % charWidth);
506 height -= (height % charHeight);
508 if ((width < 1) || (height < 1))
515 lcimg = new BufferedImage(width, height,
516 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
517 } catch (OutOfMemoryError er)
521 "Group image OutOfMemory Redraw Error.\n" + er);
522 new OOMWarning("Creating alignment image for display", er);
531 * Returns the visible width of the canvas in residues, after allowing for
532 * East or West scales (if shown)
535 * the width in pixels (possibly including scales)
539 public int getWrappedCanvasWidth(int canvasWidth)
541 FontMetrics fm = getFontMetrics(av.getFont());
546 if (av.getScaleRightWrapped())
548 labelWidthEast = getLabelWidth(fm);
551 if (av.getScaleLeftWrapped())
553 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
557 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
561 * Returns a pixel width suitable for showing the largest sequence coordinate
562 * (end position) in the alignment. Returns 2 plus the number of decimal
563 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
568 protected int getLabelWidth(FontMetrics fm)
571 * find the biggest sequence end position we need to show
572 * (note this is not necessarily the sequence length)
575 AlignmentI alignment = av.getAlignment();
576 for (int i = 0; i < alignment.getHeight(); i++)
578 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
582 for (int i = maxWidth; i > 0; i /= 10)
587 return fm.stringWidth(ZEROS.substring(0, length));
597 * @param canvasHeight
602 private void drawWrappedPanel(Graphics g, int canvasWidth,
603 int canvasHeight, int startRes)
606 AlignmentI al = av.getAlignment();
609 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
611 FontMetrics fm = getFontMetrics(av.getFont());
612 labelWidth = getLabelWidth(fm);
615 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
616 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
618 int hgap = charHeight;
619 if (av.getScaleAboveWrapped())
624 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
625 int cHeight = av.getAlignment().getHeight() * charHeight;
627 av.setWrappedWidth(cWidth);
629 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
633 int maxwidth = av.getAlignment().getWidth();
635 if (av.hasHiddenColumns())
637 maxwidth = av.getAlignment().getHiddenColumns()
638 .findColumnPosition(maxwidth);
641 int annotationHeight = getAnnotationHeight();
643 while ((ypos <= canvasHeight) && (startRes < maxwidth))
645 endx = startRes + cWidth - 1;
652 g.setFont(av.getFont());
653 g.setColor(Color.black);
655 if (av.getScaleLeftWrapped())
657 drawWestScale(g, startRes, endx, ypos);
660 if (av.getScaleRightWrapped())
662 g.translate(canvasWidth - labelWidthEast, 0);
663 drawEastScale(g, startRes, endx, ypos);
664 g.translate(-(canvasWidth - labelWidthEast), 0);
667 g.translate(labelWidthWest, 0);
669 if (av.getScaleAboveWrapped())
671 drawNorthScale(g, startRes, endx, ypos);
674 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
676 g.setColor(Color.blue);
678 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
679 List<Integer> positions = hidden.findHiddenRegionPositions();
680 for (int pos : positions)
682 res = pos - startRes;
684 if (res < 0 || res > endx - startRes)
691 { res * charWidth - charHeight / 4,
692 res * charWidth + charHeight / 4, res * charWidth },
694 { ypos - (charHeight / 2), ypos - (charHeight / 2),
695 ypos - (charHeight / 2) + 8 },
701 // When printing we have an extra clipped region,
702 // the Printable page which we need to account for here
703 Shape clip = g.getClip();
707 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
711 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
712 (int) clip.getBounds().getHeight());
715 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
717 if (av.isShowAnnotation())
719 g.translate(0, cHeight + ypos + 3);
720 if (annotations == null)
722 annotations = new AnnotationPanel(av);
725 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
727 g.translate(0, -cHeight - ypos - 3);
730 g.translate(-labelWidthWest, 0);
732 ypos += cHeight + annotationHeight + hgap;
739 * Draw a selection group over a wrapped alignment
741 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
743 int canvasHeight, int startRes)
745 // height gap above each panel
746 int hgap = charHeight;
747 if (av.getScaleAboveWrapped())
752 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
754 int cHeight = av.getAlignment().getHeight() * charHeight;
756 int startx = startRes;
758 int ypos = hgap; // vertical offset
759 int maxwidth = av.getAlignment().getWidth();
761 if (av.hasHiddenColumns())
763 maxwidth = av.getAlignment().getHiddenColumns()
764 .findColumnPosition(maxwidth);
767 // chop the wrapped alignment extent up into panel-sized blocks and treat
768 // each block as if it were a block from an unwrapped alignment
769 while ((ypos <= canvasHeight) && (startx < maxwidth))
771 // set end value to be start + width, or maxwidth, whichever is smaller
772 endx = startx + cWidth - 1;
779 g.translate(labelWidthWest, 0);
781 drawUnwrappedSelection(g, group, startx, endx, 0,
782 av.getAlignment().getHeight() - 1,
785 g.translate(-labelWidthWest, 0);
787 // update vertical offset
788 ypos += cHeight + getAnnotationHeight() + hgap;
790 // update horizontal offset
795 int getAnnotationHeight()
797 if (!av.isShowAnnotation())
802 if (annotations == null)
804 annotations = new AnnotationPanel(av);
807 return annotations.adjustPanelHeight();
812 * Draws the visible region of the alignment on the graphics context. If there
813 * are hidden column markers in the visible region, then each sub-region
814 * between the markers is drawn separately, followed by the hidden column
818 * Graphics object to draw with
820 * offset of the first column in the visible region (0..)
822 * offset of the last column in the visible region (0..)
824 * offset of the first sequence in the visible region (0..)
826 * offset of the last sequence in the visible region (0..)
828 * vertical offset at which to draw (for wrapped alignments)
830 private void drawPanel(Graphics g1, int startRes, int endRes,
831 int startSeq, int endSeq, int yOffset)
835 if (!av.hasHiddenColumns())
837 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
842 final int screenYMax = endRes - startRes;
843 int blockStart = startRes;
844 int blockEnd = endRes;
846 for (int[] region : av.getAlignment().getHiddenColumns()
847 .getHiddenColumnsCopy())
849 int hideStart = region[0];
850 int hideEnd = region[1];
852 if (hideStart <= blockStart)
854 blockStart += (hideEnd - hideStart) + 1;
859 * draw up to just before the next hidden region, or the end of
860 * the visible region, whichever comes first
862 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
865 g1.translate(screenY * charWidth, 0);
867 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
870 * draw the downline of the hidden column marker (ScalePanel draws the
871 * triangle on top) if we reached it
873 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
875 g1.setColor(Color.blue);
877 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
878 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
879 (endSeq - startSeq + 1) * charHeight + yOffset);
882 g1.translate(-screenY * charWidth, 0);
883 screenY += blockEnd - blockStart + 1;
884 blockStart = hideEnd + 1;
886 if (screenY > screenYMax)
888 // already rendered last block
893 if (screenY <= screenYMax)
895 // remaining visible region to render
896 blockEnd = blockStart + screenYMax - screenY;
897 g1.translate(screenY * charWidth, 0);
898 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
900 g1.translate(-screenY * charWidth, 0);
908 * Draws a region of the visible alignment
912 * offset of the first column in the visible region (0..)
914 * offset of the last column in the visible region (0..)
916 * offset of the first sequence in the visible region (0..)
918 * offset of the last sequence in the visible region (0..)
920 * vertical offset at which to draw (for wrapped alignments)
922 private void draw(Graphics g, int startRes, int endRes, int startSeq,
923 int endSeq, int offset)
925 g.setFont(av.getFont());
926 seqRdr.prepare(g, av.isRenderGaps());
930 // / First draw the sequences
931 // ///////////////////////////
932 for (int i = startSeq; i <= endSeq; i++)
934 nextSeq = av.getAlignment().getSequenceAt(i);
937 // occasionally, a race condition occurs such that the alignment row is
941 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
942 startRes, endRes, offset + ((i - startSeq) * charHeight));
944 if (av.isShowSequenceFeatures())
946 fr.drawSequence(g, nextSeq, startRes, endRes,
947 offset + ((i - startSeq) * charHeight), false);
951 * highlight search Results once sequence has been drawn
953 if (av.hasSearchResults())
955 SearchResultsI searchResults = av.getSearchResults();
956 int[] visibleResults = searchResults.getResults(nextSeq,
958 if (visibleResults != null)
960 for (int r = 0; r < visibleResults.length; r += 2)
962 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
963 visibleResults[r + 1], (visibleResults[r] - startRes)
965 + ((i - startSeq) * charHeight));
970 if (av.cursorMode && cursorY == i && cursorX >= startRes
971 && cursorX <= endRes)
973 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
974 offset + ((i - startSeq) * charHeight));
978 if (av.getSelectionGroup() != null
979 || av.getAlignment().getGroups().size() > 0)
981 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
986 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
987 int startSeq, int endSeq, int offset)
989 Graphics2D g = (Graphics2D) g1;
991 // ///////////////////////////////////
992 // Now outline any areas if necessary
993 // ///////////////////////////////////
995 SequenceGroup group = null;
998 if (av.getAlignment().getGroups().size() > 0)
1000 group = av.getAlignment().getGroups().get(0);
1006 g.setStroke(new BasicStroke());
1007 g.setColor(group.getOutlineColour());
1011 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1016 g.setStroke(new BasicStroke());
1018 if (groupIndex >= av.getAlignment().getGroups().size())
1023 group = av.getAlignment().getGroups().get(groupIndex);
1025 } while (groupIndex < av.getAlignment().getGroups().size());
1033 * Draw the selection group as a separate image and overlay
1035 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1036 int startSeq, int endSeq)
1038 // get a new image of the correct size
1039 BufferedImage selectionImage = setupImage();
1041 if (selectionImage == null)
1046 SequenceGroup group = av.getSelectionGroup();
1053 // set up drawing colour
1054 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1056 setupSelectionGroup(g, selectionImage);
1058 if (!av.getWrapAlignment())
1060 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1065 drawWrappedSelection(g, group, getWidth(), getHeight(),
1066 av.getRanges().getStartRes());
1070 return selectionImage;
1074 * Set up graphics for selection group
1076 private void setupSelectionGroup(Graphics2D g,
1077 BufferedImage selectionImage)
1079 // set background to transparent
1080 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1081 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1083 // set up foreground to draw red dashed line
1084 g.setComposite(AlphaComposite.Src);
1085 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1086 BasicStroke.JOIN_ROUND, 3f, new float[]
1088 g.setColor(Color.RED);
1092 * Draw a selection group over an unwrapped alignment
1093 * @param g graphics object to draw with
1094 * @param group selection group
1095 * @param startRes start residue of area to draw
1096 * @param endRes end residue of area to draw
1097 * @param startSeq start sequence of area to draw
1098 * @param endSeq end sequence of area to draw
1099 * @param offset vertical offset (used when called from wrapped alignment code)
1101 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1102 int startRes, int endRes, int startSeq, int endSeq, int offset)
1104 if (!av.hasHiddenColumns())
1106 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1111 // package into blocks of visible columns
1113 int blockStart = startRes;
1114 int blockEnd = endRes;
1116 for (int[] region : av.getAlignment().getHiddenColumns()
1117 .getHiddenColumnsCopy())
1119 int hideStart = region[0];
1120 int hideEnd = region[1];
1122 if (hideStart <= blockStart)
1124 blockStart += (hideEnd - hideStart) + 1;
1128 blockEnd = hideStart - 1;
1130 g.translate(screenY * charWidth, 0);
1131 drawPartialGroupOutline(g, group,
1132 blockStart, blockEnd, startSeq, endSeq, offset);
1134 g.translate(-screenY * charWidth, 0);
1135 screenY += blockEnd - blockStart + 1;
1136 blockStart = hideEnd + 1;
1138 if (screenY > (endRes - startRes))
1140 // already rendered last block
1145 if (screenY <= (endRes - startRes))
1147 // remaining visible region to render
1148 blockEnd = blockStart + (endRes - startRes) - screenY;
1149 g.translate(screenY * charWidth, 0);
1150 drawPartialGroupOutline(g, group,
1151 blockStart, blockEnd, startSeq, endSeq, offset);
1153 g.translate(-screenY * charWidth, 0);
1159 * Draw the selection group as a separate image and overlay
1161 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1162 int startRes, int endRes, int startSeq, int endSeq,
1165 int visWidth = (endRes - startRes + 1) * charWidth;
1169 boolean inGroup = false;
1177 for (i = startSeq; i <= endSeq; i++)
1179 // position of start residue of group relative to startRes, in pixels
1180 sx = (group.getStartRes() - startRes) * charWidth;
1182 // width of group in pixels
1183 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1186 sy = verticalOffset + (i - startSeq) * charHeight;
1188 if (sx + xwidth < 0 || sx > visWidth)
1193 if ((sx <= (endRes - startRes) * charWidth)
1194 && group.getSequences(null)
1195 .contains(av.getAlignment().getSequenceAt(i)))
1197 if ((bottom == -1) && !group.getSequences(null)
1198 .contains(av.getAlignment().getSequenceAt(i + 1)))
1200 bottom = sy + charHeight;
1205 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1206 .contains(av.getAlignment().getSequenceAt(i - 1)))
1219 // if start position is visible, draw vertical line to left of
1221 if (sx >= 0 && sx < visWidth)
1223 g.drawLine(sx, oldY, sx, sy);
1226 // if end position is visible, draw vertical line to right of
1228 if (sx + xwidth < visWidth)
1230 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1239 // don't let width extend beyond current block, or group extent
1241 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1243 xwidth = (endRes - startRes + 1) * charWidth - sx;
1246 // draw horizontal line at top of group
1249 g.drawLine(sx, top, sx + xwidth, top);
1253 // draw horizontal line at bottom of group
1256 g.drawLine(sx, bottom, sx + xwidth, bottom);
1267 sy = verticalOffset + ((i - startSeq) * charHeight);
1268 if (sx >= 0 && sx < visWidth)
1270 g.drawLine(sx, oldY, sx, sy);
1273 if (sx + xwidth < visWidth)
1275 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1284 if (sx + xwidth > visWidth)
1288 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1290 xwidth = (endRes - startRes + 1) * charWidth;
1295 g.drawLine(sx, top, sx + xwidth, top);
1301 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1310 * Highlights search results in the visible region by rendering as white text
1311 * on a black background. Any previous highlighting is removed. Answers true
1312 * if any highlight was left on the visible alignment (so status bar should be
1313 * set to match), else false.
1315 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1316 * alignment had to be scrolled to show the highlighted region, then it should
1317 * be fully redrawn, otherwise a fast paint can be performed. This argument
1318 * could be removed if fast paint of scrolled wrapped alignment is coded in
1319 * future (JAL-2609).
1322 * @param noFastPaint
1325 public boolean highlightSearchResults(SearchResultsI results,
1326 boolean noFastPaint)
1332 boolean wrapped = av.getWrapAlignment();
1336 fastPaint = !noFastPaint;
1337 fastpainting = fastPaint;
1342 * to avoid redrawing the whole visible region, we instead
1343 * redraw just the minimal regions to remove previous highlights
1346 SearchResultsI previous = av.getSearchResults();
1347 av.setSearchResults(results);
1348 boolean redrawn = false;
1349 boolean drawn = false;
1352 redrawn = drawMappedPositionsWrapped(previous);
1353 drawn = drawMappedPositionsWrapped(results);
1358 redrawn = drawMappedPositions(previous);
1359 drawn = drawMappedPositions(results);
1364 * if highlights were either removed or added, repaint
1372 * return true only if highlights were added
1378 fastpainting = false;
1383 * Redraws the minimal rectangle in the visible region (if any) that includes
1384 * mapped positions of the given search results. Whether or not positions are
1385 * highlighted depends on the SearchResults set on the Viewport. This allows
1386 * this method to be called to either clear or set highlighting. Answers true
1387 * if any positions were drawn (in which case a repaint is still required),
1393 protected boolean drawMappedPositions(SearchResultsI results)
1395 if (results == null)
1401 * calculate the minimal rectangle to redraw that
1402 * includes both new and existing search results
1404 int firstSeq = Integer.MAX_VALUE;
1406 int firstCol = Integer.MAX_VALUE;
1408 boolean matchFound = false;
1410 ViewportRanges ranges = av.getRanges();
1411 int firstVisibleColumn = ranges.getStartRes();
1412 int lastVisibleColumn = ranges.getEndRes();
1413 AlignmentI alignment = av.getAlignment();
1414 if (av.hasHiddenColumns())
1416 firstVisibleColumn = alignment.getHiddenColumns()
1417 .adjustForHiddenColumns(firstVisibleColumn);
1418 lastVisibleColumn = alignment.getHiddenColumns()
1419 .adjustForHiddenColumns(lastVisibleColumn);
1422 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1423 .getEndSeq(); seqNo++)
1425 SequenceI seq = alignment.getSequenceAt(seqNo);
1427 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1429 if (visibleResults != null)
1431 for (int i = 0; i < visibleResults.length - 1; i += 2)
1433 int firstMatchedColumn = visibleResults[i];
1434 int lastMatchedColumn = visibleResults[i + 1];
1435 if (firstMatchedColumn <= lastVisibleColumn
1436 && lastMatchedColumn >= firstVisibleColumn)
1439 * found a search results match in the visible region -
1440 * remember the first and last sequence matched, and the first
1441 * and last visible columns in the matched positions
1444 firstSeq = Math.min(firstSeq, seqNo);
1445 lastSeq = Math.max(lastSeq, seqNo);
1446 firstMatchedColumn = Math.max(firstMatchedColumn,
1447 firstVisibleColumn);
1448 lastMatchedColumn = Math.min(lastMatchedColumn,
1450 firstCol = Math.min(firstCol, firstMatchedColumn);
1451 lastCol = Math.max(lastCol, lastMatchedColumn);
1459 if (av.hasHiddenColumns())
1461 firstCol = alignment.getHiddenColumns()
1462 .findColumnPosition(firstCol);
1463 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1465 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1466 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1467 gg.translate(transX, transY);
1468 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1469 gg.translate(-transX, -transY);
1476 public void propertyChange(PropertyChangeEvent evt)
1478 String eventName = evt.getPropertyName();
1480 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1485 else if (av.getWrapAlignment())
1487 if (eventName.equals(ViewportRanges.STARTRES))
1495 if (eventName.equals(ViewportRanges.STARTRES))
1497 // Make sure we're not trying to draw a panel
1498 // larger than the visible window
1499 ViewportRanges vpRanges = av.getRanges();
1500 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1501 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1502 if (scrollX > range)
1506 else if (scrollX < -range)
1512 // Both scrolling and resizing change viewport ranges: scrolling changes
1513 // both start and end points, but resize only changes end values.
1514 // Here we only want to fastpaint on a scroll, with resize using a normal
1515 // paint, so scroll events are identified as changes to the horizontal or
1516 // vertical start value.
1517 if (eventName.equals(ViewportRanges.STARTRES))
1519 // scroll - startres and endres both change
1520 fastPaint(scrollX, 0);
1522 else if (eventName.equals(ViewportRanges.STARTSEQ))
1525 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1531 * Redraws any positions in the search results in the visible region of a
1532 * wrapped alignment. Any highlights are drawn depending on the search results
1533 * set on the Viewport, not the <code>results</code> argument. This allows
1534 * this method to be called either to clear highlights (passing the previous
1535 * search results), or to draw new highlights.
1540 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1542 if (results == null)
1547 boolean matchFound = false;
1549 int wrappedWidth = av.getWrappedWidth();
1550 int wrappedHeight = getRepeatHeightWrapped();
1552 ViewportRanges ranges = av.getRanges();
1553 int canvasHeight = getHeight();
1554 int repeats = canvasHeight / wrappedHeight;
1555 if (canvasHeight / wrappedHeight > 0)
1560 int firstVisibleColumn = ranges.getStartRes();
1561 int lastVisibleColumn = ranges.getStartRes() + repeats
1562 * ranges.getViewportWidth() - 1;
1564 AlignmentI alignment = av.getAlignment();
1565 if (av.hasHiddenColumns())
1567 firstVisibleColumn = alignment.getHiddenColumns()
1568 .adjustForHiddenColumns(firstVisibleColumn);
1569 lastVisibleColumn = alignment.getHiddenColumns()
1570 .adjustForHiddenColumns(lastVisibleColumn);
1573 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1575 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1576 .getEndSeq(); seqNo++)
1578 SequenceI seq = alignment.getSequenceAt(seqNo);
1580 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1582 if (visibleResults != null)
1584 for (int i = 0; i < visibleResults.length - 1; i += 2)
1586 int firstMatchedColumn = visibleResults[i];
1587 int lastMatchedColumn = visibleResults[i + 1];
1588 if (firstMatchedColumn <= lastVisibleColumn
1589 && lastMatchedColumn >= firstVisibleColumn)
1592 * found a search results match in the visible region
1594 firstMatchedColumn = Math.max(firstMatchedColumn,
1595 firstVisibleColumn);
1596 lastMatchedColumn = Math.min(lastMatchedColumn,
1600 * draw each mapped position separately (as contiguous positions may
1601 * wrap across lines)
1603 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1605 int displayColumn = mappedPos;
1606 if (av.hasHiddenColumns())
1608 displayColumn = alignment.getHiddenColumns()
1609 .findColumnPosition(displayColumn);
1613 * transX: offset from left edge of canvas to residue position
1615 int transX = labelWidthWest
1616 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1617 * av.getCharWidth();
1620 * transY: offset from top edge of canvas to residue position
1622 int transY = gapHeight;
1623 transY += (displayColumn - ranges.getStartRes())
1624 / wrappedWidth * wrappedHeight;
1625 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1628 * yOffset is from graphics origin to start of visible region
1630 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1631 if (transY < getHeight())
1634 gg.translate(transX, transY);
1635 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1637 gg.translate(-transX, -transY);
1649 * Answers the height in pixels of a repeating section of the wrapped
1650 * alignment, including space above, scale above if shown, sequences, and
1651 * annotation panel if shown
1655 protected int getRepeatHeightWrapped()
1657 // gap (and maybe scale) above
1658 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1661 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1663 // add annotations panel height if shown
1664 repeatHeight += getAnnotationHeight();
1666 return repeatHeight;