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.Iterator;
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 || img == 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 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
434 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
436 if (selectImage != null)
438 ((Graphics2D) g1).setComposite(AlphaComposite
439 .getInstance(AlphaComposite.SRC_OVER));
440 g1.drawImage(selectImage, 0, 0, this);
445 * Draw a wrapped alignment panel for printing
448 * Graphics object to draw with
450 * width of drawing area
451 * @param canvasHeight
452 * height of drawing area
454 * start residue of print area
456 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
457 int canvasHeight, int startRes)
459 SequenceGroup group = av.getSelectionGroup();
461 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
465 BufferedImage selectImage = null;
468 selectImage = new BufferedImage(canvasWidth, canvasHeight,
469 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
470 } catch (OutOfMemoryError er)
473 System.err.println("Print image OutOfMemory Error.\n" + er);
474 new OOMWarning("Creating wrapped alignment image for printing", er);
476 if (selectImage != null)
478 Graphics2D g2 = selectImage.createGraphics();
479 setupSelectionGroup(g2, selectImage);
480 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
484 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
485 g.drawImage(selectImage, 0, 0, this);
492 * Make a local image by combining the cached image img
495 private BufferedImage buildLocalImage(BufferedImage selectImage)
497 // clone the cached image
498 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
500 Graphics2D g2d = lcimg.createGraphics();
501 g2d.drawImage(img, 0, 0, null);
503 // overlay selection group on lcimg
504 if (selectImage != null)
507 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
508 g2d.drawImage(selectImage, 0, 0, this);
516 * Set up a buffered image of the correct height and size for the sequence canvas
518 private BufferedImage setupImage()
520 BufferedImage lcimg = null;
522 int width = getWidth();
523 int height = getHeight();
525 width -= (width % charWidth);
526 height -= (height % charHeight);
528 if ((width < 1) || (height < 1))
535 lcimg = new BufferedImage(width, height,
536 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
537 } catch (OutOfMemoryError er)
541 "Group image OutOfMemory Redraw Error.\n" + er);
542 new OOMWarning("Creating alignment image for display", er);
551 * Returns the visible width of the canvas in residues, after allowing for
552 * East or West scales (if shown)
555 * the width in pixels (possibly including scales)
559 public int getWrappedCanvasWidth(int canvasWidth)
561 FontMetrics fm = getFontMetrics(av.getFont());
566 if (av.getScaleRightWrapped())
568 labelWidthEast = getLabelWidth(fm);
571 if (av.getScaleLeftWrapped())
573 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
577 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
581 * Returns a pixel width suitable for showing the largest sequence coordinate
582 * (end position) in the alignment. Returns 2 plus the number of decimal
583 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
588 protected int getLabelWidth(FontMetrics fm)
591 * find the biggest sequence end position we need to show
592 * (note this is not necessarily the sequence length)
595 AlignmentI alignment = av.getAlignment();
596 for (int i = 0; i < alignment.getHeight(); i++)
598 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
602 for (int i = maxWidth; i > 0; i /= 10)
607 return fm.stringWidth(ZEROS.substring(0, length));
617 * @param canvasHeight
622 private void drawWrappedPanel(Graphics g, int canvasWidth,
623 int canvasHeight, int startRes)
626 AlignmentI al = av.getAlignment();
629 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
631 FontMetrics fm = getFontMetrics(av.getFont());
632 labelWidth = getLabelWidth(fm);
635 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
636 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
638 int hgap = charHeight;
639 if (av.getScaleAboveWrapped())
644 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
645 int cHeight = av.getAlignment().getHeight() * charHeight;
647 av.setWrappedWidth(cWidth);
649 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
653 int maxwidth = av.getAlignment().getWidth();
655 if (av.hasHiddenColumns())
657 maxwidth = av.getAlignment().getHiddenColumns()
658 .findColumnPosition(maxwidth);
661 int annotationHeight = getAnnotationHeight();
663 while ((ypos <= canvasHeight) && (startRes < maxwidth))
665 endx = startRes + cWidth - 1;
672 g.setFont(av.getFont());
673 g.setColor(Color.black);
675 if (av.getScaleLeftWrapped())
677 drawWestScale(g, startRes, endx, ypos);
680 if (av.getScaleRightWrapped())
682 g.translate(canvasWidth - labelWidthEast, 0);
683 drawEastScale(g, startRes, endx, ypos);
684 g.translate(-(canvasWidth - labelWidthEast), 0);
687 g.translate(labelWidthWest, 0);
689 if (av.getScaleAboveWrapped())
691 drawNorthScale(g, startRes, endx, ypos);
694 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
696 g.setColor(Color.blue);
698 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
700 Iterator<Integer> it = hidden.getBoundedStartIterator(startRes,
704 res = it.next() - startRes;
707 { res * charWidth - charHeight / 4,
708 res * charWidth + charHeight / 4, res * charWidth },
710 { ypos - (charHeight / 2), ypos - (charHeight / 2),
711 ypos - (charHeight / 2) + 8 }, 3);
715 // When printing we have an extra clipped region,
716 // the Printable page which we need to account for here
717 Shape clip = g.getClip();
721 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
725 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
726 (int) clip.getBounds().getHeight());
729 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
731 if (av.isShowAnnotation())
733 g.translate(0, cHeight + ypos + 3);
734 if (annotations == null)
736 annotations = new AnnotationPanel(av);
739 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
741 g.translate(0, -cHeight - ypos - 3);
744 g.translate(-labelWidthWest, 0);
746 ypos += cHeight + annotationHeight + hgap;
753 * Draw a selection group over a wrapped alignment
755 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
757 int canvasHeight, int startRes)
759 // height gap above each panel
760 int hgap = charHeight;
761 if (av.getScaleAboveWrapped())
766 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
768 int cHeight = av.getAlignment().getHeight() * charHeight;
770 int startx = startRes;
772 int ypos = hgap; // vertical offset
773 int maxwidth = av.getAlignment().getWidth();
775 if (av.hasHiddenColumns())
777 maxwidth = av.getAlignment().getHiddenColumns()
778 .findColumnPosition(maxwidth);
781 // chop the wrapped alignment extent up into panel-sized blocks and treat
782 // each block as if it were a block from an unwrapped alignment
783 while ((ypos <= canvasHeight) && (startx < maxwidth))
785 // set end value to be start + width, or maxwidth, whichever is smaller
786 endx = startx + cWidth - 1;
793 g.translate(labelWidthWest, 0);
795 drawUnwrappedSelection(g, group, startx, endx, 0,
796 av.getAlignment().getHeight() - 1,
799 g.translate(-labelWidthWest, 0);
801 // update vertical offset
802 ypos += cHeight + getAnnotationHeight() + hgap;
804 // update horizontal offset
809 int getAnnotationHeight()
811 if (!av.isShowAnnotation())
816 if (annotations == null)
818 annotations = new AnnotationPanel(av);
821 return annotations.adjustPanelHeight();
825 * Draws the visible region of the alignment on the graphics context. If there
826 * are hidden column markers in the visible region, then each sub-region
827 * between the markers is drawn separately, followed by the hidden column
831 * Graphics object to draw with
833 * offset of the first column in the visible region (0..)
835 * offset of the last column in the visible region (0..)
837 * offset of the first sequence in the visible region (0..)
839 * offset of the last sequence in the visible region (0..)
841 * vertical offset at which to draw (for wrapped alignments)
843 public void drawPanel(Graphics g1, final int startRes, final int endRes,
844 final int startSeq, final int endSeq, final int yOffset)
847 if (!av.hasHiddenColumns())
849 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
854 final int screenYMax = endRes - startRes;
855 int blockStart = startRes;
856 int blockEnd = endRes;
858 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
859 Iterator<int[]> regions = hidden.getBoundedVisRegionIterator(startRes,
861 while (regions.hasNext())
863 int[] region = regions.next();
865 blockStart = region[0];
868 * draw up to just before the next hidden region, or the end of
869 * the visible region, whichever comes first
871 blockEnd = Math.min(region[1], blockStart + screenYMax - screenY);
873 g1.translate(screenY * charWidth, 0);
875 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
878 * draw the downline of the hidden column marker (ScalePanel draws the
879 * triangle on top) if we reached it
881 if (av.getShowHiddenMarkers() && blockEnd == region[1])
883 g1.setColor(Color.blue);
885 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
886 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
887 (endSeq - startSeq + 1) * charHeight + yOffset);
890 g1.translate(-screenY * charWidth, 0);
891 screenY += blockEnd - blockStart + 1;
893 if (screenY > screenYMax)
895 // already rendered last block
900 if (screenY <= screenYMax)
902 // remaining visible region to render
903 blockEnd = blockStart + screenYMax - screenY;
904 g1.translate(screenY * charWidth, 0);
905 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
907 g1.translate(-screenY * charWidth, 0);
914 * Draws a region of the visible alignment
918 * offset of the first column in the visible region (0..)
920 * offset of the last column in the visible region (0..)
922 * offset of the first sequence in the visible region (0..)
924 * offset of the last sequence in the visible region (0..)
926 * vertical offset at which to draw (for wrapped alignments)
928 private void draw(Graphics g, int startRes, int endRes, int startSeq,
929 int endSeq, int offset)
931 g.setFont(av.getFont());
932 seqRdr.prepare(g, av.isRenderGaps());
936 // / First draw the sequences
937 // ///////////////////////////
938 for (int i = startSeq; i <= endSeq; i++)
940 nextSeq = av.getAlignment().getSequenceAt(i);
943 // occasionally, a race condition occurs such that the alignment row is
947 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
948 startRes, endRes, offset + ((i - startSeq) * charHeight));
950 if (av.isShowSequenceFeatures())
952 fr.drawSequence(g, nextSeq, startRes, endRes,
953 offset + ((i - startSeq) * charHeight), false);
957 * highlight search Results once sequence has been drawn
959 if (av.hasSearchResults())
961 SearchResultsI searchResults = av.getSearchResults();
962 int[] visibleResults = searchResults.getResults(nextSeq,
964 if (visibleResults != null)
966 for (int r = 0; r < visibleResults.length; r += 2)
968 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
969 visibleResults[r + 1], (visibleResults[r] - startRes)
971 + ((i - startSeq) * charHeight));
976 if (av.cursorMode && cursorY == i && cursorX >= startRes
977 && cursorX <= endRes)
979 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
980 offset + ((i - startSeq) * charHeight));
984 if (av.getSelectionGroup() != null
985 || av.getAlignment().getGroups().size() > 0)
987 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
992 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
993 int startSeq, int endSeq, int offset)
995 Graphics2D g = (Graphics2D) g1;
997 // ///////////////////////////////////
998 // Now outline any areas if necessary
999 // ///////////////////////////////////
1001 SequenceGroup group = null;
1002 int groupIndex = -1;
1004 if (av.getAlignment().getGroups().size() > 0)
1006 group = av.getAlignment().getGroups().get(0);
1012 g.setStroke(new BasicStroke());
1013 g.setColor(group.getOutlineColour());
1017 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1022 g.setStroke(new BasicStroke());
1024 if (groupIndex >= av.getAlignment().getGroups().size())
1029 group = av.getAlignment().getGroups().get(groupIndex);
1031 } while (groupIndex < av.getAlignment().getGroups().size());
1039 * Draw the selection group as a separate image and overlay
1041 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1042 int startSeq, int endSeq)
1044 // get a new image of the correct size
1045 BufferedImage selectionImage = setupImage();
1047 if (selectionImage == null)
1052 SequenceGroup group = av.getSelectionGroup();
1059 // set up drawing colour
1060 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1062 setupSelectionGroup(g, selectionImage);
1064 if (!av.getWrapAlignment())
1066 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1071 drawWrappedSelection(g, group, getWidth(), getHeight(),
1072 av.getRanges().getStartRes());
1076 return selectionImage;
1080 * Set up graphics for selection group
1082 private void setupSelectionGroup(Graphics2D g,
1083 BufferedImage selectionImage)
1085 // set background to transparent
1086 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1087 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1089 // set up foreground to draw red dashed line
1090 g.setComposite(AlphaComposite.Src);
1091 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1092 BasicStroke.JOIN_ROUND, 3f, new float[]
1094 g.setColor(Color.RED);
1098 * Draw a selection group over an unwrapped alignment
1099 * @param g graphics object to draw with
1100 * @param group selection group
1101 * @param startRes start residue of area to draw
1102 * @param endRes end residue of area to draw
1103 * @param startSeq start sequence of area to draw
1104 * @param endSeq end sequence of area to draw
1105 * @param offset vertical offset (used when called from wrapped alignment code)
1107 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1108 int startRes, int endRes, int startSeq, int endSeq, int offset)
1110 if (!av.hasHiddenColumns())
1112 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1117 // package into blocks of visible columns
1119 int blockStart = startRes;
1120 int blockEnd = endRes;
1122 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1123 Iterator<int[]> regions = hidden.getBoundedVisRegionIterator(startRes,
1125 while (regions.hasNext())
1127 int[] region = regions.next();
1129 blockStart = region[0];
1130 blockEnd = region[1];
1132 g.translate(screenY * charWidth, 0);
1133 drawPartialGroupOutline(g, group,
1134 blockStart, blockEnd, startSeq, endSeq, offset);
1136 g.translate(-screenY * charWidth, 0);
1137 screenY += blockEnd - blockStart + 1;
1139 if (screenY > (endRes - startRes))
1141 // already rendered last block
1146 if (screenY <= (endRes - startRes))
1148 // remaining visible region to render
1149 blockEnd = blockStart + (endRes - startRes) - screenY;
1150 g.translate(screenY * charWidth, 0);
1151 drawPartialGroupOutline(g, group,
1152 blockStart, blockEnd, startSeq, endSeq, offset);
1154 g.translate(-screenY * charWidth, 0);
1160 * Draw the selection group as a separate image and overlay
1162 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1163 int startRes, int endRes, int startSeq, int endSeq,
1166 int visWidth = (endRes - startRes + 1) * charWidth;
1170 boolean inGroup = false;
1178 for (i = startSeq; i <= endSeq; i++)
1180 // position of start residue of group relative to startRes, in pixels
1181 sx = (group.getStartRes() - startRes) * charWidth;
1183 // width of group in pixels
1184 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1187 sy = verticalOffset + (i - startSeq) * charHeight;
1189 if (sx + xwidth < 0 || sx > visWidth)
1194 if ((sx <= (endRes - startRes) * charWidth)
1195 && group.getSequences(null)
1196 .contains(av.getAlignment().getSequenceAt(i)))
1198 if ((bottom == -1) && !group.getSequences(null)
1199 .contains(av.getAlignment().getSequenceAt(i + 1)))
1201 bottom = sy + charHeight;
1206 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1207 .contains(av.getAlignment().getSequenceAt(i - 1)))
1220 // if start position is visible, draw vertical line to left of
1222 if (sx >= 0 && sx < visWidth)
1224 g.drawLine(sx, oldY, sx, sy);
1227 // if end position is visible, draw vertical line to right of
1229 if (sx + xwidth < visWidth)
1231 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1240 // don't let width extend beyond current block, or group extent
1242 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1244 xwidth = (endRes - startRes + 1) * charWidth - sx;
1247 // draw horizontal line at top of group
1250 g.drawLine(sx, top, sx + xwidth, top);
1254 // draw horizontal line at bottom of group
1257 g.drawLine(sx, bottom, sx + xwidth, bottom);
1268 sy = verticalOffset + ((i - startSeq) * charHeight);
1269 if (sx >= 0 && sx < visWidth)
1271 g.drawLine(sx, oldY, sx, sy);
1274 if (sx + xwidth < visWidth)
1276 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1285 if (sx + xwidth > visWidth)
1289 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1291 xwidth = (endRes - startRes + 1) * charWidth;
1296 g.drawLine(sx, top, sx + xwidth, top);
1302 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1311 * Highlights search results in the visible region by rendering as white text
1312 * on a black background. Any previous highlighting is removed. Answers true
1313 * if any highlight was left on the visible alignment (so status bar should be
1314 * set to match), else false.
1316 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1317 * alignment had to be scrolled to show the highlighted region, then it should
1318 * be fully redrawn, otherwise a fast paint can be performed. This argument
1319 * could be removed if fast paint of scrolled wrapped alignment is coded in
1320 * future (JAL-2609).
1323 * @param noFastPaint
1326 public boolean highlightSearchResults(SearchResultsI results,
1327 boolean noFastPaint)
1333 boolean wrapped = av.getWrapAlignment();
1337 fastPaint = !noFastPaint;
1338 fastpainting = fastPaint;
1343 * to avoid redrawing the whole visible region, we instead
1344 * redraw just the minimal regions to remove previous highlights
1347 SearchResultsI previous = av.getSearchResults();
1348 av.setSearchResults(results);
1349 boolean redrawn = false;
1350 boolean drawn = false;
1353 redrawn = drawMappedPositionsWrapped(previous);
1354 drawn = drawMappedPositionsWrapped(results);
1359 redrawn = drawMappedPositions(previous);
1360 drawn = drawMappedPositions(results);
1365 * if highlights were either removed or added, repaint
1373 * return true only if highlights were added
1379 fastpainting = false;
1384 * Redraws the minimal rectangle in the visible region (if any) that includes
1385 * mapped positions of the given search results. Whether or not positions are
1386 * highlighted depends on the SearchResults set on the Viewport. This allows
1387 * this method to be called to either clear or set highlighting. Answers true
1388 * if any positions were drawn (in which case a repaint is still required),
1394 protected boolean drawMappedPositions(SearchResultsI results)
1396 if (results == null)
1402 * calculate the minimal rectangle to redraw that
1403 * includes both new and existing search results
1405 int firstSeq = Integer.MAX_VALUE;
1407 int firstCol = Integer.MAX_VALUE;
1409 boolean matchFound = false;
1411 ViewportRanges ranges = av.getRanges();
1412 int firstVisibleColumn = ranges.getStartRes();
1413 int lastVisibleColumn = ranges.getEndRes();
1414 AlignmentI alignment = av.getAlignment();
1415 if (av.hasHiddenColumns())
1417 firstVisibleColumn = alignment.getHiddenColumns()
1418 .adjustForHiddenColumns(firstVisibleColumn);
1419 lastVisibleColumn = alignment.getHiddenColumns()
1420 .adjustForHiddenColumns(lastVisibleColumn);
1423 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1424 .getEndSeq(); seqNo++)
1426 SequenceI seq = alignment.getSequenceAt(seqNo);
1428 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1430 if (visibleResults != null)
1432 for (int i = 0; i < visibleResults.length - 1; i += 2)
1434 int firstMatchedColumn = visibleResults[i];
1435 int lastMatchedColumn = visibleResults[i + 1];
1436 if (firstMatchedColumn <= lastVisibleColumn
1437 && lastMatchedColumn >= firstVisibleColumn)
1440 * found a search results match in the visible region -
1441 * remember the first and last sequence matched, and the first
1442 * and last visible columns in the matched positions
1445 firstSeq = Math.min(firstSeq, seqNo);
1446 lastSeq = Math.max(lastSeq, seqNo);
1447 firstMatchedColumn = Math.max(firstMatchedColumn,
1448 firstVisibleColumn);
1449 lastMatchedColumn = Math.min(lastMatchedColumn,
1451 firstCol = Math.min(firstCol, firstMatchedColumn);
1452 lastCol = Math.max(lastCol, lastMatchedColumn);
1460 if (av.hasHiddenColumns())
1462 firstCol = alignment.getHiddenColumns()
1463 .findColumnPosition(firstCol);
1464 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1466 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1467 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1468 gg.translate(transX, transY);
1469 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1470 gg.translate(-transX, -transY);
1477 public void propertyChange(PropertyChangeEvent evt)
1479 String eventName = evt.getPropertyName();
1481 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1486 else if (av.getWrapAlignment())
1488 if (eventName.equals(ViewportRanges.STARTRES))
1496 if (eventName.equals(ViewportRanges.STARTRES))
1498 // Make sure we're not trying to draw a panel
1499 // larger than the visible window
1500 ViewportRanges vpRanges = av.getRanges();
1501 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1502 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1503 if (scrollX > range)
1507 else if (scrollX < -range)
1513 // Both scrolling and resizing change viewport ranges: scrolling changes
1514 // both start and end points, but resize only changes end values.
1515 // Here we only want to fastpaint on a scroll, with resize using a normal
1516 // paint, so scroll events are identified as changes to the horizontal or
1517 // vertical start value.
1518 if (eventName.equals(ViewportRanges.STARTRES))
1520 // scroll - startres and endres both change
1521 fastPaint(scrollX, 0);
1523 else if (eventName.equals(ViewportRanges.STARTSEQ))
1526 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1532 * Redraws any positions in the search results in the visible region of a
1533 * wrapped alignment. Any highlights are drawn depending on the search results
1534 * set on the Viewport, not the <code>results</code> argument. This allows
1535 * this method to be called either to clear highlights (passing the previous
1536 * search results), or to draw new highlights.
1541 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1543 if (results == null)
1548 boolean matchFound = false;
1550 int wrappedWidth = av.getWrappedWidth();
1551 int wrappedHeight = getRepeatHeightWrapped();
1553 ViewportRanges ranges = av.getRanges();
1554 int canvasHeight = getHeight();
1555 int repeats = canvasHeight / wrappedHeight;
1556 if (canvasHeight / wrappedHeight > 0)
1561 int firstVisibleColumn = ranges.getStartRes();
1562 int lastVisibleColumn = ranges.getStartRes() + repeats
1563 * ranges.getViewportWidth() - 1;
1565 AlignmentI alignment = av.getAlignment();
1566 if (av.hasHiddenColumns())
1568 firstVisibleColumn = alignment.getHiddenColumns()
1569 .adjustForHiddenColumns(firstVisibleColumn);
1570 lastVisibleColumn = alignment.getHiddenColumns()
1571 .adjustForHiddenColumns(lastVisibleColumn);
1574 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1576 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1577 .getEndSeq(); seqNo++)
1579 SequenceI seq = alignment.getSequenceAt(seqNo);
1581 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1583 if (visibleResults != null)
1585 for (int i = 0; i < visibleResults.length - 1; i += 2)
1587 int firstMatchedColumn = visibleResults[i];
1588 int lastMatchedColumn = visibleResults[i + 1];
1589 if (firstMatchedColumn <= lastVisibleColumn
1590 && lastMatchedColumn >= firstVisibleColumn)
1593 * found a search results match in the visible region
1595 firstMatchedColumn = Math.max(firstMatchedColumn,
1596 firstVisibleColumn);
1597 lastMatchedColumn = Math.min(lastMatchedColumn,
1601 * draw each mapped position separately (as contiguous positions may
1602 * wrap across lines)
1604 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1606 int displayColumn = mappedPos;
1607 if (av.hasHiddenColumns())
1609 displayColumn = alignment.getHiddenColumns()
1610 .findColumnPosition(displayColumn);
1614 * transX: offset from left edge of canvas to residue position
1616 int transX = labelWidthWest
1617 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1618 * av.getCharWidth();
1621 * transY: offset from top edge of canvas to residue position
1623 int transY = gapHeight;
1624 transY += (displayColumn - ranges.getStartRes())
1625 / wrappedWidth * wrappedHeight;
1626 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1629 * yOffset is from graphics origin to start of visible region
1631 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1632 if (transY < getHeight())
1635 gg.translate(transX, transY);
1636 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1638 gg.translate(-transX, -transY);
1650 * Answers the height in pixels of a repeating section of the wrapped
1651 * alignment, including space above, scale above if shown, sequences, and
1652 * annotation panel if shown
1656 protected int getRepeatHeightWrapped()
1658 // gap (and maybe scale) above
1659 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1662 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1664 // add annotations panel height if shown
1665 repeatHeight += getAnnotationHeight();
1667 return repeatHeight;