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 - LABEL_EAST - LABEL_WEST) / charWidth;
769 int cHeight = av.getAlignment().getHeight() * charHeight;
771 int startx = startRes;
773 int ypos = hgap; // vertical offset
774 int maxwidth = av.getAlignment().getWidth();
776 if (av.hasHiddenColumns())
778 maxwidth = av.getAlignment().getHiddenColumns()
779 .findColumnPosition(maxwidth);
782 // chop the wrapped alignment extent up into panel-sized blocks and treat
783 // each block as if it were a block from an unwrapped alignment
784 while ((ypos <= canvasHeight) && (startx < maxwidth))
786 // set end value to be start + width, or maxwidth, whichever is smaller
787 endx = startx + cWidth - 1;
794 g.translate(LABEL_WEST, 0);
796 drawUnwrappedSelection(g, group, startx, endx, 0,
797 av.getAlignment().getHeight() - 1,
800 g.translate(-LABEL_WEST, 0);
802 // update vertical offset
803 ypos += cHeight + getAnnotationHeight() + hgap;
805 // update horizontal offset
810 int getAnnotationHeight()
812 if (!av.isShowAnnotation())
817 if (annotations == null)
819 annotations = new AnnotationPanel(av);
822 return annotations.adjustPanelHeight();
826 * Draws the visible region of the alignment on the graphics context. If there
827 * are hidden column markers in the visible region, then each sub-region
828 * between the markers is drawn separately, followed by the hidden column
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;