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.HiddenColumns.VisibleBlocksVisBoundsIterator;
26 import jalview.datamodel.SearchResultsI;
27 import jalview.datamodel.SequenceGroup;
28 import jalview.datamodel.SequenceI;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
34 import java.awt.AlphaComposite;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.RenderingHints;
42 import java.awt.Shape;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
47 import javax.swing.JComponent;
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static String ZEROS = "0000000000";
59 final FeatureRenderer fr;
61 final SequenceRenderer seqRdr;
69 boolean fastPaint = false;
83 boolean fastpainting = false;
85 AnnotationPanel annotations;
88 * Creates a new SeqCanvas object.
93 public SeqCanvas(AlignmentPanel ap)
97 fr = new FeatureRenderer(ap);
98 seqRdr = new SequenceRenderer(av);
99 setLayout(new BorderLayout());
100 PaintRefresher.Register(this, av.getSequenceSetId());
101 setBackground(Color.white);
103 av.getRanges().addPropertyChangeListener(this);
106 public SequenceRenderer getSequenceRenderer()
111 public FeatureRenderer getFeatureRenderer()
116 private void updateViewport()
118 charHeight = av.getCharHeight();
119 charWidth = av.getCharWidth();
134 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
137 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
140 int mpos = mark.column; // (i - startx - 1)
145 String mstring = mark.text;
151 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
153 g.drawLine((mpos * charWidth) + (charWidth / 2),
154 (ypos + 2) - (charHeight / 2),
155 (mpos * charWidth) + (charWidth / 2), ypos - 2);
172 void drawWestScale(Graphics g, int startx, int endx, int ypos)
174 FontMetrics fm = getFontMetrics(av.getFont());
177 if (av.hasHiddenColumns())
179 startx = av.getAlignment().getHiddenColumns()
180 .adjustForHiddenColumns(startx);
181 endx = av.getAlignment().getHiddenColumns()
182 .adjustForHiddenColumns(endx);
185 int maxwidth = av.getAlignment().getWidth();
186 if (av.hasHiddenColumns())
188 maxwidth = av.getAlignment().getHiddenColumns()
189 .findColumnPosition(maxwidth) - 1;
193 for (int i = 0; i < av.getAlignment().getHeight(); i++)
195 SequenceI seq = av.getAlignment().getSequenceAt(i);
201 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
208 value = av.getAlignment().getSequenceAt(i).findPosition(index);
215 int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
217 g.drawString(value + "", x,
218 (ypos + (i * charHeight)) - (charHeight / 5));
235 void drawEastScale(Graphics g, int startx, int endx, int ypos)
239 if (av.hasHiddenColumns())
241 endx = av.getAlignment().getHiddenColumns()
242 .adjustForHiddenColumns(endx);
247 for (int i = 0; i < av.getAlignment().getHeight(); i++)
249 seq = av.getAlignment().getSequenceAt(i);
253 while (index > startx)
255 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
262 value = seq.findPosition(index);
269 g.drawString(String.valueOf(value), 0,
270 (ypos + (i * charHeight)) - (charHeight / 5));
277 * need to make this thread safe move alignment rendering in response to
283 * shift up or down in repaint
285 public void fastPaint(int horizontal, int vertical)
287 if (fastpainting || gg == null || img == null)
295 ViewportRanges ranges = av.getRanges();
296 int startRes = ranges.getStartRes();
297 int endRes = ranges.getEndRes();
298 int startSeq = ranges.getStartSeq();
299 int endSeq = ranges.getEndSeq();
303 gg.copyArea(horizontal * charWidth, vertical * charHeight,
304 img.getWidth(), img.getHeight(), -horizontal * charWidth,
305 -vertical * charHeight);
307 if (horizontal > 0) // scrollbar pulled right, image to the left
309 transX = (endRes - startRes - horizontal) * charWidth;
310 startRes = endRes - horizontal;
312 else if (horizontal < 0)
314 endRes = startRes - horizontal;
316 else if (vertical > 0) // scroll down
318 startSeq = endSeq - vertical;
320 if (startSeq < ranges.getStartSeq())
321 { // ie scrolling too fast, more than a page at a time
322 startSeq = ranges.getStartSeq();
326 transY = img.getHeight() - ((vertical + 1) * charHeight);
329 else if (vertical < 0)
331 endSeq = startSeq - vertical;
333 if (endSeq > ranges.getEndSeq())
335 endSeq = ranges.getEndSeq();
339 gg.translate(transX, transY);
340 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
341 gg.translate(-transX, -transY);
344 fastpainting = false;
348 public void paintComponent(Graphics g)
350 super.paintComponent(g);
354 ViewportRanges ranges = av.getRanges();
356 int width = getWidth();
357 int height = getHeight();
359 width -= (width % charWidth);
360 height -= (height % charHeight);
362 // selectImage is the selection group outline image
363 BufferedImage selectImage = drawSelectionGroup(
364 ranges.getStartRes(), ranges.getEndRes(),
365 ranges.getStartSeq(), ranges.getEndSeq());
367 if ((img != null) && (fastPaint
368 || (getVisibleRect().width != g.getClipBounds().width)
369 || (getVisibleRect().height != g.getClipBounds().height)))
371 BufferedImage lcimg = buildLocalImage(selectImage);
372 g.drawImage(lcimg, 0, 0, this);
375 else if ((width > 0) && (height > 0))
377 // img is a cached version of the last view we drew, if any
378 // if we have no img or the size has changed, make a new one
379 if (img == null || width != img.getWidth()
380 || height != img.getHeight())
387 gg = (Graphics2D) img.getGraphics();
388 gg.setFont(av.getFont());
393 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
394 RenderingHints.VALUE_ANTIALIAS_ON);
397 gg.setColor(Color.white);
398 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
400 if (av.getWrapAlignment())
402 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
406 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
407 ranges.getStartSeq(), ranges.getEndSeq(), 0);
410 // lcimg is a local *copy* of img which we'll draw selectImage on top of
411 BufferedImage lcimg = buildLocalImage(selectImage);
412 g.drawImage(lcimg, 0, 0, this);
417 * Draw an alignment panel for printing
420 * Graphics object to draw with
422 * start residue of print area
424 * end residue of print area
426 * start sequence of print area
428 * end sequence of print area
430 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
431 int startSeq, int endSeq)
433 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
435 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
437 if (selectImage != null)
439 ((Graphics2D) g1).setComposite(AlphaComposite
440 .getInstance(AlphaComposite.SRC_OVER));
441 g1.drawImage(selectImage, 0, 0, this);
446 * Draw a wrapped alignment panel for printing
449 * Graphics object to draw with
451 * width of drawing area
452 * @param canvasHeight
453 * height of drawing area
455 * start residue of print area
457 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
458 int canvasHeight, int startRes)
460 SequenceGroup group = av.getSelectionGroup();
462 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
466 BufferedImage selectImage = null;
469 selectImage = new BufferedImage(canvasWidth, canvasHeight,
470 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
471 } catch (OutOfMemoryError er)
474 System.err.println("Print image OutOfMemory Error.\n" + er);
475 new OOMWarning("Creating wrapped alignment image for printing", er);
477 if (selectImage != null)
479 Graphics2D g2 = selectImage.createGraphics();
480 setupSelectionGroup(g2, selectImage);
481 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
485 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
486 g.drawImage(selectImage, 0, 0, this);
493 * Make a local image by combining the cached image img
496 private BufferedImage buildLocalImage(BufferedImage selectImage)
498 // clone the cached image
499 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
501 Graphics2D g2d = lcimg.createGraphics();
502 g2d.drawImage(img, 0, 0, null);
504 // overlay selection group on lcimg
505 if (selectImage != null)
508 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
509 g2d.drawImage(selectImage, 0, 0, this);
517 * Set up a buffered image of the correct height and size for the sequence canvas
519 private BufferedImage setupImage()
521 BufferedImage lcimg = null;
523 int width = getWidth();
524 int height = getHeight();
526 width -= (width % charWidth);
527 height -= (height % charHeight);
529 if ((width < 1) || (height < 1))
536 lcimg = new BufferedImage(width, height,
537 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
538 } catch (OutOfMemoryError er)
542 "Group image OutOfMemory Redraw Error.\n" + er);
543 new OOMWarning("Creating alignment image for display", er);
552 * Returns the visible width of the canvas in residues, after allowing for
553 * East or West scales (if shown)
556 * the width in pixels (possibly including scales)
560 public int getWrappedCanvasWidth(int canvasWidth)
562 FontMetrics fm = getFontMetrics(av.getFont());
567 if (av.getScaleRightWrapped())
569 labelWidthEast = getLabelWidth(fm);
572 if (av.getScaleLeftWrapped())
574 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
578 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
582 * Returns a pixel width suitable for showing the largest sequence coordinate
583 * (end position) in the alignment. Returns 2 plus the number of decimal
584 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
589 protected int getLabelWidth(FontMetrics fm)
592 * find the biggest sequence end position we need to show
593 * (note this is not necessarily the sequence length)
596 AlignmentI alignment = av.getAlignment();
597 for (int i = 0; i < alignment.getHeight(); i++)
599 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
603 for (int i = maxWidth; i > 0; i /= 10)
608 return fm.stringWidth(ZEROS.substring(0, length));
618 * @param canvasHeight
623 private void drawWrappedPanel(Graphics g, int canvasWidth,
624 int canvasHeight, int startRes)
627 AlignmentI al = av.getAlignment();
630 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
632 FontMetrics fm = getFontMetrics(av.getFont());
633 labelWidth = getLabelWidth(fm);
636 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
637 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
639 int hgap = charHeight;
640 if (av.getScaleAboveWrapped())
645 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
646 int cHeight = av.getAlignment().getHeight() * charHeight;
648 av.setWrappedWidth(cWidth);
650 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
654 int maxwidth = av.getAlignment().getWidth();
656 if (av.hasHiddenColumns())
658 maxwidth = av.getAlignment().getHiddenColumns()
659 .findColumnPosition(maxwidth);
662 int annotationHeight = getAnnotationHeight();
664 while ((ypos <= canvasHeight) && (startRes < maxwidth))
666 endx = startRes + cWidth - 1;
673 g.setFont(av.getFont());
674 g.setColor(Color.black);
676 if (av.getScaleLeftWrapped())
678 drawWestScale(g, startRes, endx, ypos);
681 if (av.getScaleRightWrapped())
683 g.translate(canvasWidth - labelWidthEast, 0);
684 drawEastScale(g, startRes, endx, ypos);
685 g.translate(-(canvasWidth - labelWidthEast), 0);
688 g.translate(labelWidthWest, 0);
690 if (av.getScaleAboveWrapped())
692 drawNorthScale(g, startRes, endx, ypos);
695 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
697 g.setColor(Color.blue);
699 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
701 Iterator<Integer> it = hidden.getBoundedStartIterator(startRes,
705 res = it.next() - startRes;
708 { res * charWidth - charHeight / 4,
709 res * charWidth + charHeight / 4, res * charWidth },
711 { ypos - (charHeight / 2), ypos - (charHeight / 2),
712 ypos - (charHeight / 2) + 8 }, 3);
716 // When printing we have an extra clipped region,
717 // the Printable page which we need to account for here
718 Shape clip = g.getClip();
722 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
726 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
727 (int) clip.getBounds().getHeight());
730 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
732 if (av.isShowAnnotation())
734 g.translate(0, cHeight + ypos + 3);
735 if (annotations == null)
737 annotations = new AnnotationPanel(av);
740 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
742 g.translate(0, -cHeight - ypos - 3);
745 g.translate(-labelWidthWest, 0);
747 ypos += cHeight + annotationHeight + hgap;
754 * Draw a selection group over a wrapped alignment
756 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
758 int canvasHeight, int startRes)
760 // height gap above each panel
761 int hgap = charHeight;
762 if (av.getScaleAboveWrapped())
767 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
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(labelWidthWest, 0);
796 drawUnwrappedSelection(g, group, startx, endx, 0,
797 av.getAlignment().getHeight() - 1,
800 g.translate(-labelWidthWest, 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
832 * Graphics object to draw with
834 * offset of the first column in the visible region (0..)
836 * offset of the last column in the visible region (0..)
838 * offset of the first sequence in the visible region (0..)
840 * offset of the last sequence in the visible region (0..)
842 * vertical offset at which to draw (for wrapped alignments)
844 public void drawPanel(Graphics g1, final int startRes, final int endRes,
845 final int startSeq, final int endSeq, final int yOffset)
848 if (!av.hasHiddenColumns())
850 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
858 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
859 VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
860 .getVisibleBlocksIterator(startRes, endRes, true);
862 while (regions.hasNext())
864 int[] region = regions.next();
865 blockEnd = region[1];
866 blockStart = region[0];
869 * draw up to just before the next hidden region, or the end of
870 * the visible region, whichever comes first
872 g1.translate(screenY * charWidth, 0);
874 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
877 * draw the downline of the hidden column marker (ScalePanel draws the
878 * triangle on top) if we reached it
880 if (av.getShowHiddenMarkers()
881 && (regions.hasNext() || regions.endsAtHidden()))
883 g1.setColor(Color.blue);
885 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
886 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
887 (endSeq - startSeq + 1) * charHeight + yOffset);
890 g1.translate(-screenY * charWidth, 0);
891 screenY += blockEnd - blockStart + 1;
898 * Draws a region of the visible alignment
902 * offset of the first column in the visible region (0..)
904 * offset of the last column in the visible region (0..)
906 * offset of the first sequence in the visible region (0..)
908 * offset of the last sequence in the visible region (0..)
910 * vertical offset at which to draw (for wrapped alignments)
912 private void draw(Graphics g, int startRes, int endRes, int startSeq,
913 int endSeq, int offset)
915 g.setFont(av.getFont());
916 seqRdr.prepare(g, av.isRenderGaps());
920 // / First draw the sequences
921 // ///////////////////////////
922 for (int i = startSeq; i <= endSeq; i++)
924 nextSeq = av.getAlignment().getSequenceAt(i);
927 // occasionally, a race condition occurs such that the alignment row is
931 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
932 startRes, endRes, offset + ((i - startSeq) * charHeight));
934 if (av.isShowSequenceFeatures())
936 fr.drawSequence(g, nextSeq, startRes, endRes,
937 offset + ((i - startSeq) * charHeight), false);
941 * highlight search Results once sequence has been drawn
943 if (av.hasSearchResults())
945 SearchResultsI searchResults = av.getSearchResults();
946 int[] visibleResults = searchResults.getResults(nextSeq,
948 if (visibleResults != null)
950 for (int r = 0; r < visibleResults.length; r += 2)
952 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
953 visibleResults[r + 1], (visibleResults[r] - startRes)
955 + ((i - startSeq) * charHeight));
960 if (av.cursorMode && cursorY == i && cursorX >= startRes
961 && cursorX <= endRes)
963 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
964 offset + ((i - startSeq) * charHeight));
968 if (av.getSelectionGroup() != null
969 || av.getAlignment().getGroups().size() > 0)
971 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
976 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
977 int startSeq, int endSeq, int offset)
979 Graphics2D g = (Graphics2D) g1;
981 // ///////////////////////////////////
982 // Now outline any areas if necessary
983 // ///////////////////////////////////
985 SequenceGroup group = null;
988 if (av.getAlignment().getGroups().size() > 0)
990 group = av.getAlignment().getGroups().get(0);
996 g.setStroke(new BasicStroke());
997 g.setColor(group.getOutlineColour());
1001 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1006 g.setStroke(new BasicStroke());
1008 if (groupIndex >= av.getAlignment().getGroups().size())
1013 group = av.getAlignment().getGroups().get(groupIndex);
1015 } while (groupIndex < av.getAlignment().getGroups().size());
1023 * Draw the selection group as a separate image and overlay
1025 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1026 int startSeq, int endSeq)
1028 // get a new image of the correct size
1029 BufferedImage selectionImage = setupImage();
1031 if (selectionImage == null)
1036 SequenceGroup group = av.getSelectionGroup();
1043 // set up drawing colour
1044 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1046 setupSelectionGroup(g, selectionImage);
1048 if (!av.getWrapAlignment())
1050 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1055 drawWrappedSelection(g, group, getWidth(), getHeight(),
1056 av.getRanges().getStartRes());
1060 return selectionImage;
1064 * Set up graphics for selection group
1066 private void setupSelectionGroup(Graphics2D g,
1067 BufferedImage selectionImage)
1069 // set background to transparent
1070 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1071 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1073 // set up foreground to draw red dashed line
1074 g.setComposite(AlphaComposite.Src);
1075 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1076 BasicStroke.JOIN_ROUND, 3f, new float[]
1078 g.setColor(Color.RED);
1082 * Draw a selection group over an unwrapped alignment
1083 * @param g graphics object to draw with
1084 * @param group selection group
1085 * @param startRes start residue of area to draw
1086 * @param endRes end residue of area to draw
1087 * @param startSeq start sequence of area to draw
1088 * @param endSeq end sequence of area to draw
1089 * @param offset vertical offset (used when called from wrapped alignment code)
1091 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1092 int startRes, int endRes, int startSeq, int endSeq, int offset)
1094 if (!av.hasHiddenColumns())
1096 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1101 // package into blocks of visible columns
1106 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1107 VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
1108 .getVisibleBlocksIterator(startRes, endRes, true);
1109 while (regions.hasNext())
1111 int[] region = regions.next();
1112 blockEnd = region[1];
1113 blockStart = region[0];
1115 g.translate(screenY * charWidth, 0);
1116 drawPartialGroupOutline(g, group,
1117 blockStart, blockEnd, startSeq, endSeq, offset);
1119 g.translate(-screenY * charWidth, 0);
1120 screenY += blockEnd - blockStart + 1;
1126 * Draw the selection group as a separate image and overlay
1128 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1129 int startRes, int endRes, int startSeq, int endSeq,
1132 int visWidth = (endRes - startRes + 1) * charWidth;
1136 boolean inGroup = false;
1144 for (i = startSeq; i <= endSeq; i++)
1146 // position of start residue of group relative to startRes, in pixels
1147 sx = (group.getStartRes() - startRes) * charWidth;
1149 // width of group in pixels
1150 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1153 sy = verticalOffset + (i - startSeq) * charHeight;
1155 if (sx + xwidth < 0 || sx > visWidth)
1160 if ((sx <= (endRes - startRes) * charWidth)
1161 && group.getSequences(null)
1162 .contains(av.getAlignment().getSequenceAt(i)))
1164 if ((bottom == -1) && !group.getSequences(null)
1165 .contains(av.getAlignment().getSequenceAt(i + 1)))
1167 bottom = sy + charHeight;
1172 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1173 .contains(av.getAlignment().getSequenceAt(i - 1)))
1186 // if start position is visible, draw vertical line to left of
1188 if (sx >= 0 && sx < visWidth)
1190 g.drawLine(sx, oldY, sx, sy);
1193 // if end position is visible, draw vertical line to right of
1195 if (sx + xwidth < visWidth)
1197 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1206 // don't let width extend beyond current block, or group extent
1208 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1210 xwidth = (endRes - startRes + 1) * charWidth - sx;
1213 // draw horizontal line at top of group
1216 g.drawLine(sx, top, sx + xwidth, top);
1220 // draw horizontal line at bottom of group
1223 g.drawLine(sx, bottom, sx + xwidth, bottom);
1234 sy = verticalOffset + ((i - startSeq) * charHeight);
1235 if (sx >= 0 && sx < visWidth)
1237 g.drawLine(sx, oldY, sx, sy);
1240 if (sx + xwidth < visWidth)
1242 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1251 if (sx + xwidth > visWidth)
1255 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1257 xwidth = (endRes - startRes + 1) * charWidth;
1262 g.drawLine(sx, top, sx + xwidth, top);
1268 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1277 * Highlights search results in the visible region by rendering as white text
1278 * on a black background. Any previous highlighting is removed. Answers true
1279 * if any highlight was left on the visible alignment (so status bar should be
1280 * set to match), else false.
1282 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1283 * alignment had to be scrolled to show the highlighted region, then it should
1284 * be fully redrawn, otherwise a fast paint can be performed. This argument
1285 * could be removed if fast paint of scrolled wrapped alignment is coded in
1286 * future (JAL-2609).
1289 * @param noFastPaint
1292 public boolean highlightSearchResults(SearchResultsI results,
1293 boolean noFastPaint)
1299 boolean wrapped = av.getWrapAlignment();
1303 fastPaint = !noFastPaint;
1304 fastpainting = fastPaint;
1309 * to avoid redrawing the whole visible region, we instead
1310 * redraw just the minimal regions to remove previous highlights
1313 SearchResultsI previous = av.getSearchResults();
1314 av.setSearchResults(results);
1315 boolean redrawn = false;
1316 boolean drawn = false;
1319 redrawn = drawMappedPositionsWrapped(previous);
1320 drawn = drawMappedPositionsWrapped(results);
1325 redrawn = drawMappedPositions(previous);
1326 drawn = drawMappedPositions(results);
1331 * if highlights were either removed or added, repaint
1339 * return true only if highlights were added
1345 fastpainting = false;
1350 * Redraws the minimal rectangle in the visible region (if any) that includes
1351 * mapped positions of the given search results. Whether or not positions are
1352 * highlighted depends on the SearchResults set on the Viewport. This allows
1353 * this method to be called to either clear or set highlighting. Answers true
1354 * if any positions were drawn (in which case a repaint is still required),
1360 protected boolean drawMappedPositions(SearchResultsI results)
1362 if (results == null)
1368 * calculate the minimal rectangle to redraw that
1369 * includes both new and existing search results
1371 int firstSeq = Integer.MAX_VALUE;
1373 int firstCol = Integer.MAX_VALUE;
1375 boolean matchFound = false;
1377 ViewportRanges ranges = av.getRanges();
1378 int firstVisibleColumn = ranges.getStartRes();
1379 int lastVisibleColumn = ranges.getEndRes();
1380 AlignmentI alignment = av.getAlignment();
1381 if (av.hasHiddenColumns())
1383 firstVisibleColumn = alignment.getHiddenColumns()
1384 .adjustForHiddenColumns(firstVisibleColumn);
1385 lastVisibleColumn = alignment.getHiddenColumns()
1386 .adjustForHiddenColumns(lastVisibleColumn);
1389 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1390 .getEndSeq(); seqNo++)
1392 SequenceI seq = alignment.getSequenceAt(seqNo);
1394 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1396 if (visibleResults != null)
1398 for (int i = 0; i < visibleResults.length - 1; i += 2)
1400 int firstMatchedColumn = visibleResults[i];
1401 int lastMatchedColumn = visibleResults[i + 1];
1402 if (firstMatchedColumn <= lastVisibleColumn
1403 && lastMatchedColumn >= firstVisibleColumn)
1406 * found a search results match in the visible region -
1407 * remember the first and last sequence matched, and the first
1408 * and last visible columns in the matched positions
1411 firstSeq = Math.min(firstSeq, seqNo);
1412 lastSeq = Math.max(lastSeq, seqNo);
1413 firstMatchedColumn = Math.max(firstMatchedColumn,
1414 firstVisibleColumn);
1415 lastMatchedColumn = Math.min(lastMatchedColumn,
1417 firstCol = Math.min(firstCol, firstMatchedColumn);
1418 lastCol = Math.max(lastCol, lastMatchedColumn);
1426 if (av.hasHiddenColumns())
1428 firstCol = alignment.getHiddenColumns()
1429 .findColumnPosition(firstCol);
1430 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1432 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1433 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1434 gg.translate(transX, transY);
1435 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1436 gg.translate(-transX, -transY);
1443 public void propertyChange(PropertyChangeEvent evt)
1445 String eventName = evt.getPropertyName();
1447 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1452 else if (av.getWrapAlignment())
1454 if (eventName.equals(ViewportRanges.STARTRES))
1462 if (eventName.equals(ViewportRanges.STARTRES))
1464 // Make sure we're not trying to draw a panel
1465 // larger than the visible window
1466 ViewportRanges vpRanges = av.getRanges();
1467 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1468 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1469 if (scrollX > range)
1473 else if (scrollX < -range)
1479 // Both scrolling and resizing change viewport ranges: scrolling changes
1480 // both start and end points, but resize only changes end values.
1481 // Here we only want to fastpaint on a scroll, with resize using a normal
1482 // paint, so scroll events are identified as changes to the horizontal or
1483 // vertical start value.
1484 if (eventName.equals(ViewportRanges.STARTRES))
1486 // scroll - startres and endres both change
1487 fastPaint(scrollX, 0);
1489 else if (eventName.equals(ViewportRanges.STARTSEQ))
1492 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1498 * Redraws any positions in the search results in the visible region of a
1499 * wrapped alignment. Any highlights are drawn depending on the search results
1500 * set on the Viewport, not the <code>results</code> argument. This allows
1501 * this method to be called either to clear highlights (passing the previous
1502 * search results), or to draw new highlights.
1507 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1509 if (results == null)
1514 boolean matchFound = false;
1516 int wrappedWidth = av.getWrappedWidth();
1517 int wrappedHeight = getRepeatHeightWrapped();
1519 ViewportRanges ranges = av.getRanges();
1520 int canvasHeight = getHeight();
1521 int repeats = canvasHeight / wrappedHeight;
1522 if (canvasHeight / wrappedHeight > 0)
1527 int firstVisibleColumn = ranges.getStartRes();
1528 int lastVisibleColumn = ranges.getStartRes() + repeats
1529 * ranges.getViewportWidth() - 1;
1531 AlignmentI alignment = av.getAlignment();
1532 if (av.hasHiddenColumns())
1534 firstVisibleColumn = alignment.getHiddenColumns()
1535 .adjustForHiddenColumns(firstVisibleColumn);
1536 lastVisibleColumn = alignment.getHiddenColumns()
1537 .adjustForHiddenColumns(lastVisibleColumn);
1540 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1542 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1543 .getEndSeq(); seqNo++)
1545 SequenceI seq = alignment.getSequenceAt(seqNo);
1547 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1549 if (visibleResults != null)
1551 for (int i = 0; i < visibleResults.length - 1; i += 2)
1553 int firstMatchedColumn = visibleResults[i];
1554 int lastMatchedColumn = visibleResults[i + 1];
1555 if (firstMatchedColumn <= lastVisibleColumn
1556 && lastMatchedColumn >= firstVisibleColumn)
1559 * found a search results match in the visible region
1561 firstMatchedColumn = Math.max(firstMatchedColumn,
1562 firstVisibleColumn);
1563 lastMatchedColumn = Math.min(lastMatchedColumn,
1567 * draw each mapped position separately (as contiguous positions may
1568 * wrap across lines)
1570 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1572 int displayColumn = mappedPos;
1573 if (av.hasHiddenColumns())
1575 displayColumn = alignment.getHiddenColumns()
1576 .findColumnPosition(displayColumn);
1580 * transX: offset from left edge of canvas to residue position
1582 int transX = labelWidthWest
1583 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1584 * av.getCharWidth();
1587 * transY: offset from top edge of canvas to residue position
1589 int transY = gapHeight;
1590 transY += (displayColumn - ranges.getStartRes())
1591 / wrappedWidth * wrappedHeight;
1592 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1595 * yOffset is from graphics origin to start of visible region
1597 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1598 if (transY < getHeight())
1601 gg.translate(transX, transY);
1602 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1604 gg.translate(-transX, -transY);
1616 * Answers the height in pixels of a repeating section of the wrapped
1617 * alignment, including space above, scale above if shown, sequences, and
1618 * annotation panel if shown
1622 protected int getRepeatHeightWrapped()
1624 // gap (and maybe scale) above
1625 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1628 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1630 // add annotations panel height if shown
1631 repeatHeight += getAnnotationHeight();
1633 return repeatHeight;