2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.viewmodel.ViewportListenerI;
31 import jalview.viewmodel.ViewportRanges;
33 import java.awt.AlphaComposite;
34 import java.awt.BasicStroke;
35 import java.awt.BorderLayout;
36 import java.awt.Color;
37 import java.awt.FontMetrics;
38 import java.awt.Graphics;
39 import java.awt.Graphics2D;
40 import java.awt.RenderingHints;
41 import java.awt.Shape;
42 import java.awt.image.BufferedImage;
43 import java.beans.PropertyChangeEvent;
44 import java.util.List;
46 import javax.swing.JComponent;
54 public class SeqCanvas extends JComponent implements ViewportListenerI
56 private static String ZEROS = "0000000000";
58 final FeatureRenderer fr;
60 final SequenceRenderer seqRdr;
68 boolean fastPaint = false;
82 boolean fastpainting = false;
84 AnnotationPanel annotations;
87 * Creates a new SeqCanvas object.
92 public SeqCanvas(AlignmentPanel ap)
96 fr = new FeatureRenderer(ap);
97 seqRdr = new SequenceRenderer(av);
98 setLayout(new BorderLayout());
99 PaintRefresher.Register(this, av.getSequenceSetId());
100 setBackground(Color.white);
102 av.getRanges().addPropertyChangeListener(this);
105 public SequenceRenderer getSequenceRenderer()
110 public FeatureRenderer getFeatureRenderer()
115 private void updateViewport()
117 charHeight = av.getCharHeight();
118 charWidth = av.getCharWidth();
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
139 int mpos = mark.column; // (i - startx - 1)
144 String mstring = mark.text;
150 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
152 g.drawLine((mpos * charWidth) + (charWidth / 2),
153 (ypos + 2) - (charHeight / 2),
154 (mpos * charWidth) + (charWidth / 2), ypos - 2);
171 void drawWestScale(Graphics g, int startx, int endx, int ypos)
173 FontMetrics fm = getFontMetrics(av.getFont());
176 if (av.hasHiddenColumns())
178 startx = av.getAlignment().getHiddenColumns()
179 .adjustForHiddenColumns(startx);
180 endx = av.getAlignment().getHiddenColumns()
181 .adjustForHiddenColumns(endx);
184 int maxwidth = av.getAlignment().getWidth();
185 if (av.hasHiddenColumns())
187 maxwidth = av.getAlignment().getHiddenColumns()
188 .findColumnPosition(maxwidth) - 1;
192 for (int i = 0; i < av.getAlignment().getHeight(); i++)
194 SequenceI seq = av.getAlignment().getSequenceAt(i);
200 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
207 value = av.getAlignment().getSequenceAt(i).findPosition(index);
214 int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
216 g.drawString(value + "", x,
217 (ypos + (i * charHeight)) - (charHeight / 5));
234 void drawEastScale(Graphics g, int startx, int endx, int ypos)
238 if (av.hasHiddenColumns())
240 endx = av.getAlignment().getHiddenColumns()
241 .adjustForHiddenColumns(endx);
246 for (int i = 0; i < av.getAlignment().getHeight(); i++)
248 seq = av.getAlignment().getSequenceAt(i);
252 while (index > startx)
254 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
261 value = seq.findPosition(index);
268 g.drawString(String.valueOf(value), 0,
269 (ypos + (i * charHeight)) - (charHeight / 5));
276 * need to make this thread safe move alignment rendering in response to
282 * shift up or down in repaint
284 public void fastPaint(int horizontal, int vertical)
286 if (fastpainting || gg == null)
294 ViewportRanges ranges = av.getRanges();
295 int startRes = ranges.getStartRes();
296 int endRes = ranges.getEndRes();
297 int startSeq = ranges.getStartSeq();
298 int endSeq = ranges.getEndSeq();
302 gg.copyArea(horizontal * charWidth, vertical * charHeight,
303 img.getWidth(), img.getHeight(), -horizontal * charWidth,
304 -vertical * charHeight);
306 if (horizontal > 0) // scrollbar pulled right, image to the left
308 transX = (endRes - startRes - horizontal) * charWidth;
309 startRes = endRes - horizontal;
311 else if (horizontal < 0)
313 endRes = startRes - horizontal;
315 else if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
343 fastpainting = false;
347 public void paintComponent(Graphics g)
349 super.paintComponent(g);
353 ViewportRanges ranges = av.getRanges();
355 int width = getWidth();
356 int height = getHeight();
358 width -= (width % charWidth);
359 height -= (height % charHeight);
361 // selectImage is the selection group outline image
362 BufferedImage selectImage = drawSelectionGroup(
363 ranges.getStartRes(), ranges.getEndRes(),
364 ranges.getStartSeq(), ranges.getEndSeq());
366 if ((img != null) && (fastPaint
367 || (getVisibleRect().width != g.getClipBounds().width)
368 || (getVisibleRect().height != g.getClipBounds().height)))
370 BufferedImage lcimg = buildLocalImage(selectImage);
371 g.drawImage(lcimg, 0, 0, this);
374 else if ((width > 0) && (height > 0))
376 // img is a cached version of the last view we drew, if any
377 // if we have no img or the size has changed, make a new one
378 if (img == null || width != img.getWidth()
379 || height != img.getHeight())
386 gg = (Graphics2D) img.getGraphics();
387 gg.setFont(av.getFont());
392 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
393 RenderingHints.VALUE_ANTIALIAS_ON);
396 gg.setColor(Color.white);
397 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
399 if (av.getWrapAlignment())
401 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
405 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
406 ranges.getStartSeq(), ranges.getEndSeq(), 0);
409 // lcimg is a local *copy* of img which we'll draw selectImage on top of
410 BufferedImage lcimg = buildLocalImage(selectImage);
411 g.drawImage(lcimg, 0, 0, this);
416 * Draw an alignment panel for printing
419 * Graphics object to draw with
421 * start residue of print area
423 * end residue of print area
425 * start sequence of print area
427 * end sequence of print area
429 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
430 int startSeq, int endSeq)
432 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
434 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
435 ((Graphics2D) g1).setComposite(
436 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
437 g1.drawImage(selectImage, 0, 0, this);
441 * Draw a wrapped alignment panel for printing
444 * Graphics object to draw with
446 * width of drawing area
447 * @param canvasHeight
448 * height of drawing area
450 * start residue of print area
452 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
453 int canvasHeight, int startRes)
455 SequenceGroup group = av.getSelectionGroup();
457 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
461 BufferedImage selectImage = null;
464 selectImage = new BufferedImage(canvasWidth, canvasHeight,
465 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
466 } catch (OutOfMemoryError er)
469 System.err.println("Print image OutOfMemory Error.\n" + er);
470 new OOMWarning("Creating wrapped alignment image for printing", er);
472 if (selectImage != null)
474 Graphics2D g2 = selectImage.createGraphics();
475 setupSelectionGroup(g2, selectImage);
476 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
480 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
481 g.drawImage(selectImage, 0, 0, this);
488 * Make a local image by combining the cached image img
491 private BufferedImage buildLocalImage(BufferedImage selectImage)
493 // clone the cached image
494 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
496 Graphics2D g2d = lcimg.createGraphics();
497 g2d.drawImage(img, 0, 0, null);
499 // overlay selection group on lcimg
500 if (selectImage != null)
503 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
504 g2d.drawImage(selectImage, 0, 0, this);
512 * Set up a buffered image of the correct height and size for the sequence canvas
514 private BufferedImage setupImage()
516 BufferedImage lcimg = null;
518 int width = getWidth();
519 int height = getHeight();
521 width -= (width % charWidth);
522 height -= (height % charHeight);
524 if ((width < 1) || (height < 1))
531 lcimg = new BufferedImage(width, height,
532 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
533 } catch (OutOfMemoryError er)
537 "Group image OutOfMemory Redraw Error.\n" + er);
538 new OOMWarning("Creating alignment image for display", er);
547 * Returns the visible width of the canvas in residues, after allowing for
548 * East or West scales (if shown)
551 * the width in pixels (possibly including scales)
555 public int getWrappedCanvasWidth(int canvasWidth)
557 FontMetrics fm = getFontMetrics(av.getFont());
562 if (av.getScaleRightWrapped())
564 labelWidthEast = getLabelWidth(fm);
567 if (av.getScaleLeftWrapped())
569 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
573 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
577 * Returns a pixel width suitable for showing the largest sequence coordinate
578 * (end position) in the alignment. Returns 2 plus the number of decimal
579 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
584 protected int getLabelWidth(FontMetrics fm)
587 * find the biggest sequence end position we need to show
588 * (note this is not necessarily the sequence length)
591 AlignmentI alignment = av.getAlignment();
592 for (int i = 0; i < alignment.getHeight(); i++)
594 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
598 for (int i = maxWidth; i > 0; i /= 10)
603 return fm.stringWidth(ZEROS.substring(0, length));
613 * @param canvasHeight
618 private void drawWrappedPanel(Graphics g, int canvasWidth,
619 int canvasHeight, int startRes)
622 AlignmentI al = av.getAlignment();
625 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
627 FontMetrics fm = getFontMetrics(av.getFont());
628 labelWidth = getLabelWidth(fm);
631 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
632 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
634 int hgap = charHeight;
635 if (av.getScaleAboveWrapped())
640 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
641 int cHeight = av.getAlignment().getHeight() * charHeight;
643 av.setWrappedWidth(cWidth);
645 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
649 int maxwidth = av.getAlignment().getWidth();
651 if (av.hasHiddenColumns())
653 maxwidth = av.getAlignment().getHiddenColumns()
654 .findColumnPosition(maxwidth);
657 int annotationHeight = getAnnotationHeight();
659 while ((ypos <= canvasHeight) && (startRes < maxwidth))
661 endx = startRes + cWidth - 1;
668 g.setFont(av.getFont());
669 g.setColor(Color.black);
671 if (av.getScaleLeftWrapped())
673 drawWestScale(g, startRes, endx, ypos);
676 if (av.getScaleRightWrapped())
678 g.translate(canvasWidth - labelWidthEast, 0);
679 drawEastScale(g, startRes, endx, ypos);
680 g.translate(-(canvasWidth - labelWidthEast), 0);
683 g.translate(labelWidthWest, 0);
685 if (av.getScaleAboveWrapped())
687 drawNorthScale(g, startRes, endx, ypos);
690 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
692 g.setColor(Color.blue);
694 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
695 List<Integer> positions = hidden.findHiddenRegionPositions();
696 for (int pos : positions)
698 res = pos - startRes;
700 if (res < 0 || res > endx - 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 },
717 // When printing we have an extra clipped region,
718 // the Printable page which we need to account for here
719 Shape clip = g.getClip();
723 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
727 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
728 (int) clip.getBounds().getHeight());
731 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
733 if (av.isShowAnnotation())
735 g.translate(0, cHeight + ypos + 3);
736 if (annotations == null)
738 annotations = new AnnotationPanel(av);
741 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
743 g.translate(0, -cHeight - ypos - 3);
746 g.translate(-labelWidthWest, 0);
748 ypos += cHeight + annotationHeight + hgap;
755 * Draw a selection group over a wrapped alignment
757 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
759 int canvasHeight, int startRes)
761 // height gap above each panel
762 int hgap = charHeight;
763 if (av.getScaleAboveWrapped())
768 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
770 int cHeight = av.getAlignment().getHeight() * charHeight;
772 int startx = startRes;
774 int ypos = hgap; // vertical offset
775 int maxwidth = av.getAlignment().getWidth();
777 if (av.hasHiddenColumns())
779 maxwidth = av.getAlignment().getHiddenColumns()
780 .findColumnPosition(maxwidth);
783 // chop the wrapped alignment extent up into panel-sized blocks and treat
784 // each block as if it were a block from an unwrapped alignment
785 while ((ypos <= canvasHeight) && (startx < maxwidth))
787 // set end value to be start + width, or maxwidth, whichever is smaller
788 endx = startx + cWidth - 1;
795 g.translate(labelWidthWest, 0);
797 drawUnwrappedSelection(g, group, startx, endx, 0,
798 av.getAlignment().getHeight() - 1,
801 g.translate(-labelWidthWest, 0);
803 // update vertical offset
804 ypos += cHeight + getAnnotationHeight() + hgap;
806 // update horizontal offset
811 int getAnnotationHeight()
813 if (!av.isShowAnnotation())
818 if (annotations == null)
820 annotations = new AnnotationPanel(av);
823 return annotations.adjustPanelHeight();
827 * Draws the visible region of the alignment on the graphics context. If there
828 * are hidden column markers in the visible region, then each sub-region
829 * between the markers is drawn separately, followed by the hidden column
833 * Graphics object to draw with
835 * offset of the first column in the visible region (0..)
837 * offset of the last column in the visible region (0..)
839 * offset of the first sequence in the visible region (0..)
841 * offset of the last sequence in the visible region (0..)
843 * vertical offset at which to draw (for wrapped alignments)
845 public void drawPanel(Graphics g1, final int startRes, final int endRes,
846 final int startSeq, final int endSeq, final int yOffset)
849 if (!av.hasHiddenColumns())
851 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
856 final int screenYMax = endRes - startRes;
857 int blockStart = startRes;
858 int blockEnd = endRes;
860 for (int[] region : av.getAlignment().getHiddenColumns()
861 .getHiddenColumnsCopy())
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, blockStart + screenYMax
879 g1.translate(screenY * charWidth, 0);
881 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
884 * draw the downline of the hidden column marker (ScalePanel draws the
885 * triangle on top) if we reached it
887 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
889 g1.setColor(Color.blue);
891 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
892 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
893 (endSeq - startSeq + 1) * charHeight + yOffset);
896 g1.translate(-screenY * charWidth, 0);
897 screenY += blockEnd - blockStart + 1;
898 blockStart = hideEnd + 1;
900 if (screenY > screenYMax)
902 // already rendered last block
907 if (screenY <= screenYMax)
909 // remaining visible region to render
910 blockEnd = blockStart + screenYMax - screenY;
911 g1.translate(screenY * charWidth, 0);
912 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
914 g1.translate(-screenY * charWidth, 0);
921 * Draws a region of the visible alignment
925 * offset of the first column in the visible region (0..)
927 * offset of the last column in the visible region (0..)
929 * offset of the first sequence in the visible region (0..)
931 * offset of the last sequence in the visible region (0..)
933 * vertical offset at which to draw (for wrapped alignments)
935 private void draw(Graphics g, int startRes, int endRes, int startSeq,
936 int endSeq, int offset)
938 g.setFont(av.getFont());
939 seqRdr.prepare(g, av.isRenderGaps());
943 // / First draw the sequences
944 // ///////////////////////////
945 for (int i = startSeq; i <= endSeq; i++)
947 nextSeq = av.getAlignment().getSequenceAt(i);
950 // occasionally, a race condition occurs such that the alignment row is
954 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
955 startRes, endRes, offset + ((i - startSeq) * charHeight));
957 if (av.isShowSequenceFeatures())
959 fr.drawSequence(g, nextSeq, startRes, endRes,
960 offset + ((i - startSeq) * charHeight), false);
964 * highlight search Results once sequence has been drawn
966 if (av.hasSearchResults())
968 SearchResultsI searchResults = av.getSearchResults();
969 int[] visibleResults = searchResults.getResults(nextSeq,
971 if (visibleResults != null)
973 for (int r = 0; r < visibleResults.length; r += 2)
975 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
976 visibleResults[r + 1], (visibleResults[r] - startRes)
978 + ((i - startSeq) * charHeight));
983 if (av.cursorMode && cursorY == i && cursorX >= startRes
984 && cursorX <= endRes)
986 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
987 offset + ((i - startSeq) * charHeight));
991 if (av.getSelectionGroup() != null
992 || av.getAlignment().getGroups().size() > 0)
994 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
999 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1000 int startSeq, int endSeq, int offset)
1002 Graphics2D g = (Graphics2D) g1;
1004 // ///////////////////////////////////
1005 // Now outline any areas if necessary
1006 // ///////////////////////////////////
1008 SequenceGroup group = null;
1009 int groupIndex = -1;
1011 if (av.getAlignment().getGroups().size() > 0)
1013 group = av.getAlignment().getGroups().get(0);
1019 g.setStroke(new BasicStroke());
1020 g.setColor(group.getOutlineColour());
1024 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1029 g.setStroke(new BasicStroke());
1031 if (groupIndex >= av.getAlignment().getGroups().size())
1036 group = av.getAlignment().getGroups().get(groupIndex);
1038 } while (groupIndex < av.getAlignment().getGroups().size());
1046 * Draw the selection group as a separate image and overlay
1048 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1049 int startSeq, int endSeq)
1051 // get a new image of the correct size
1052 BufferedImage selectionImage = setupImage();
1054 if (selectionImage == null)
1059 SequenceGroup group = av.getSelectionGroup();
1066 // set up drawing colour
1067 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1069 setupSelectionGroup(g, selectionImage);
1071 if (!av.getWrapAlignment())
1073 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1078 drawWrappedSelection(g, group, getWidth(), getHeight(),
1079 av.getRanges().getStartRes());
1083 return selectionImage;
1087 * Set up graphics for selection group
1089 private void setupSelectionGroup(Graphics2D g,
1090 BufferedImage selectionImage)
1092 // set background to transparent
1093 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1094 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1096 // set up foreground to draw red dashed line
1097 g.setComposite(AlphaComposite.Src);
1098 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1099 BasicStroke.JOIN_ROUND, 3f, new float[]
1101 g.setColor(Color.RED);
1105 * Draw a selection group over an unwrapped alignment
1106 * @param g graphics object to draw with
1107 * @param group selection group
1108 * @param startRes start residue of area to draw
1109 * @param endRes end residue of area to draw
1110 * @param startSeq start sequence of area to draw
1111 * @param endSeq end sequence of area to draw
1112 * @param offset vertical offset (used when called from wrapped alignment code)
1114 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1115 int startRes, int endRes, int startSeq, int endSeq, int offset)
1117 if (!av.hasHiddenColumns())
1119 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1124 // package into blocks of visible columns
1126 int blockStart = startRes;
1127 int blockEnd = endRes;
1129 for (int[] region : av.getAlignment().getHiddenColumns()
1130 .getHiddenColumnsCopy())
1132 int hideStart = region[0];
1133 int hideEnd = region[1];
1135 if (hideStart <= blockStart)
1137 blockStart += (hideEnd - hideStart) + 1;
1141 blockEnd = hideStart - 1;
1143 g.translate(screenY * charWidth, 0);
1144 drawPartialGroupOutline(g, group,
1145 blockStart, blockEnd, startSeq, endSeq, offset);
1147 g.translate(-screenY * charWidth, 0);
1148 screenY += blockEnd - blockStart + 1;
1149 blockStart = hideEnd + 1;
1151 if (screenY > (endRes - startRes))
1153 // already rendered last block
1158 if (screenY <= (endRes - startRes))
1160 // remaining visible region to render
1161 blockEnd = blockStart + (endRes - startRes) - screenY;
1162 g.translate(screenY * charWidth, 0);
1163 drawPartialGroupOutline(g, group,
1164 blockStart, blockEnd, startSeq, endSeq, offset);
1166 g.translate(-screenY * charWidth, 0);
1172 * Draw the selection group as a separate image and overlay
1174 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1175 int startRes, int endRes, int startSeq, int endSeq,
1178 int visWidth = (endRes - startRes + 1) * charWidth;
1182 boolean inGroup = false;
1190 for (i = startSeq; i <= endSeq; i++)
1192 // position of start residue of group relative to startRes, in pixels
1193 sx = (group.getStartRes() - startRes) * charWidth;
1195 // width of group in pixels
1196 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1199 sy = verticalOffset + (i - startSeq) * charHeight;
1201 if (sx + xwidth < 0 || sx > visWidth)
1206 if ((sx <= (endRes - startRes) * charWidth)
1207 && group.getSequences(null)
1208 .contains(av.getAlignment().getSequenceAt(i)))
1210 if ((bottom == -1) && !group.getSequences(null)
1211 .contains(av.getAlignment().getSequenceAt(i + 1)))
1213 bottom = sy + charHeight;
1218 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1219 .contains(av.getAlignment().getSequenceAt(i - 1)))
1232 // if start position is visible, draw vertical line to left of
1234 if (sx >= 0 && sx < visWidth)
1236 g.drawLine(sx, oldY, sx, sy);
1239 // if end position is visible, draw vertical line to right of
1241 if (sx + xwidth < visWidth)
1243 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1252 // don't let width extend beyond current block, or group extent
1254 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1256 xwidth = (endRes - startRes + 1) * charWidth - sx;
1259 // draw horizontal line at top of group
1262 g.drawLine(sx, top, sx + xwidth, top);
1266 // draw horizontal line at bottom of group
1269 g.drawLine(sx, bottom, sx + xwidth, bottom);
1280 sy = verticalOffset + ((i - startSeq) * charHeight);
1281 if (sx >= 0 && sx < visWidth)
1283 g.drawLine(sx, oldY, sx, sy);
1286 if (sx + xwidth < visWidth)
1288 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1297 if (sx + xwidth > visWidth)
1301 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1303 xwidth = (endRes - startRes + 1) * charWidth;
1308 g.drawLine(sx, top, sx + xwidth, top);
1314 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1323 * Highlights search results in the visible region by rendering as white text
1324 * on a black background. Any previous highlighting is removed. Answers true
1325 * if any highlight was left on the visible alignment (so status bar should be
1326 * set to match), else false.
1328 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1329 * alignment had to be scrolled to show the highlighted region, then it should
1330 * be fully redrawn, otherwise a fast paint can be performed. This argument
1331 * could be removed if fast paint of scrolled wrapped alignment is coded in
1332 * future (JAL-2609).
1335 * @param noFastPaint
1338 public boolean highlightSearchResults(SearchResultsI results,
1339 boolean noFastPaint)
1345 boolean wrapped = av.getWrapAlignment();
1349 fastPaint = !noFastPaint;
1350 fastpainting = fastPaint;
1355 * to avoid redrawing the whole visible region, we instead
1356 * redraw just the minimal regions to remove previous highlights
1359 SearchResultsI previous = av.getSearchResults();
1360 av.setSearchResults(results);
1361 boolean redrawn = false;
1362 boolean drawn = false;
1365 redrawn = drawMappedPositionsWrapped(previous);
1366 drawn = drawMappedPositionsWrapped(results);
1371 redrawn = drawMappedPositions(previous);
1372 drawn = drawMappedPositions(results);
1377 * if highlights were either removed or added, repaint
1385 * return true only if highlights were added
1391 fastpainting = false;
1396 * Redraws the minimal rectangle in the visible region (if any) that includes
1397 * mapped positions of the given search results. Whether or not positions are
1398 * highlighted depends on the SearchResults set on the Viewport. This allows
1399 * this method to be called to either clear or set highlighting. Answers true
1400 * if any positions were drawn (in which case a repaint is still required),
1406 protected boolean drawMappedPositions(SearchResultsI results)
1408 if (results == null)
1414 * calculate the minimal rectangle to redraw that
1415 * includes both new and existing search results
1417 int firstSeq = Integer.MAX_VALUE;
1419 int firstCol = Integer.MAX_VALUE;
1421 boolean matchFound = false;
1423 ViewportRanges ranges = av.getRanges();
1424 int firstVisibleColumn = ranges.getStartRes();
1425 int lastVisibleColumn = ranges.getEndRes();
1426 AlignmentI alignment = av.getAlignment();
1427 if (av.hasHiddenColumns())
1429 firstVisibleColumn = alignment.getHiddenColumns()
1430 .adjustForHiddenColumns(firstVisibleColumn);
1431 lastVisibleColumn = alignment.getHiddenColumns()
1432 .adjustForHiddenColumns(lastVisibleColumn);
1435 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1436 .getEndSeq(); seqNo++)
1438 SequenceI seq = alignment.getSequenceAt(seqNo);
1440 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1442 if (visibleResults != null)
1444 for (int i = 0; i < visibleResults.length - 1; i += 2)
1446 int firstMatchedColumn = visibleResults[i];
1447 int lastMatchedColumn = visibleResults[i + 1];
1448 if (firstMatchedColumn <= lastVisibleColumn
1449 && lastMatchedColumn >= firstVisibleColumn)
1452 * found a search results match in the visible region -
1453 * remember the first and last sequence matched, and the first
1454 * and last visible columns in the matched positions
1457 firstSeq = Math.min(firstSeq, seqNo);
1458 lastSeq = Math.max(lastSeq, seqNo);
1459 firstMatchedColumn = Math.max(firstMatchedColumn,
1460 firstVisibleColumn);
1461 lastMatchedColumn = Math.min(lastMatchedColumn,
1463 firstCol = Math.min(firstCol, firstMatchedColumn);
1464 lastCol = Math.max(lastCol, lastMatchedColumn);
1472 if (av.hasHiddenColumns())
1474 firstCol = alignment.getHiddenColumns()
1475 .findColumnPosition(firstCol);
1476 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1478 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1479 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1480 gg.translate(transX, transY);
1481 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1482 gg.translate(-transX, -transY);
1489 public void propertyChange(PropertyChangeEvent evt)
1491 String eventName = evt.getPropertyName();
1493 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1498 else if (av.getWrapAlignment())
1500 if (eventName.equals(ViewportRanges.STARTRES))
1508 if (eventName.equals(ViewportRanges.STARTRES))
1510 // Make sure we're not trying to draw a panel
1511 // larger than the visible window
1512 ViewportRanges vpRanges = av.getRanges();
1513 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1514 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1515 if (scrollX > range)
1519 else if (scrollX < -range)
1525 // Both scrolling and resizing change viewport ranges: scrolling changes
1526 // both start and end points, but resize only changes end values.
1527 // Here we only want to fastpaint on a scroll, with resize using a normal
1528 // paint, so scroll events are identified as changes to the horizontal or
1529 // vertical start value.
1530 if (eventName.equals(ViewportRanges.STARTRES))
1532 // scroll - startres and endres both change
1533 fastPaint(scrollX, 0);
1535 else if (eventName.equals(ViewportRanges.STARTSEQ))
1538 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1544 * Redraws any positions in the search results in the visible region of a
1545 * wrapped alignment. Any highlights are drawn depending on the search results
1546 * set on the Viewport, not the <code>results</code> argument. This allows
1547 * this method to be called either to clear highlights (passing the previous
1548 * search results), or to draw new highlights.
1553 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1555 if (results == null)
1560 boolean matchFound = false;
1562 int wrappedWidth = av.getWrappedWidth();
1563 int wrappedHeight = getRepeatHeightWrapped();
1565 ViewportRanges ranges = av.getRanges();
1566 int canvasHeight = getHeight();
1567 int repeats = canvasHeight / wrappedHeight;
1568 if (canvasHeight / wrappedHeight > 0)
1573 int firstVisibleColumn = ranges.getStartRes();
1574 int lastVisibleColumn = ranges.getStartRes() + repeats
1575 * ranges.getViewportWidth() - 1;
1577 AlignmentI alignment = av.getAlignment();
1578 if (av.hasHiddenColumns())
1580 firstVisibleColumn = alignment.getHiddenColumns()
1581 .adjustForHiddenColumns(firstVisibleColumn);
1582 lastVisibleColumn = alignment.getHiddenColumns()
1583 .adjustForHiddenColumns(lastVisibleColumn);
1586 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1588 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1589 .getEndSeq(); seqNo++)
1591 SequenceI seq = alignment.getSequenceAt(seqNo);
1593 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1595 if (visibleResults != null)
1597 for (int i = 0; i < visibleResults.length - 1; i += 2)
1599 int firstMatchedColumn = visibleResults[i];
1600 int lastMatchedColumn = visibleResults[i + 1];
1601 if (firstMatchedColumn <= lastVisibleColumn
1602 && lastMatchedColumn >= firstVisibleColumn)
1605 * found a search results match in the visible region
1607 firstMatchedColumn = Math.max(firstMatchedColumn,
1608 firstVisibleColumn);
1609 lastMatchedColumn = Math.min(lastMatchedColumn,
1613 * draw each mapped position separately (as contiguous positions may
1614 * wrap across lines)
1616 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1618 int displayColumn = mappedPos;
1619 if (av.hasHiddenColumns())
1621 displayColumn = alignment.getHiddenColumns()
1622 .findColumnPosition(displayColumn);
1626 * transX: offset from left edge of canvas to residue position
1628 int transX = labelWidthWest
1629 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1630 * av.getCharWidth();
1633 * transY: offset from top edge of canvas to residue position
1635 int transY = gapHeight;
1636 transY += (displayColumn - ranges.getStartRes())
1637 / wrappedWidth * wrappedHeight;
1638 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1641 * yOffset is from graphics origin to start of visible region
1643 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1644 if (transY < getHeight())
1647 gg.translate(transX, transY);
1648 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1650 gg.translate(-transX, -transY);
1662 * Answers the height in pixels of a repeating section of the wrapped
1663 * alignment, including space above, scale above if shown, sequences, and
1664 * annotation panel if shown
1668 protected int getRepeatHeightWrapped()
1670 // gap (and maybe scale) above
1671 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1674 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1676 // add annotations panel height if shown
1677 repeatHeight += getAnnotationHeight();
1679 return repeatHeight;