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.iterator();
860 while (regions.hasNext())
862 int[] region = regions.next();
863 int hideStart = region[0];
864 int hideEnd = region[1];
866 if (hideStart <= blockStart)
868 blockStart += (hideEnd - hideStart) + 1;
873 * draw up to just before the next hidden region, or the end of
874 * the visible region, whichever comes first
876 blockEnd = Math.min(hideStart - 1,
877 blockStart + screenYMax - screenY);
878 g1.translate(screenY * charWidth, 0);
880 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
883 * draw the downline of the hidden column marker (ScalePanel draws the
884 * triangle on top) if we reached it
886 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
888 g1.setColor(Color.blue);
890 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
891 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
892 (endSeq - startSeq + 1) * charHeight + yOffset);
895 g1.translate(-screenY * charWidth, 0);
896 screenY += blockEnd - blockStart + 1;
897 blockStart = hideEnd + 1;
899 if (screenY > screenYMax)
901 // already rendered last block
906 if (screenY <= screenYMax)
908 // remaining visible region to render
909 blockEnd = blockStart + screenYMax - screenY;
910 g1.translate(screenY * charWidth, 0);
911 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
913 g1.translate(-screenY * charWidth, 0);
920 * Draws a region of the visible alignment
924 * offset of the first column in the visible region (0..)
926 * offset of the last column in the visible region (0..)
928 * offset of the first sequence in the visible region (0..)
930 * offset of the last sequence in the visible region (0..)
932 * vertical offset at which to draw (for wrapped alignments)
934 private void draw(Graphics g, int startRes, int endRes, int startSeq,
935 int endSeq, int offset)
937 g.setFont(av.getFont());
938 seqRdr.prepare(g, av.isRenderGaps());
942 // / First draw the sequences
943 // ///////////////////////////
944 for (int i = startSeq; i <= endSeq; i++)
946 nextSeq = av.getAlignment().getSequenceAt(i);
949 // occasionally, a race condition occurs such that the alignment row is
953 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
954 startRes, endRes, offset + ((i - startSeq) * charHeight));
956 if (av.isShowSequenceFeatures())
958 fr.drawSequence(g, nextSeq, startRes, endRes,
959 offset + ((i - startSeq) * charHeight), false);
963 * highlight search Results once sequence has been drawn
965 if (av.hasSearchResults())
967 SearchResultsI searchResults = av.getSearchResults();
968 int[] visibleResults = searchResults.getResults(nextSeq,
970 if (visibleResults != null)
972 for (int r = 0; r < visibleResults.length; r += 2)
974 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
975 visibleResults[r + 1], (visibleResults[r] - startRes)
977 + ((i - startSeq) * charHeight));
982 if (av.cursorMode && cursorY == i && cursorX >= startRes
983 && cursorX <= endRes)
985 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
986 offset + ((i - startSeq) * charHeight));
990 if (av.getSelectionGroup() != null
991 || av.getAlignment().getGroups().size() > 0)
993 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
998 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
999 int startSeq, int endSeq, int offset)
1001 Graphics2D g = (Graphics2D) g1;
1003 // ///////////////////////////////////
1004 // Now outline any areas if necessary
1005 // ///////////////////////////////////
1007 SequenceGroup group = null;
1008 int groupIndex = -1;
1010 if (av.getAlignment().getGroups().size() > 0)
1012 group = av.getAlignment().getGroups().get(0);
1018 g.setStroke(new BasicStroke());
1019 g.setColor(group.getOutlineColour());
1023 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1028 g.setStroke(new BasicStroke());
1030 if (groupIndex >= av.getAlignment().getGroups().size())
1035 group = av.getAlignment().getGroups().get(groupIndex);
1037 } while (groupIndex < av.getAlignment().getGroups().size());
1045 * Draw the selection group as a separate image and overlay
1047 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1048 int startSeq, int endSeq)
1050 // get a new image of the correct size
1051 BufferedImage selectionImage = setupImage();
1053 if (selectionImage == null)
1058 SequenceGroup group = av.getSelectionGroup();
1065 // set up drawing colour
1066 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1068 setupSelectionGroup(g, selectionImage);
1070 if (!av.getWrapAlignment())
1072 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1077 drawWrappedSelection(g, group, getWidth(), getHeight(),
1078 av.getRanges().getStartRes());
1082 return selectionImage;
1086 * Set up graphics for selection group
1088 private void setupSelectionGroup(Graphics2D g,
1089 BufferedImage selectionImage)
1091 // set background to transparent
1092 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1093 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1095 // set up foreground to draw red dashed line
1096 g.setComposite(AlphaComposite.Src);
1097 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1098 BasicStroke.JOIN_ROUND, 3f, new float[]
1100 g.setColor(Color.RED);
1104 * Draw a selection group over an unwrapped alignment
1105 * @param g graphics object to draw with
1106 * @param group selection group
1107 * @param startRes start residue of area to draw
1108 * @param endRes end residue of area to draw
1109 * @param startSeq start sequence of area to draw
1110 * @param endSeq end sequence of area to draw
1111 * @param offset vertical offset (used when called from wrapped alignment code)
1113 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1114 int startRes, int endRes, int startSeq, int endSeq, int offset)
1116 if (!av.hasHiddenColumns())
1118 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1123 // package into blocks of visible columns
1125 int blockStart = startRes;
1126 int blockEnd = endRes;
1128 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1129 Iterator<int[]> regions = hidden.iterator();
1130 while (regions.hasNext())
1132 int[] region = regions.next();
1133 int hideStart = region[0];
1134 int hideEnd = region[1];
1136 if (hideStart <= blockStart)
1138 blockStart += (hideEnd - hideStart) + 1;
1142 blockEnd = hideStart - 1;
1144 g.translate(screenY * charWidth, 0);
1145 drawPartialGroupOutline(g, group,
1146 blockStart, blockEnd, startSeq, endSeq, offset);
1148 g.translate(-screenY * charWidth, 0);
1149 screenY += blockEnd - blockStart + 1;
1150 blockStart = hideEnd + 1;
1152 if (screenY > (endRes - startRes))
1154 // already rendered last block
1159 if (screenY <= (endRes - startRes))
1161 // remaining visible region to render
1162 blockEnd = blockStart + (endRes - startRes) - screenY;
1163 g.translate(screenY * charWidth, 0);
1164 drawPartialGroupOutline(g, group,
1165 blockStart, blockEnd, startSeq, endSeq, offset);
1167 g.translate(-screenY * charWidth, 0);
1173 * Draw the selection group as a separate image and overlay
1175 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1176 int startRes, int endRes, int startSeq, int endSeq,
1179 int visWidth = (endRes - startRes + 1) * charWidth;
1183 boolean inGroup = false;
1191 for (i = startSeq; i <= endSeq; i++)
1193 // position of start residue of group relative to startRes, in pixels
1194 sx = (group.getStartRes() - startRes) * charWidth;
1196 // width of group in pixels
1197 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1200 sy = verticalOffset + (i - startSeq) * charHeight;
1202 if (sx + xwidth < 0 || sx > visWidth)
1207 if ((sx <= (endRes - startRes) * charWidth)
1208 && group.getSequences(null)
1209 .contains(av.getAlignment().getSequenceAt(i)))
1211 if ((bottom == -1) && !group.getSequences(null)
1212 .contains(av.getAlignment().getSequenceAt(i + 1)))
1214 bottom = sy + charHeight;
1219 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1220 .contains(av.getAlignment().getSequenceAt(i - 1)))
1233 // if start position is visible, draw vertical line to left of
1235 if (sx >= 0 && sx < visWidth)
1237 g.drawLine(sx, oldY, sx, sy);
1240 // if end position is visible, draw vertical line to right of
1242 if (sx + xwidth < visWidth)
1244 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1253 // don't let width extend beyond current block, or group extent
1255 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1257 xwidth = (endRes - startRes + 1) * charWidth - sx;
1260 // draw horizontal line at top of group
1263 g.drawLine(sx, top, sx + xwidth, top);
1267 // draw horizontal line at bottom of group
1270 g.drawLine(sx, bottom, sx + xwidth, bottom);
1281 sy = verticalOffset + ((i - startSeq) * charHeight);
1282 if (sx >= 0 && sx < visWidth)
1284 g.drawLine(sx, oldY, sx, sy);
1287 if (sx + xwidth < visWidth)
1289 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1298 if (sx + xwidth > visWidth)
1302 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1304 xwidth = (endRes - startRes + 1) * charWidth;
1309 g.drawLine(sx, top, sx + xwidth, top);
1315 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1324 * Highlights search results in the visible region by rendering as white text
1325 * on a black background. Any previous highlighting is removed. Answers true
1326 * if any highlight was left on the visible alignment (so status bar should be
1327 * set to match), else false.
1329 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1330 * alignment had to be scrolled to show the highlighted region, then it should
1331 * be fully redrawn, otherwise a fast paint can be performed. This argument
1332 * could be removed if fast paint of scrolled wrapped alignment is coded in
1333 * future (JAL-2609).
1336 * @param noFastPaint
1339 public boolean highlightSearchResults(SearchResultsI results,
1340 boolean noFastPaint)
1346 boolean wrapped = av.getWrapAlignment();
1350 fastPaint = !noFastPaint;
1351 fastpainting = fastPaint;
1356 * to avoid redrawing the whole visible region, we instead
1357 * redraw just the minimal regions to remove previous highlights
1360 SearchResultsI previous = av.getSearchResults();
1361 av.setSearchResults(results);
1362 boolean redrawn = false;
1363 boolean drawn = false;
1366 redrawn = drawMappedPositionsWrapped(previous);
1367 drawn = drawMappedPositionsWrapped(results);
1372 redrawn = drawMappedPositions(previous);
1373 drawn = drawMappedPositions(results);
1378 * if highlights were either removed or added, repaint
1386 * return true only if highlights were added
1392 fastpainting = false;
1397 * Redraws the minimal rectangle in the visible region (if any) that includes
1398 * mapped positions of the given search results. Whether or not positions are
1399 * highlighted depends on the SearchResults set on the Viewport. This allows
1400 * this method to be called to either clear or set highlighting. Answers true
1401 * if any positions were drawn (in which case a repaint is still required),
1407 protected boolean drawMappedPositions(SearchResultsI results)
1409 if (results == null)
1415 * calculate the minimal rectangle to redraw that
1416 * includes both new and existing search results
1418 int firstSeq = Integer.MAX_VALUE;
1420 int firstCol = Integer.MAX_VALUE;
1422 boolean matchFound = false;
1424 ViewportRanges ranges = av.getRanges();
1425 int firstVisibleColumn = ranges.getStartRes();
1426 int lastVisibleColumn = ranges.getEndRes();
1427 AlignmentI alignment = av.getAlignment();
1428 if (av.hasHiddenColumns())
1430 firstVisibleColumn = alignment.getHiddenColumns()
1431 .adjustForHiddenColumns(firstVisibleColumn);
1432 lastVisibleColumn = alignment.getHiddenColumns()
1433 .adjustForHiddenColumns(lastVisibleColumn);
1436 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1437 .getEndSeq(); seqNo++)
1439 SequenceI seq = alignment.getSequenceAt(seqNo);
1441 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1443 if (visibleResults != null)
1445 for (int i = 0; i < visibleResults.length - 1; i += 2)
1447 int firstMatchedColumn = visibleResults[i];
1448 int lastMatchedColumn = visibleResults[i + 1];
1449 if (firstMatchedColumn <= lastVisibleColumn
1450 && lastMatchedColumn >= firstVisibleColumn)
1453 * found a search results match in the visible region -
1454 * remember the first and last sequence matched, and the first
1455 * and last visible columns in the matched positions
1458 firstSeq = Math.min(firstSeq, seqNo);
1459 lastSeq = Math.max(lastSeq, seqNo);
1460 firstMatchedColumn = Math.max(firstMatchedColumn,
1461 firstVisibleColumn);
1462 lastMatchedColumn = Math.min(lastMatchedColumn,
1464 firstCol = Math.min(firstCol, firstMatchedColumn);
1465 lastCol = Math.max(lastCol, lastMatchedColumn);
1473 if (av.hasHiddenColumns())
1475 firstCol = alignment.getHiddenColumns()
1476 .findColumnPosition(firstCol);
1477 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1479 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1480 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1481 gg.translate(transX, transY);
1482 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1483 gg.translate(-transX, -transY);
1490 public void propertyChange(PropertyChangeEvent evt)
1492 String eventName = evt.getPropertyName();
1494 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1499 else if (av.getWrapAlignment())
1501 if (eventName.equals(ViewportRanges.STARTRES))
1509 if (eventName.equals(ViewportRanges.STARTRES))
1511 // Make sure we're not trying to draw a panel
1512 // larger than the visible window
1513 ViewportRanges vpRanges = av.getRanges();
1514 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1515 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1516 if (scrollX > range)
1520 else if (scrollX < -range)
1526 // Both scrolling and resizing change viewport ranges: scrolling changes
1527 // both start and end points, but resize only changes end values.
1528 // Here we only want to fastpaint on a scroll, with resize using a normal
1529 // paint, so scroll events are identified as changes to the horizontal or
1530 // vertical start value.
1531 if (eventName.equals(ViewportRanges.STARTRES))
1533 // scroll - startres and endres both change
1534 fastPaint(scrollX, 0);
1536 else if (eventName.equals(ViewportRanges.STARTSEQ))
1539 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1545 * Redraws any positions in the search results in the visible region of a
1546 * wrapped alignment. Any highlights are drawn depending on the search results
1547 * set on the Viewport, not the <code>results</code> argument. This allows
1548 * this method to be called either to clear highlights (passing the previous
1549 * search results), or to draw new highlights.
1554 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1556 if (results == null)
1561 boolean matchFound = false;
1563 int wrappedWidth = av.getWrappedWidth();
1564 int wrappedHeight = getRepeatHeightWrapped();
1566 ViewportRanges ranges = av.getRanges();
1567 int canvasHeight = getHeight();
1568 int repeats = canvasHeight / wrappedHeight;
1569 if (canvasHeight / wrappedHeight > 0)
1574 int firstVisibleColumn = ranges.getStartRes();
1575 int lastVisibleColumn = ranges.getStartRes() + repeats
1576 * ranges.getViewportWidth() - 1;
1578 AlignmentI alignment = av.getAlignment();
1579 if (av.hasHiddenColumns())
1581 firstVisibleColumn = alignment.getHiddenColumns()
1582 .adjustForHiddenColumns(firstVisibleColumn);
1583 lastVisibleColumn = alignment.getHiddenColumns()
1584 .adjustForHiddenColumns(lastVisibleColumn);
1587 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1589 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1590 .getEndSeq(); seqNo++)
1592 SequenceI seq = alignment.getSequenceAt(seqNo);
1594 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1596 if (visibleResults != null)
1598 for (int i = 0; i < visibleResults.length - 1; i += 2)
1600 int firstMatchedColumn = visibleResults[i];
1601 int lastMatchedColumn = visibleResults[i + 1];
1602 if (firstMatchedColumn <= lastVisibleColumn
1603 && lastMatchedColumn >= firstVisibleColumn)
1606 * found a search results match in the visible region
1608 firstMatchedColumn = Math.max(firstMatchedColumn,
1609 firstVisibleColumn);
1610 lastMatchedColumn = Math.min(lastMatchedColumn,
1614 * draw each mapped position separately (as contiguous positions may
1615 * wrap across lines)
1617 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1619 int displayColumn = mappedPos;
1620 if (av.hasHiddenColumns())
1622 displayColumn = alignment.getHiddenColumns()
1623 .findColumnPosition(displayColumn);
1627 * transX: offset from left edge of canvas to residue position
1629 int transX = labelWidthWest
1630 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1631 * av.getCharWidth();
1634 * transY: offset from top edge of canvas to residue position
1636 int transY = gapHeight;
1637 transY += (displayColumn - ranges.getStartRes())
1638 / wrappedWidth * wrappedHeight;
1639 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1642 * yOffset is from graphics origin to start of visible region
1644 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1645 if (transY < getHeight())
1648 gg.translate(transX, transY);
1649 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1651 gg.translate(-transX, -transY);
1663 * Answers the height in pixels of a repeating section of the wrapped
1664 * alignment, including space above, scale above if shown, sequences, and
1665 * annotation panel if shown
1669 protected int getRepeatHeightWrapped()
1671 // gap (and maybe scale) above
1672 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1675 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1677 // add annotations panel height if shown
1678 repeatHeight += getAnnotationHeight();
1680 return repeatHeight;