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 for (int[] region : av.getAlignment().getHiddenColumns()
859 .getHiddenColumnsCopy())
861 int hideStart = region[0];
862 int hideEnd = region[1];
864 if (hideStart <= blockStart)
866 blockStart += (hideEnd - hideStart) + 1;
871 * draw up to just before the next hidden region, or the end of
872 * the visible region, whichever comes first
874 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
877 g1.translate(screenY * charWidth, 0);
879 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
882 * draw the downline of the hidden column marker (ScalePanel draws the
883 * triangle on top) if we reached it
885 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
887 g1.setColor(Color.blue);
889 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
890 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
891 (endSeq - startSeq + 1) * charHeight + yOffset);
894 g1.translate(-screenY * charWidth, 0);
895 screenY += blockEnd - blockStart + 1;
896 blockStart = hideEnd + 1;
898 if (screenY > screenYMax)
900 // already rendered last block
905 if (screenY <= screenYMax)
907 // remaining visible region to render
908 blockEnd = blockStart + screenYMax - screenY;
909 g1.translate(screenY * charWidth, 0);
910 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
912 g1.translate(-screenY * charWidth, 0);
919 * Draws a region of the visible alignment
923 * offset of the first column in the visible region (0..)
925 * offset of the last column in the visible region (0..)
927 * offset of the first sequence in the visible region (0..)
929 * offset of the last sequence in the visible region (0..)
931 * vertical offset at which to draw (for wrapped alignments)
933 private void draw(Graphics g, int startRes, int endRes, int startSeq,
934 int endSeq, int offset)
936 g.setFont(av.getFont());
937 seqRdr.prepare(g, av.isRenderGaps());
941 // / First draw the sequences
942 // ///////////////////////////
943 for (int i = startSeq; i <= endSeq; i++)
945 nextSeq = av.getAlignment().getSequenceAt(i);
948 // occasionally, a race condition occurs such that the alignment row is
952 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
953 startRes, endRes, offset + ((i - startSeq) * charHeight));
955 if (av.isShowSequenceFeatures())
957 fr.drawSequence(g, nextSeq, startRes, endRes,
958 offset + ((i - startSeq) * charHeight), false);
962 * highlight search Results once sequence has been drawn
964 if (av.hasSearchResults())
966 SearchResultsI searchResults = av.getSearchResults();
967 int[] visibleResults = searchResults.getResults(nextSeq,
969 if (visibleResults != null)
971 for (int r = 0; r < visibleResults.length; r += 2)
973 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
974 visibleResults[r + 1], (visibleResults[r] - startRes)
976 + ((i - startSeq) * charHeight));
981 if (av.cursorMode && cursorY == i && cursorX >= startRes
982 && cursorX <= endRes)
984 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
985 offset + ((i - startSeq) * charHeight));
989 if (av.getSelectionGroup() != null
990 || av.getAlignment().getGroups().size() > 0)
992 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
997 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
998 int startSeq, int endSeq, int offset)
1000 Graphics2D g = (Graphics2D) g1;
1002 // ///////////////////////////////////
1003 // Now outline any areas if necessary
1004 // ///////////////////////////////////
1006 SequenceGroup group = null;
1007 int groupIndex = -1;
1009 if (av.getAlignment().getGroups().size() > 0)
1011 group = av.getAlignment().getGroups().get(0);
1017 g.setStroke(new BasicStroke());
1018 g.setColor(group.getOutlineColour());
1022 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1027 g.setStroke(new BasicStroke());
1029 if (groupIndex >= av.getAlignment().getGroups().size())
1034 group = av.getAlignment().getGroups().get(groupIndex);
1036 } while (groupIndex < av.getAlignment().getGroups().size());
1044 * Draw the selection group as a separate image and overlay
1046 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1047 int startSeq, int endSeq)
1049 // get a new image of the correct size
1050 BufferedImage selectionImage = setupImage();
1052 if (selectionImage == null)
1057 SequenceGroup group = av.getSelectionGroup();
1064 // set up drawing colour
1065 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1067 setupSelectionGroup(g, selectionImage);
1069 if (!av.getWrapAlignment())
1071 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1076 drawWrappedSelection(g, group, getWidth(), getHeight(),
1077 av.getRanges().getStartRes());
1081 return selectionImage;
1085 * Set up graphics for selection group
1087 private void setupSelectionGroup(Graphics2D g,
1088 BufferedImage selectionImage)
1090 // set background to transparent
1091 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1092 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1094 // set up foreground to draw red dashed line
1095 g.setComposite(AlphaComposite.Src);
1096 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1097 BasicStroke.JOIN_ROUND, 3f, new float[]
1099 g.setColor(Color.RED);
1103 * Draw a selection group over an unwrapped alignment
1104 * @param g graphics object to draw with
1105 * @param group selection group
1106 * @param startRes start residue of area to draw
1107 * @param endRes end residue of area to draw
1108 * @param startSeq start sequence of area to draw
1109 * @param endSeq end sequence of area to draw
1110 * @param offset vertical offset (used when called from wrapped alignment code)
1112 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1113 int startRes, int endRes, int startSeq, int endSeq, int offset)
1115 if (!av.hasHiddenColumns())
1117 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1122 // package into blocks of visible columns
1124 int blockStart = startRes;
1125 int blockEnd = endRes;
1127 for (int[] region : av.getAlignment().getHiddenColumns()
1128 .getHiddenColumnsCopy())
1130 int hideStart = region[0];
1131 int hideEnd = region[1];
1133 if (hideStart <= blockStart)
1135 blockStart += (hideEnd - hideStart) + 1;
1139 blockEnd = hideStart - 1;
1141 g.translate(screenY * charWidth, 0);
1142 drawPartialGroupOutline(g, group,
1143 blockStart, blockEnd, startSeq, endSeq, offset);
1145 g.translate(-screenY * charWidth, 0);
1146 screenY += blockEnd - blockStart + 1;
1147 blockStart = hideEnd + 1;
1149 if (screenY > (endRes - startRes))
1151 // already rendered last block
1156 if (screenY <= (endRes - startRes))
1158 // remaining visible region to render
1159 blockEnd = blockStart + (endRes - startRes) - screenY;
1160 g.translate(screenY * charWidth, 0);
1161 drawPartialGroupOutline(g, group,
1162 blockStart, blockEnd, startSeq, endSeq, offset);
1164 g.translate(-screenY * charWidth, 0);
1170 * Draw the selection group as a separate image and overlay
1172 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1173 int startRes, int endRes, int startSeq, int endSeq,
1176 int visWidth = (endRes - startRes + 1) * charWidth;
1180 boolean inGroup = false;
1188 for (i = startSeq; i <= endSeq; i++)
1190 // position of start residue of group relative to startRes, in pixels
1191 sx = (group.getStartRes() - startRes) * charWidth;
1193 // width of group in pixels
1194 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1197 sy = verticalOffset + (i - startSeq) * charHeight;
1199 if (sx + xwidth < 0 || sx > visWidth)
1204 if ((sx <= (endRes - startRes) * charWidth)
1205 && group.getSequences(null)
1206 .contains(av.getAlignment().getSequenceAt(i)))
1208 if ((bottom == -1) && !group.getSequences(null)
1209 .contains(av.getAlignment().getSequenceAt(i + 1)))
1211 bottom = sy + charHeight;
1216 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1217 .contains(av.getAlignment().getSequenceAt(i - 1)))
1230 // if start position is visible, draw vertical line to left of
1232 if (sx >= 0 && sx < visWidth)
1234 g.drawLine(sx, oldY, sx, sy);
1237 // if end position is visible, draw vertical line to right of
1239 if (sx + xwidth < visWidth)
1241 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1250 // don't let width extend beyond current block, or group extent
1252 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1254 xwidth = (endRes - startRes + 1) * charWidth - sx;
1257 // draw horizontal line at top of group
1260 g.drawLine(sx, top, sx + xwidth, top);
1264 // draw horizontal line at bottom of group
1267 g.drawLine(sx, bottom, sx + xwidth, bottom);
1278 sy = verticalOffset + ((i - startSeq) * charHeight);
1279 if (sx >= 0 && sx < visWidth)
1281 g.drawLine(sx, oldY, sx, sy);
1284 if (sx + xwidth < visWidth)
1286 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1295 if (sx + xwidth > visWidth)
1299 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1301 xwidth = (endRes - startRes + 1) * charWidth;
1306 g.drawLine(sx, top, sx + xwidth, top);
1312 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1321 * Highlights search results in the visible region by rendering as white text
1322 * on a black background. Any previous highlighting is removed. Answers true
1323 * if any highlight was left on the visible alignment (so status bar should be
1324 * set to match), else false.
1326 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1327 * alignment had to be scrolled to show the highlighted region, then it should
1328 * be fully redrawn, otherwise a fast paint can be performed. This argument
1329 * could be removed if fast paint of scrolled wrapped alignment is coded in
1330 * future (JAL-2609).
1333 * @param noFastPaint
1336 public boolean highlightSearchResults(SearchResultsI results,
1337 boolean noFastPaint)
1343 boolean wrapped = av.getWrapAlignment();
1347 fastPaint = !noFastPaint;
1348 fastpainting = fastPaint;
1353 * to avoid redrawing the whole visible region, we instead
1354 * redraw just the minimal regions to remove previous highlights
1357 SearchResultsI previous = av.getSearchResults();
1358 av.setSearchResults(results);
1359 boolean redrawn = false;
1360 boolean drawn = false;
1363 redrawn = drawMappedPositionsWrapped(previous);
1364 drawn = drawMappedPositionsWrapped(results);
1369 redrawn = drawMappedPositions(previous);
1370 drawn = drawMappedPositions(results);
1375 * if highlights were either removed or added, repaint
1383 * return true only if highlights were added
1389 fastpainting = false;
1394 * Redraws the minimal rectangle in the visible region (if any) that includes
1395 * mapped positions of the given search results. Whether or not positions are
1396 * highlighted depends on the SearchResults set on the Viewport. This allows
1397 * this method to be called to either clear or set highlighting. Answers true
1398 * if any positions were drawn (in which case a repaint is still required),
1404 protected boolean drawMappedPositions(SearchResultsI results)
1406 if (results == null)
1412 * calculate the minimal rectangle to redraw that
1413 * includes both new and existing search results
1415 int firstSeq = Integer.MAX_VALUE;
1417 int firstCol = Integer.MAX_VALUE;
1419 boolean matchFound = false;
1421 ViewportRanges ranges = av.getRanges();
1422 int firstVisibleColumn = ranges.getStartRes();
1423 int lastVisibleColumn = ranges.getEndRes();
1424 AlignmentI alignment = av.getAlignment();
1425 if (av.hasHiddenColumns())
1427 firstVisibleColumn = alignment.getHiddenColumns()
1428 .adjustForHiddenColumns(firstVisibleColumn);
1429 lastVisibleColumn = alignment.getHiddenColumns()
1430 .adjustForHiddenColumns(lastVisibleColumn);
1433 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1434 .getEndSeq(); seqNo++)
1436 SequenceI seq = alignment.getSequenceAt(seqNo);
1438 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1440 if (visibleResults != null)
1442 for (int i = 0; i < visibleResults.length - 1; i += 2)
1444 int firstMatchedColumn = visibleResults[i];
1445 int lastMatchedColumn = visibleResults[i + 1];
1446 if (firstMatchedColumn <= lastVisibleColumn
1447 && lastMatchedColumn >= firstVisibleColumn)
1450 * found a search results match in the visible region -
1451 * remember the first and last sequence matched, and the first
1452 * and last visible columns in the matched positions
1455 firstSeq = Math.min(firstSeq, seqNo);
1456 lastSeq = Math.max(lastSeq, seqNo);
1457 firstMatchedColumn = Math.max(firstMatchedColumn,
1458 firstVisibleColumn);
1459 lastMatchedColumn = Math.min(lastMatchedColumn,
1461 firstCol = Math.min(firstCol, firstMatchedColumn);
1462 lastCol = Math.max(lastCol, lastMatchedColumn);
1470 if (av.hasHiddenColumns())
1472 firstCol = alignment.getHiddenColumns()
1473 .findColumnPosition(firstCol);
1474 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1476 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1477 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1478 gg.translate(transX, transY);
1479 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1480 gg.translate(-transX, -transY);
1487 public void propertyChange(PropertyChangeEvent evt)
1489 String eventName = evt.getPropertyName();
1491 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1496 else if (av.getWrapAlignment())
1498 if (eventName.equals(ViewportRanges.STARTRES))
1506 if (eventName.equals(ViewportRanges.STARTRES))
1508 // Make sure we're not trying to draw a panel
1509 // larger than the visible window
1510 ViewportRanges vpRanges = av.getRanges();
1511 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1512 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1513 if (scrollX > range)
1517 else if (scrollX < -range)
1523 // Both scrolling and resizing change viewport ranges: scrolling changes
1524 // both start and end points, but resize only changes end values.
1525 // Here we only want to fastpaint on a scroll, with resize using a normal
1526 // paint, so scroll events are identified as changes to the horizontal or
1527 // vertical start value.
1528 if (eventName.equals(ViewportRanges.STARTRES))
1530 // scroll - startres and endres both change
1531 fastPaint(scrollX, 0);
1533 else if (eventName.equals(ViewportRanges.STARTSEQ))
1536 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1542 * Redraws any positions in the search results in the visible region of a
1543 * wrapped alignment. Any highlights are drawn depending on the search results
1544 * set on the Viewport, not the <code>results</code> argument. This allows
1545 * this method to be called either to clear highlights (passing the previous
1546 * search results), or to draw new highlights.
1551 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1553 if (results == null)
1558 boolean matchFound = false;
1560 int wrappedWidth = av.getWrappedWidth();
1561 int wrappedHeight = getRepeatHeightWrapped();
1563 ViewportRanges ranges = av.getRanges();
1564 int canvasHeight = getHeight();
1565 int repeats = canvasHeight / wrappedHeight;
1566 if (canvasHeight / wrappedHeight > 0)
1571 int firstVisibleColumn = ranges.getStartRes();
1572 int lastVisibleColumn = ranges.getStartRes() + repeats
1573 * ranges.getViewportWidth() - 1;
1575 AlignmentI alignment = av.getAlignment();
1576 if (av.hasHiddenColumns())
1578 firstVisibleColumn = alignment.getHiddenColumns()
1579 .adjustForHiddenColumns(firstVisibleColumn);
1580 lastVisibleColumn = alignment.getHiddenColumns()
1581 .adjustForHiddenColumns(lastVisibleColumn);
1584 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1586 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1587 .getEndSeq(); seqNo++)
1589 SequenceI seq = alignment.getSequenceAt(seqNo);
1591 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1593 if (visibleResults != null)
1595 for (int i = 0; i < visibleResults.length - 1; i += 2)
1597 int firstMatchedColumn = visibleResults[i];
1598 int lastMatchedColumn = visibleResults[i + 1];
1599 if (firstMatchedColumn <= lastVisibleColumn
1600 && lastMatchedColumn >= firstVisibleColumn)
1603 * found a search results match in the visible region
1605 firstMatchedColumn = Math.max(firstMatchedColumn,
1606 firstVisibleColumn);
1607 lastMatchedColumn = Math.min(lastMatchedColumn,
1611 * draw each mapped position separately (as contiguous positions may
1612 * wrap across lines)
1614 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1616 int displayColumn = mappedPos;
1617 if (av.hasHiddenColumns())
1619 displayColumn = alignment.getHiddenColumns()
1620 .findColumnPosition(displayColumn);
1624 * transX: offset from left edge of canvas to residue position
1626 int transX = labelWidthWest
1627 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1628 * av.getCharWidth();
1631 * transY: offset from top edge of canvas to residue position
1633 int transY = gapHeight;
1634 transY += (displayColumn - ranges.getStartRes())
1635 / wrappedWidth * wrappedHeight;
1636 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1639 * yOffset is from graphics origin to start of visible region
1641 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1642 if (transY < getHeight())
1645 gg.translate(transX, transY);
1646 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1648 gg.translate(-transX, -transY);
1660 * Answers the height in pixels of a repeating section of the wrapped
1661 * alignment, including space above, scale above if shown, sequences, and
1662 * annotation panel if shown
1666 protected int getRepeatHeightWrapped()
1668 // gap (and maybe scale) above
1669 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1672 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1674 // add annotations panel height if shown
1675 repeatHeight += getAnnotationHeight();
1677 return repeatHeight;