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 drawPanelForPrint(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 * Make a local image by combining the cached image img
444 private BufferedImage buildLocalImage(BufferedImage selectImage)
446 // clone the cached image
447 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
449 Graphics2D g2d = lcimg.createGraphics();
450 g2d.drawImage(img, 0, 0, null);
452 // overlay selection group on lcimg
453 if (selectImage != null)
456 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
457 g2d.drawImage(selectImage, 0, 0, this);
465 * Set up a buffered image of the correct height and size for the sequence canvas
467 private BufferedImage setupImage()
469 BufferedImage lcimg = null;
471 int width = getWidth();
472 int height = getHeight();
474 width -= (width % charWidth);
475 height -= (height % charHeight);
477 if ((width < 1) || (height < 1))
484 lcimg = new BufferedImage(width, height,
485 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
486 } catch (OutOfMemoryError er)
490 "Group image OutOfMemory Redraw Error.\n" + er);
491 new OOMWarning("Creating alignment image for display", er);
500 * Returns the visible width of the canvas in residues, after allowing for
501 * East or West scales (if shown)
504 * the width in pixels (possibly including scales)
508 public int getWrappedCanvasWidth(int canvasWidth)
510 FontMetrics fm = getFontMetrics(av.getFont());
515 if (av.getScaleRightWrapped())
517 labelWidthEast = getLabelWidth(fm);
520 if (av.getScaleLeftWrapped())
522 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
526 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
530 * Returns a pixel width suitable for showing the largest sequence coordinate
531 * (end position) in the alignment. Returns 2 plus the number of decimal
532 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
537 protected int getLabelWidth(FontMetrics fm)
540 * find the biggest sequence end position we need to show
541 * (note this is not necessarily the sequence length)
544 AlignmentI alignment = av.getAlignment();
545 for (int i = 0; i < alignment.getHeight(); i++)
547 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
551 for (int i = maxWidth; i > 0; i /= 10)
556 return fm.stringWidth(ZEROS.substring(0, length));
566 * @param canvasHeight
571 public void drawWrappedPanel(Graphics g, int canvasWidth,
572 int canvasHeight, int startRes)
575 AlignmentI al = av.getAlignment();
578 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
580 FontMetrics fm = getFontMetrics(av.getFont());
581 labelWidth = getLabelWidth(fm);
584 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
585 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
587 int hgap = charHeight;
588 if (av.getScaleAboveWrapped())
593 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
594 int cHeight = av.getAlignment().getHeight() * charHeight;
596 av.setWrappedWidth(cWidth);
598 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
602 int maxwidth = av.getAlignment().getWidth();
604 if (av.hasHiddenColumns())
606 maxwidth = av.getAlignment().getHiddenColumns()
607 .findColumnPosition(maxwidth);
610 int annotationHeight = getAnnotationHeight();
612 while ((ypos <= canvasHeight) && (startRes < maxwidth))
614 endx = startRes + cWidth - 1;
621 g.setFont(av.getFont());
622 g.setColor(Color.black);
624 if (av.getScaleLeftWrapped())
626 drawWestScale(g, startRes, endx, ypos);
629 if (av.getScaleRightWrapped())
631 g.translate(canvasWidth - labelWidthEast, 0);
632 drawEastScale(g, startRes, endx, ypos);
633 g.translate(-(canvasWidth - labelWidthEast), 0);
636 g.translate(labelWidthWest, 0);
638 if (av.getScaleAboveWrapped())
640 drawNorthScale(g, startRes, endx, ypos);
643 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
645 g.setColor(Color.blue);
647 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
648 List<Integer> positions = hidden.findHiddenRegionPositions();
649 for (int pos : positions)
651 res = pos - startRes;
653 if (res < 0 || res > endx - startRes)
660 { res * charWidth - charHeight / 4,
661 res * charWidth + charHeight / 4, res * charWidth },
663 { ypos - (charHeight / 2), ypos - (charHeight / 2),
664 ypos - (charHeight / 2) + 8 },
670 // When printing we have an extra clipped region,
671 // the Printable page which we need to account for here
672 Shape clip = g.getClip();
676 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
680 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
681 (int) clip.getBounds().getHeight());
684 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
686 if (av.isShowAnnotation())
688 g.translate(0, cHeight + ypos + 3);
689 if (annotations == null)
691 annotations = new AnnotationPanel(av);
694 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
696 g.translate(0, -cHeight - ypos - 3);
699 g.translate(-labelWidthWest, 0);
701 ypos += cHeight + annotationHeight + hgap;
708 * Draw a selection group over a wrapped alignment
710 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
712 int canvasHeight, int startRes)
714 // height gap above each panel
715 int hgap = charHeight;
716 if (av.getScaleAboveWrapped())
721 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
723 int cHeight = av.getAlignment().getHeight() * charHeight;
725 int startx = startRes;
727 int ypos = hgap; // vertical offset
728 int maxwidth = av.getAlignment().getWidth();
730 if (av.hasHiddenColumns())
732 maxwidth = av.getAlignment().getHiddenColumns()
733 .findColumnPosition(maxwidth);
736 // chop the wrapped alignment extent up into panel-sized blocks and treat
737 // each block as if it were a block from an unwrapped alignment
738 while ((ypos <= canvasHeight) && (startx < maxwidth))
740 // set end value to be start + width, or maxwidth, whichever is smaller
741 endx = startx + cWidth - 1;
748 g.translate(labelWidthWest, 0);
750 drawUnwrappedSelection(g, group, startx, endx, 0,
751 av.getAlignment().getHeight() - 1,
754 g.translate(-labelWidthWest, 0);
756 // update vertical offset
757 ypos += cHeight + getAnnotationHeight() + hgap;
759 // update horizontal offset
764 int getAnnotationHeight()
766 if (!av.isShowAnnotation())
771 if (annotations == null)
773 annotations = new AnnotationPanel(av);
776 return annotations.adjustPanelHeight();
781 * Draws the visible region of the alignment on the graphics context. If there
782 * are hidden column markers in the visible region, then each sub-region
783 * between the markers is drawn separately, followed by the hidden column
787 * Graphics object to draw with
789 * offset of the first column in the visible region (0..)
791 * offset of the last column in the visible region (0..)
793 * offset of the first sequence in the visible region (0..)
795 * offset of the last sequence in the visible region (0..)
797 * vertical offset at which to draw (for wrapped alignments)
799 private void drawPanel(Graphics g1, int startRes, int endRes,
800 int startSeq, int endSeq, int yOffset)
804 if (!av.hasHiddenColumns())
806 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
811 final int screenYMax = endRes - startRes;
812 int blockStart = startRes;
813 int blockEnd = endRes;
815 for (int[] region : av.getAlignment().getHiddenColumns()
816 .getHiddenColumnsCopy())
818 int hideStart = region[0];
819 int hideEnd = region[1];
821 if (hideStart <= blockStart)
823 blockStart += (hideEnd - hideStart) + 1;
828 * draw up to just before the next hidden region, or the end of
829 * the visible region, whichever comes first
831 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
834 g1.translate(screenY * charWidth, 0);
836 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
839 * draw the downline of the hidden column marker (ScalePanel draws the
840 * triangle on top) if we reached it
842 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
844 g1.setColor(Color.blue);
846 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
847 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
848 (endSeq - startSeq + 1) * charHeight + yOffset);
851 g1.translate(-screenY * charWidth, 0);
852 screenY += blockEnd - blockStart + 1;
853 blockStart = hideEnd + 1;
855 if (screenY > screenYMax)
857 // already rendered last block
862 if (screenY <= screenYMax)
864 // remaining visible region to render
865 blockEnd = blockStart + screenYMax - screenY;
866 g1.translate(screenY * charWidth, 0);
867 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
869 g1.translate(-screenY * charWidth, 0);
877 * Draws a region of the visible alignment
881 * offset of the first column in the visible region (0..)
883 * offset of the last column in the visible region (0..)
885 * offset of the first sequence in the visible region (0..)
887 * offset of the last sequence in the visible region (0..)
889 * vertical offset at which to draw (for wrapped alignments)
891 private void draw(Graphics g, int startRes, int endRes, int startSeq,
892 int endSeq, int offset)
894 g.setFont(av.getFont());
895 seqRdr.prepare(g, av.isRenderGaps());
899 // / First draw the sequences
900 // ///////////////////////////
901 for (int i = startSeq; i <= endSeq; i++)
903 nextSeq = av.getAlignment().getSequenceAt(i);
906 // occasionally, a race condition occurs such that the alignment row is
910 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
911 startRes, endRes, offset + ((i - startSeq) * charHeight));
913 if (av.isShowSequenceFeatures())
915 fr.drawSequence(g, nextSeq, startRes, endRes,
916 offset + ((i - startSeq) * charHeight), false);
920 * highlight search Results once sequence has been drawn
922 if (av.hasSearchResults())
924 SearchResultsI searchResults = av.getSearchResults();
925 int[] visibleResults = searchResults.getResults(nextSeq,
927 if (visibleResults != null)
929 for (int r = 0; r < visibleResults.length; r += 2)
931 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
932 visibleResults[r + 1], (visibleResults[r] - startRes)
934 + ((i - startSeq) * charHeight));
939 if (av.cursorMode && cursorY == i && cursorX >= startRes
940 && cursorX <= endRes)
942 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
943 offset + ((i - startSeq) * charHeight));
947 if (av.getSelectionGroup() != null
948 || av.getAlignment().getGroups().size() > 0)
950 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
955 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
956 int startSeq, int endSeq, int offset)
958 Graphics2D g = (Graphics2D) g1;
960 // ///////////////////////////////////
961 // Now outline any areas if necessary
962 // ///////////////////////////////////
964 SequenceGroup group = null;
967 if (av.getAlignment().getGroups().size() > 0)
969 group = av.getAlignment().getGroups().get(0);
975 g.setStroke(new BasicStroke());
976 g.setColor(group.getOutlineColour());
980 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
985 g.setStroke(new BasicStroke());
987 if (groupIndex >= av.getAlignment().getGroups().size())
992 group = av.getAlignment().getGroups().get(groupIndex);
994 } while (groupIndex < av.getAlignment().getGroups().size());
1002 * Draw the selection group as a separate image and overlay
1004 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1005 int startSeq, int endSeq)
1007 // get a new image of the correct size
1008 BufferedImage selectionImage = setupImage();
1010 if (selectionImage == null)
1015 SequenceGroup group = av.getSelectionGroup();
1022 // set up drawing colour
1023 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1025 // set background to transparent
1026 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1027 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1029 // set up foreground to draw red dashed line
1030 g.setComposite(AlphaComposite.Src);
1031 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1032 BasicStroke.JOIN_ROUND, 3f, new float[]
1034 g.setColor(Color.RED);
1036 if (!av.getWrapAlignment())
1038 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1043 drawWrappedSelection(g, group, getWidth(), getHeight(),
1044 av.getRanges().getStartRes());
1048 return selectionImage;
1052 * Draw a selection group over an unwrapped alignment
1053 * @param g graphics object to draw with
1054 * @param group selection group
1055 * @param startRes start residue of area to draw
1056 * @param endRes end residue of area to draw
1057 * @param startSeq start sequence of area to draw
1058 * @param endSeq end sequence of area to draw
1059 * @param offset vertical offset (used when called from wrapped alignment code)
1061 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1062 int startRes, int endRes, int startSeq, int endSeq, int offset)
1064 if (!av.hasHiddenColumns())
1066 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1071 // package into blocks of visible columns
1073 int blockStart = startRes;
1074 int blockEnd = endRes;
1076 for (int[] region : av.getAlignment().getHiddenColumns()
1077 .getHiddenColumnsCopy())
1079 int hideStart = region[0];
1080 int hideEnd = region[1];
1082 if (hideStart <= blockStart)
1084 blockStart += (hideEnd - hideStart) + 1;
1088 blockEnd = hideStart - 1;
1090 g.translate(screenY * charWidth, 0);
1091 drawPartialGroupOutline(g, group,
1092 blockStart, blockEnd, startSeq, endSeq, offset);
1094 g.translate(-screenY * charWidth, 0);
1095 screenY += blockEnd - blockStart + 1;
1096 blockStart = hideEnd + 1;
1098 if (screenY > (endRes - startRes))
1100 // already rendered last block
1105 if (screenY <= (endRes - startRes))
1107 // remaining visible region to render
1108 blockEnd = blockStart + (endRes - startRes) - screenY;
1109 g.translate(screenY * charWidth, 0);
1110 drawPartialGroupOutline(g, group,
1111 blockStart, blockEnd, startSeq, endSeq, offset);
1113 g.translate(-screenY * charWidth, 0);
1119 * Draw the selection group as a separate image and overlay
1121 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1122 int startRes, int endRes, int startSeq, int endSeq,
1125 int visWidth = (endRes - startRes + 1) * charWidth;
1129 boolean inGroup = false;
1137 for (i = startSeq; i <= endSeq; i++)
1139 // position of start residue of group relative to startRes, in pixels
1140 sx = (group.getStartRes() - startRes) * charWidth;
1142 // width of group in pixels
1143 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1146 sy = verticalOffset + (i - startSeq) * charHeight;
1148 if (sx + xwidth < 0 || sx > visWidth)
1153 if ((sx <= (endRes - startRes) * charWidth)
1154 && group.getSequences(null)
1155 .contains(av.getAlignment().getSequenceAt(i)))
1157 if ((bottom == -1) && !group.getSequences(null)
1158 .contains(av.getAlignment().getSequenceAt(i + 1)))
1160 bottom = sy + charHeight;
1165 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1166 .contains(av.getAlignment().getSequenceAt(i - 1)))
1179 // if start position is visible, draw vertical line to left of
1181 if (sx >= 0 && sx < visWidth)
1183 g.drawLine(sx, oldY, sx, sy);
1186 // if end position is visible, draw vertical line to right of
1188 if (sx + xwidth < visWidth)
1190 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1199 // don't let width extend beyond current block, or group extent
1201 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1203 xwidth = (endRes - startRes + 1) * charWidth - sx;
1206 // draw horizontal line at top of group
1209 g.drawLine(sx, top, sx + xwidth, top);
1213 // draw horizontal line at bottom of group
1216 g.drawLine(sx, bottom, sx + xwidth, bottom);
1227 sy = verticalOffset + ((i - startSeq) * charHeight);
1228 if (sx >= 0 && sx < visWidth)
1230 g.drawLine(sx, oldY, sx, sy);
1233 if (sx + xwidth < visWidth)
1235 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1244 if (sx + xwidth > visWidth)
1248 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1250 xwidth = (endRes - startRes + 1) * charWidth;
1255 g.drawLine(sx, top, sx + xwidth, top);
1261 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1270 * Highlights search results in the visible region by rendering as white text
1271 * on a black background. Any previous highlighting is removed. Answers true
1272 * if any highlight was left on the visible alignment (so status bar should be
1273 * set to match), else false.
1275 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1276 * alignment had to be scrolled to show the highlighted region, then it should
1277 * be fully redrawn, otherwise a fast paint can be performed. This argument
1278 * could be removed if fast paint of scrolled wrapped alignment is coded in
1279 * future (JAL-2609).
1282 * @param noFastPaint
1285 public boolean highlightSearchResults(SearchResultsI results,
1286 boolean noFastPaint)
1292 boolean wrapped = av.getWrapAlignment();
1296 fastPaint = !noFastPaint;
1297 fastpainting = fastPaint;
1302 * to avoid redrawing the whole visible region, we instead
1303 * redraw just the minimal regions to remove previous highlights
1306 SearchResultsI previous = av.getSearchResults();
1307 av.setSearchResults(results);
1308 boolean redrawn = false;
1309 boolean drawn = false;
1312 redrawn = drawMappedPositionsWrapped(previous);
1313 drawn = drawMappedPositionsWrapped(results);
1318 redrawn = drawMappedPositions(previous);
1319 drawn = drawMappedPositions(results);
1324 * if highlights were either removed or added, repaint
1332 * return true only if highlights were added
1338 fastpainting = false;
1343 * Redraws the minimal rectangle in the visible region (if any) that includes
1344 * mapped positions of the given search results. Whether or not positions are
1345 * highlighted depends on the SearchResults set on the Viewport. This allows
1346 * this method to be called to either clear or set highlighting. Answers true
1347 * if any positions were drawn (in which case a repaint is still required),
1353 protected boolean drawMappedPositions(SearchResultsI results)
1355 if (results == null)
1361 * calculate the minimal rectangle to redraw that
1362 * includes both new and existing search results
1364 int firstSeq = Integer.MAX_VALUE;
1366 int firstCol = Integer.MAX_VALUE;
1368 boolean matchFound = false;
1370 ViewportRanges ranges = av.getRanges();
1371 int firstVisibleColumn = ranges.getStartRes();
1372 int lastVisibleColumn = ranges.getEndRes();
1373 AlignmentI alignment = av.getAlignment();
1374 if (av.hasHiddenColumns())
1376 firstVisibleColumn = alignment.getHiddenColumns()
1377 .adjustForHiddenColumns(firstVisibleColumn);
1378 lastVisibleColumn = alignment.getHiddenColumns()
1379 .adjustForHiddenColumns(lastVisibleColumn);
1382 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1383 .getEndSeq(); seqNo++)
1385 SequenceI seq = alignment.getSequenceAt(seqNo);
1387 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1389 if (visibleResults != null)
1391 for (int i = 0; i < visibleResults.length - 1; i += 2)
1393 int firstMatchedColumn = visibleResults[i];
1394 int lastMatchedColumn = visibleResults[i + 1];
1395 if (firstMatchedColumn <= lastVisibleColumn
1396 && lastMatchedColumn >= firstVisibleColumn)
1399 * found a search results match in the visible region -
1400 * remember the first and last sequence matched, and the first
1401 * and last visible columns in the matched positions
1404 firstSeq = Math.min(firstSeq, seqNo);
1405 lastSeq = Math.max(lastSeq, seqNo);
1406 firstMatchedColumn = Math.max(firstMatchedColumn,
1407 firstVisibleColumn);
1408 lastMatchedColumn = Math.min(lastMatchedColumn,
1410 firstCol = Math.min(firstCol, firstMatchedColumn);
1411 lastCol = Math.max(lastCol, lastMatchedColumn);
1419 if (av.hasHiddenColumns())
1421 firstCol = alignment.getHiddenColumns()
1422 .findColumnPosition(firstCol);
1423 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1425 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1426 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1427 gg.translate(transX, transY);
1428 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1429 gg.translate(-transX, -transY);
1436 public void propertyChange(PropertyChangeEvent evt)
1438 String eventName = evt.getPropertyName();
1440 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1445 else if (av.getWrapAlignment())
1447 if (eventName.equals(ViewportRanges.STARTRES))
1455 if (eventName.equals(ViewportRanges.STARTRES))
1457 // Make sure we're not trying to draw a panel
1458 // larger than the visible window
1459 ViewportRanges vpRanges = av.getRanges();
1460 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1461 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1462 if (scrollX > range)
1466 else if (scrollX < -range)
1472 // Both scrolling and resizing change viewport ranges: scrolling changes
1473 // both start and end points, but resize only changes end values.
1474 // Here we only want to fastpaint on a scroll, with resize using a normal
1475 // paint, so scroll events are identified as changes to the horizontal or
1476 // vertical start value.
1477 if (eventName.equals(ViewportRanges.STARTRES))
1479 // scroll - startres and endres both change
1480 fastPaint(scrollX, 0);
1482 else if (eventName.equals(ViewportRanges.STARTSEQ))
1485 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1491 * Redraws any positions in the search results in the visible region of a
1492 * wrapped alignment. Any highlights are drawn depending on the search results
1493 * set on the Viewport, not the <code>results</code> argument. This allows
1494 * this method to be called either to clear highlights (passing the previous
1495 * search results), or to draw new highlights.
1500 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1502 if (results == null)
1507 boolean matchFound = false;
1509 int wrappedWidth = av.getWrappedWidth();
1510 int wrappedHeight = getRepeatHeightWrapped();
1512 ViewportRanges ranges = av.getRanges();
1513 int canvasHeight = getHeight();
1514 int repeats = canvasHeight / wrappedHeight;
1515 if (canvasHeight / wrappedHeight > 0)
1520 int firstVisibleColumn = ranges.getStartRes();
1521 int lastVisibleColumn = ranges.getStartRes() + repeats
1522 * ranges.getViewportWidth() - 1;
1524 AlignmentI alignment = av.getAlignment();
1525 if (av.hasHiddenColumns())
1527 firstVisibleColumn = alignment.getHiddenColumns()
1528 .adjustForHiddenColumns(firstVisibleColumn);
1529 lastVisibleColumn = alignment.getHiddenColumns()
1530 .adjustForHiddenColumns(lastVisibleColumn);
1533 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1535 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1536 .getEndSeq(); seqNo++)
1538 SequenceI seq = alignment.getSequenceAt(seqNo);
1540 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1542 if (visibleResults != null)
1544 for (int i = 0; i < visibleResults.length - 1; i += 2)
1546 int firstMatchedColumn = visibleResults[i];
1547 int lastMatchedColumn = visibleResults[i + 1];
1548 if (firstMatchedColumn <= lastVisibleColumn
1549 && lastMatchedColumn >= firstVisibleColumn)
1552 * found a search results match in the visible region
1554 firstMatchedColumn = Math.max(firstMatchedColumn,
1555 firstVisibleColumn);
1556 lastMatchedColumn = Math.min(lastMatchedColumn,
1560 * draw each mapped position separately (as contiguous positions may
1561 * wrap across lines)
1563 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1565 int displayColumn = mappedPos;
1566 if (av.hasHiddenColumns())
1568 displayColumn = alignment.getHiddenColumns()
1569 .findColumnPosition(displayColumn);
1573 * transX: offset from left edge of canvas to residue position
1575 int transX = labelWidthWest
1576 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1577 * av.getCharWidth();
1580 * transY: offset from top edge of canvas to residue position
1582 int transY = gapHeight;
1583 transY += (displayColumn - ranges.getStartRes())
1584 / wrappedWidth * wrappedHeight;
1585 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1588 * yOffset is from graphics origin to start of visible region
1590 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1591 if (transY < getHeight())
1594 gg.translate(transX, transY);
1595 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1597 gg.translate(-transX, -transY);
1609 * Answers the height in pixels of a repeating section of the wrapped
1610 * alignment, including space above, scale above if shown, sequences, and
1611 * annotation panel if shown
1615 protected int getRepeatHeightWrapped()
1617 // gap (and maybe scale) above
1618 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1621 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1623 // add annotations panel height if shown
1624 repeatHeight += getAnnotationHeight();
1626 return repeatHeight;