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 || img == null)
294 ViewportRanges ranges = av.getRanges();
295 int startRes = ranges.getStartRes();
296 int endRes = ranges.getEndRes();
297 int startSeq = ranges.getStartSeq();
298 int endSeq = ranges.getEndSeq();
302 gg.copyArea(horizontal * charWidth, vertical * charHeight,
303 img.getWidth(), img.getHeight(), -horizontal * charWidth,
304 -vertical * charHeight);
306 if (horizontal > 0) // scrollbar pulled right, image to the left
308 transX = (endRes - startRes - horizontal) * charWidth;
309 startRes = endRes - horizontal;
311 else if (horizontal < 0)
313 endRes = startRes - horizontal;
315 else if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
343 fastpainting = false;
347 public void paintComponent(Graphics g)
349 super.paintComponent(g);
353 ViewportRanges ranges = av.getRanges();
355 int width = getWidth();
356 int height = getHeight();
358 width -= (width % charWidth);
359 height -= (height % charHeight);
361 // selectImage is the selection group outline image
362 BufferedImage selectImage = drawSelectionGroup(
363 ranges.getStartRes(), ranges.getEndRes(),
364 ranges.getStartSeq(), ranges.getEndSeq());
366 if ((img != null) && (fastPaint
367 || (getVisibleRect().width != g.getClipBounds().width)
368 || (getVisibleRect().height != g.getClipBounds().height)))
370 BufferedImage lcimg = buildLocalImage(selectImage);
371 g.drawImage(lcimg, 0, 0, this);
374 else if ((width > 0) && (height > 0))
376 // img is a cached version of the last view we drew, if any
377 // if we have no img or the size has changed, make a new one
378 if (img == null || width != img.getWidth()
379 || height != img.getHeight())
386 gg = (Graphics2D) img.getGraphics();
387 gg.setFont(av.getFont());
392 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
393 RenderingHints.VALUE_ANTIALIAS_ON);
396 gg.setColor(Color.white);
397 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
399 if (av.getWrapAlignment())
401 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
405 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
406 ranges.getStartSeq(), ranges.getEndSeq(), 0);
409 // lcimg is a local *copy* of img which we'll draw selectImage on top of
410 BufferedImage lcimg = buildLocalImage(selectImage);
411 g.drawImage(lcimg, 0, 0, this);
416 * Draw an alignment panel for printing
419 * Graphics object to draw with
421 * start residue of print area
423 * end residue of print area
425 * start sequence of print area
427 * end sequence of print area
429 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
430 int startSeq, int endSeq)
432 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
434 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
436 if (selectImage != null)
438 ((Graphics2D) g1).setComposite(AlphaComposite
439 .getInstance(AlphaComposite.SRC_OVER));
440 g1.drawImage(selectImage, 0, 0, this);
445 * Draw a wrapped alignment panel for printing
448 * Graphics object to draw with
450 * width of drawing area
451 * @param canvasHeight
452 * height of drawing area
454 * start residue of print area
456 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
457 int canvasHeight, int startRes)
459 SequenceGroup group = av.getSelectionGroup();
461 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
465 BufferedImage selectImage = null;
468 selectImage = new BufferedImage(canvasWidth, canvasHeight,
469 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
470 } catch (OutOfMemoryError er)
473 System.err.println("Print image OutOfMemory Error.\n" + er);
474 new OOMWarning("Creating wrapped alignment image for printing", er);
476 if (selectImage != null)
478 Graphics2D g2 = selectImage.createGraphics();
479 setupSelectionGroup(g2, selectImage);
480 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
484 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
485 g.drawImage(selectImage, 0, 0, this);
492 * Make a local image by combining the cached image img
495 private BufferedImage buildLocalImage(BufferedImage selectImage)
497 // clone the cached image
498 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
500 Graphics2D g2d = lcimg.createGraphics();
501 g2d.drawImage(img, 0, 0, null);
503 // overlay selection group on lcimg
504 if (selectImage != null)
507 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
508 g2d.drawImage(selectImage, 0, 0, this);
516 * Set up a buffered image of the correct height and size for the sequence canvas
518 private BufferedImage setupImage()
520 BufferedImage lcimg = null;
522 int width = getWidth();
523 int height = getHeight();
525 width -= (width % charWidth);
526 height -= (height % charHeight);
528 if ((width < 1) || (height < 1))
535 lcimg = new BufferedImage(width, height,
536 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
537 } catch (OutOfMemoryError er)
541 "Group image OutOfMemory Redraw Error.\n" + er);
542 new OOMWarning("Creating alignment image for display", er);
551 * Returns the visible width of the canvas in residues, after allowing for
552 * East or West scales (if shown)
555 * the width in pixels (possibly including scales)
559 public int getWrappedCanvasWidth(int canvasWidth)
561 FontMetrics fm = getFontMetrics(av.getFont());
566 if (av.getScaleRightWrapped())
568 labelWidthEast = getLabelWidth(fm);
571 if (av.getScaleLeftWrapped())
573 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
577 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
581 * Returns a pixel width suitable for showing the largest sequence coordinate
582 * (end position) in the alignment. Returns 2 plus the number of decimal
583 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
588 protected int getLabelWidth(FontMetrics fm)
591 * find the biggest sequence end position we need to show
592 * (note this is not necessarily the sequence length)
595 AlignmentI alignment = av.getAlignment();
596 for (int i = 0; i < alignment.getHeight(); i++)
598 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
602 for (int i = maxWidth; i > 0; i /= 10)
607 return fm.stringWidth(ZEROS.substring(0, length));
617 * @param canvasHeight
622 private void drawWrappedPanel(Graphics g, int canvasWidth,
623 int canvasHeight, int startRes)
626 AlignmentI al = av.getAlignment();
629 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
631 FontMetrics fm = getFontMetrics(av.getFont());
632 labelWidth = getLabelWidth(fm);
635 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
636 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
638 int hgap = charHeight;
639 if (av.getScaleAboveWrapped())
644 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
645 int cHeight = av.getAlignment().getHeight() * charHeight;
647 av.setWrappedWidth(cWidth);
649 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
653 int maxwidth = av.getAlignment().getWidth();
655 if (av.hasHiddenColumns())
657 maxwidth = av.getAlignment().getHiddenColumns()
658 .findColumnPosition(maxwidth);
661 int annotationHeight = getAnnotationHeight();
663 while ((ypos <= canvasHeight) && (startRes < maxwidth))
665 endx = startRes + cWidth - 1;
672 g.setFont(av.getFont());
673 g.setColor(Color.black);
675 if (av.getScaleLeftWrapped())
677 drawWestScale(g, startRes, endx, ypos);
680 if (av.getScaleRightWrapped())
682 g.translate(canvasWidth - labelWidthEast, 0);
683 drawEastScale(g, startRes, endx, ypos);
684 g.translate(-(canvasWidth - labelWidthEast), 0);
687 g.translate(labelWidthWest, 0);
689 if (av.getScaleAboveWrapped())
691 drawNorthScale(g, startRes, endx, ypos);
694 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
696 g.setColor(Color.blue);
698 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
699 List<Integer> positions = hidden.findHiddenRegionPositions();
700 for (int pos : positions)
702 res = pos - startRes;
704 if (res < 0 || res > endx - startRes)
711 { res * charWidth - charHeight / 4,
712 res * charWidth + charHeight / 4, res * charWidth },
714 { ypos - (charHeight / 2), ypos - (charHeight / 2),
715 ypos - (charHeight / 2) + 8 },
721 // When printing we have an extra clipped region,
722 // the Printable page which we need to account for here
723 Shape clip = g.getClip();
727 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
731 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
732 (int) clip.getBounds().getHeight());
735 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
737 if (av.isShowAnnotation())
739 g.translate(0, cHeight + ypos + 3);
740 if (annotations == null)
742 annotations = new AnnotationPanel(av);
745 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
747 g.translate(0, -cHeight - ypos - 3);
750 g.translate(-labelWidthWest, 0);
752 ypos += cHeight + annotationHeight + hgap;
759 * Draw a selection group over a wrapped alignment
761 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
763 int canvasHeight, int startRes)
765 // height gap above each panel
766 int hgap = charHeight;
767 if (av.getScaleAboveWrapped())
772 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
774 int cHeight = av.getAlignment().getHeight() * charHeight;
776 int startx = startRes;
778 int ypos = hgap; // vertical offset
779 int maxwidth = av.getAlignment().getWidth();
781 if (av.hasHiddenColumns())
783 maxwidth = av.getAlignment().getHiddenColumns()
784 .findColumnPosition(maxwidth);
787 // chop the wrapped alignment extent up into panel-sized blocks and treat
788 // each block as if it were a block from an unwrapped alignment
789 while ((ypos <= canvasHeight) && (startx < maxwidth))
791 // set end value to be start + width, or maxwidth, whichever is smaller
792 endx = startx + cWidth - 1;
799 g.translate(labelWidthWest, 0);
801 drawUnwrappedSelection(g, group, startx, endx, 0,
802 av.getAlignment().getHeight() - 1,
805 g.translate(-labelWidthWest, 0);
807 // update vertical offset
808 ypos += cHeight + getAnnotationHeight() + hgap;
810 // update horizontal offset
815 int getAnnotationHeight()
817 if (!av.isShowAnnotation())
822 if (annotations == null)
824 annotations = new AnnotationPanel(av);
827 return annotations.adjustPanelHeight();
831 * Draws the visible region of the alignment on the graphics context. If there
832 * are hidden column markers in the visible region, then each sub-region
833 * between the markers is drawn separately, followed by the hidden column
837 * Graphics object to draw with
839 * offset of the first column in the visible region (0..)
841 * offset of the last column in the visible region (0..)
843 * offset of the first sequence in the visible region (0..)
845 * offset of the last sequence in the visible region (0..)
847 * vertical offset at which to draw (for wrapped alignments)
849 public void drawPanel(Graphics g1, final int startRes, final int endRes,
850 final int startSeq, final int endSeq, final int yOffset)
853 if (!av.hasHiddenColumns())
855 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
860 final int screenYMax = endRes - startRes;
861 int blockStart = startRes;
862 int blockEnd = endRes;
864 for (int[] region : av.getAlignment().getHiddenColumns()
865 .getHiddenColumnsCopy())
867 int hideStart = region[0];
868 int hideEnd = region[1];
870 if (hideStart <= blockStart)
872 blockStart += (hideEnd - hideStart) + 1;
877 * draw up to just before the next hidden region, or the end of
878 * the visible region, whichever comes first
880 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
883 g1.translate(screenY * charWidth, 0);
885 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
888 * draw the downline of the hidden column marker (ScalePanel draws the
889 * triangle on top) if we reached it
891 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
893 g1.setColor(Color.blue);
895 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
896 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
897 (endSeq - startSeq + 1) * charHeight + yOffset);
900 g1.translate(-screenY * charWidth, 0);
901 screenY += blockEnd - blockStart + 1;
902 blockStart = hideEnd + 1;
904 if (screenY > screenYMax)
906 // already rendered last block
911 if (screenY <= screenYMax)
913 // remaining visible region to render
914 blockEnd = blockStart + screenYMax - screenY;
915 g1.translate(screenY * charWidth, 0);
916 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
918 g1.translate(-screenY * charWidth, 0);
925 * Draws a region of the visible alignment
929 * offset of the first column in the visible region (0..)
931 * offset of the last column in the visible region (0..)
933 * offset of the first sequence in the visible region (0..)
935 * offset of the last sequence in the visible region (0..)
937 * vertical offset at which to draw (for wrapped alignments)
939 private void draw(Graphics g, int startRes, int endRes, int startSeq,
940 int endSeq, int offset)
942 g.setFont(av.getFont());
943 seqRdr.prepare(g, av.isRenderGaps());
947 // / First draw the sequences
948 // ///////////////////////////
949 for (int i = startSeq; i <= endSeq; i++)
951 nextSeq = av.getAlignment().getSequenceAt(i);
954 // occasionally, a race condition occurs such that the alignment row is
958 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
959 startRes, endRes, offset + ((i - startSeq) * charHeight));
961 if (av.isShowSequenceFeatures())
963 fr.drawSequence(g, nextSeq, startRes, endRes,
964 offset + ((i - startSeq) * charHeight), false);
968 * highlight search Results once sequence has been drawn
970 if (av.hasSearchResults())
972 SearchResultsI searchResults = av.getSearchResults();
973 int[] visibleResults = searchResults.getResults(nextSeq,
975 if (visibleResults != null)
977 for (int r = 0; r < visibleResults.length; r += 2)
979 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
980 visibleResults[r + 1], (visibleResults[r] - startRes)
982 + ((i - startSeq) * charHeight));
987 if (av.cursorMode && cursorY == i && cursorX >= startRes
988 && cursorX <= endRes)
990 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
991 offset + ((i - startSeq) * charHeight));
995 if (av.getSelectionGroup() != null
996 || av.getAlignment().getGroups().size() > 0)
998 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1003 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1004 int startSeq, int endSeq, int offset)
1006 Graphics2D g = (Graphics2D) g1;
1008 // ///////////////////////////////////
1009 // Now outline any areas if necessary
1010 // ///////////////////////////////////
1012 SequenceGroup group = null;
1013 int groupIndex = -1;
1015 if (av.getAlignment().getGroups().size() > 0)
1017 group = av.getAlignment().getGroups().get(0);
1023 g.setStroke(new BasicStroke());
1024 g.setColor(group.getOutlineColour());
1028 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1033 g.setStroke(new BasicStroke());
1035 if (groupIndex >= av.getAlignment().getGroups().size())
1040 group = av.getAlignment().getGroups().get(groupIndex);
1042 } while (groupIndex < av.getAlignment().getGroups().size());
1050 * Draw the selection group as a separate image and overlay
1052 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1053 int startSeq, int endSeq)
1055 // get a new image of the correct size
1056 BufferedImage selectionImage = setupImage();
1058 if (selectionImage == null)
1063 SequenceGroup group = av.getSelectionGroup();
1070 // set up drawing colour
1071 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1073 setupSelectionGroup(g, selectionImage);
1075 if (!av.getWrapAlignment())
1077 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1082 drawWrappedSelection(g, group, getWidth(), getHeight(),
1083 av.getRanges().getStartRes());
1087 return selectionImage;
1091 * Set up graphics for selection group
1093 private void setupSelectionGroup(Graphics2D g,
1094 BufferedImage selectionImage)
1096 // set background to transparent
1097 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1098 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1100 // set up foreground to draw red dashed line
1101 g.setComposite(AlphaComposite.Src);
1102 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1103 BasicStroke.JOIN_ROUND, 3f, new float[]
1105 g.setColor(Color.RED);
1109 * Draw a selection group over an unwrapped alignment
1110 * @param g graphics object to draw with
1111 * @param group selection group
1112 * @param startRes start residue of area to draw
1113 * @param endRes end residue of area to draw
1114 * @param startSeq start sequence of area to draw
1115 * @param endSeq end sequence of area to draw
1116 * @param offset vertical offset (used when called from wrapped alignment code)
1118 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1119 int startRes, int endRes, int startSeq, int endSeq, int offset)
1121 if (!av.hasHiddenColumns())
1123 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1128 // package into blocks of visible columns
1130 int blockStart = startRes;
1131 int blockEnd = endRes;
1133 for (int[] region : av.getAlignment().getHiddenColumns()
1134 .getHiddenColumnsCopy())
1136 int hideStart = region[0];
1137 int hideEnd = region[1];
1139 if (hideStart <= blockStart)
1141 blockStart += (hideEnd - hideStart) + 1;
1145 blockEnd = hideStart - 1;
1147 g.translate(screenY * charWidth, 0);
1148 drawPartialGroupOutline(g, group,
1149 blockStart, blockEnd, startSeq, endSeq, offset);
1151 g.translate(-screenY * charWidth, 0);
1152 screenY += blockEnd - blockStart + 1;
1153 blockStart = hideEnd + 1;
1155 if (screenY > (endRes - startRes))
1157 // already rendered last block
1162 if (screenY <= (endRes - startRes))
1164 // remaining visible region to render
1165 blockEnd = blockStart + (endRes - startRes) - screenY;
1166 g.translate(screenY * charWidth, 0);
1167 drawPartialGroupOutline(g, group,
1168 blockStart, blockEnd, startSeq, endSeq, offset);
1170 g.translate(-screenY * charWidth, 0);
1176 * Draw the selection group as a separate image and overlay
1178 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1179 int startRes, int endRes, int startSeq, int endSeq,
1182 int visWidth = (endRes - startRes + 1) * charWidth;
1186 boolean inGroup = false;
1194 for (i = startSeq; i <= endSeq; i++)
1196 // position of start residue of group relative to startRes, in pixels
1197 sx = (group.getStartRes() - startRes) * charWidth;
1199 // width of group in pixels
1200 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1203 sy = verticalOffset + (i - startSeq) * charHeight;
1205 if (sx + xwidth < 0 || sx > visWidth)
1210 if ((sx <= (endRes - startRes) * charWidth)
1211 && group.getSequences(null)
1212 .contains(av.getAlignment().getSequenceAt(i)))
1214 if ((bottom == -1) && !group.getSequences(null)
1215 .contains(av.getAlignment().getSequenceAt(i + 1)))
1217 bottom = sy + charHeight;
1222 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1223 .contains(av.getAlignment().getSequenceAt(i - 1)))
1236 // if start position is visible, draw vertical line to left of
1238 if (sx >= 0 && sx < visWidth)
1240 g.drawLine(sx, oldY, sx, sy);
1243 // if end position is visible, draw vertical line to right of
1245 if (sx + xwidth < visWidth)
1247 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1256 // don't let width extend beyond current block, or group extent
1258 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1260 xwidth = (endRes - startRes + 1) * charWidth - sx;
1263 // draw horizontal line at top of group
1266 g.drawLine(sx, top, sx + xwidth, top);
1270 // draw horizontal line at bottom of group
1273 g.drawLine(sx, bottom, sx + xwidth, bottom);
1284 sy = verticalOffset + ((i - startSeq) * charHeight);
1285 if (sx >= 0 && sx < visWidth)
1287 g.drawLine(sx, oldY, sx, sy);
1290 if (sx + xwidth < visWidth)
1292 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1301 if (sx + xwidth > visWidth)
1305 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1307 xwidth = (endRes - startRes + 1) * charWidth;
1312 g.drawLine(sx, top, sx + xwidth, top);
1318 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1327 * Highlights search results in the visible region by rendering as white text
1328 * on a black background. Any previous highlighting is removed. Answers true
1329 * if any highlight was left on the visible alignment (so status bar should be
1330 * set to match), else false.
1332 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1333 * alignment had to be scrolled to show the highlighted region, then it should
1334 * be fully redrawn, otherwise a fast paint can be performed. This argument
1335 * could be removed if fast paint of scrolled wrapped alignment is coded in
1336 * future (JAL-2609).
1339 * @param noFastPaint
1342 public boolean highlightSearchResults(SearchResultsI results,
1343 boolean noFastPaint)
1349 boolean wrapped = av.getWrapAlignment();
1353 fastPaint = !noFastPaint;
1354 fastpainting = fastPaint;
1359 * to avoid redrawing the whole visible region, we instead
1360 * redraw just the minimal regions to remove previous highlights
1363 SearchResultsI previous = av.getSearchResults();
1364 av.setSearchResults(results);
1365 boolean redrawn = false;
1366 boolean drawn = false;
1369 redrawn = drawMappedPositionsWrapped(previous);
1370 drawn = drawMappedPositionsWrapped(results);
1375 redrawn = drawMappedPositions(previous);
1376 drawn = drawMappedPositions(results);
1381 * if highlights were either removed or added, repaint
1389 * return true only if highlights were added
1395 fastpainting = false;
1400 * Redraws the minimal rectangle in the visible region (if any) that includes
1401 * mapped positions of the given search results. Whether or not positions are
1402 * highlighted depends on the SearchResults set on the Viewport. This allows
1403 * this method to be called to either clear or set highlighting. Answers true
1404 * if any positions were drawn (in which case a repaint is still required),
1410 protected boolean drawMappedPositions(SearchResultsI results)
1412 if (results == null)
1418 * calculate the minimal rectangle to redraw that
1419 * includes both new and existing search results
1421 int firstSeq = Integer.MAX_VALUE;
1423 int firstCol = Integer.MAX_VALUE;
1425 boolean matchFound = false;
1427 ViewportRanges ranges = av.getRanges();
1428 int firstVisibleColumn = ranges.getStartRes();
1429 int lastVisibleColumn = ranges.getEndRes();
1430 AlignmentI alignment = av.getAlignment();
1431 if (av.hasHiddenColumns())
1433 firstVisibleColumn = alignment.getHiddenColumns()
1434 .adjustForHiddenColumns(firstVisibleColumn);
1435 lastVisibleColumn = alignment.getHiddenColumns()
1436 .adjustForHiddenColumns(lastVisibleColumn);
1439 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1440 .getEndSeq(); seqNo++)
1442 SequenceI seq = alignment.getSequenceAt(seqNo);
1444 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1446 if (visibleResults != null)
1448 for (int i = 0; i < visibleResults.length - 1; i += 2)
1450 int firstMatchedColumn = visibleResults[i];
1451 int lastMatchedColumn = visibleResults[i + 1];
1452 if (firstMatchedColumn <= lastVisibleColumn
1453 && lastMatchedColumn >= firstVisibleColumn)
1456 * found a search results match in the visible region -
1457 * remember the first and last sequence matched, and the first
1458 * and last visible columns in the matched positions
1461 firstSeq = Math.min(firstSeq, seqNo);
1462 lastSeq = Math.max(lastSeq, seqNo);
1463 firstMatchedColumn = Math.max(firstMatchedColumn,
1464 firstVisibleColumn);
1465 lastMatchedColumn = Math.min(lastMatchedColumn,
1467 firstCol = Math.min(firstCol, firstMatchedColumn);
1468 lastCol = Math.max(lastCol, lastMatchedColumn);
1476 if (av.hasHiddenColumns())
1478 firstCol = alignment.getHiddenColumns()
1479 .findColumnPosition(firstCol);
1480 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1482 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1483 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1484 gg.translate(transX, transY);
1485 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1486 gg.translate(-transX, -transY);
1493 public void propertyChange(PropertyChangeEvent evt)
1495 String eventName = evt.getPropertyName();
1497 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1502 else if (av.getWrapAlignment())
1504 if (eventName.equals(ViewportRanges.STARTRES))
1512 if (eventName.equals(ViewportRanges.STARTRES))
1514 // Make sure we're not trying to draw a panel
1515 // larger than the visible window
1516 ViewportRanges vpRanges = av.getRanges();
1517 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1518 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1519 if (scrollX > range)
1523 else if (scrollX < -range)
1529 // Both scrolling and resizing change viewport ranges: scrolling changes
1530 // both start and end points, but resize only changes end values.
1531 // Here we only want to fastpaint on a scroll, with resize using a normal
1532 // paint, so scroll events are identified as changes to the horizontal or
1533 // vertical start value.
1534 if (eventName.equals(ViewportRanges.STARTRES))
1536 // scroll - startres and endres both change
1537 fastPaint(scrollX, 0);
1539 else if (eventName.equals(ViewportRanges.STARTSEQ))
1542 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1548 * Redraws any positions in the search results in the visible region of a
1549 * wrapped alignment. Any highlights are drawn depending on the search results
1550 * set on the Viewport, not the <code>results</code> argument. This allows
1551 * this method to be called either to clear highlights (passing the previous
1552 * search results), or to draw new highlights.
1557 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1559 if (results == null)
1564 boolean matchFound = false;
1566 int wrappedWidth = av.getWrappedWidth();
1567 int wrappedHeight = getRepeatHeightWrapped();
1569 ViewportRanges ranges = av.getRanges();
1570 int canvasHeight = getHeight();
1571 int repeats = canvasHeight / wrappedHeight;
1572 if (canvasHeight / wrappedHeight > 0)
1577 int firstVisibleColumn = ranges.getStartRes();
1578 int lastVisibleColumn = ranges.getStartRes() + repeats
1579 * ranges.getViewportWidth() - 1;
1581 AlignmentI alignment = av.getAlignment();
1582 if (av.hasHiddenColumns())
1584 firstVisibleColumn = alignment.getHiddenColumns()
1585 .adjustForHiddenColumns(firstVisibleColumn);
1586 lastVisibleColumn = alignment.getHiddenColumns()
1587 .adjustForHiddenColumns(lastVisibleColumn);
1590 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1592 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1593 .getEndSeq(); seqNo++)
1595 SequenceI seq = alignment.getSequenceAt(seqNo);
1597 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1599 if (visibleResults != null)
1601 for (int i = 0; i < visibleResults.length - 1; i += 2)
1603 int firstMatchedColumn = visibleResults[i];
1604 int lastMatchedColumn = visibleResults[i + 1];
1605 if (firstMatchedColumn <= lastVisibleColumn
1606 && lastMatchedColumn >= firstVisibleColumn)
1609 * found a search results match in the visible region
1611 firstMatchedColumn = Math.max(firstMatchedColumn,
1612 firstVisibleColumn);
1613 lastMatchedColumn = Math.min(lastMatchedColumn,
1617 * draw each mapped position separately (as contiguous positions may
1618 * wrap across lines)
1620 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1622 int displayColumn = mappedPos;
1623 if (av.hasHiddenColumns())
1625 displayColumn = alignment.getHiddenColumns()
1626 .findColumnPosition(displayColumn);
1630 * transX: offset from left edge of canvas to residue position
1632 int transX = labelWidthWest
1633 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1634 * av.getCharWidth();
1637 * transY: offset from top edge of canvas to residue position
1639 int transY = gapHeight;
1640 transY += (displayColumn - ranges.getStartRes())
1641 / wrappedWidth * wrappedHeight;
1642 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1645 * yOffset is from graphics origin to start of visible region
1647 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1648 if (transY < getHeight())
1651 gg.translate(transX, transY);
1652 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1654 gg.translate(-transX, -transY);
1666 * Answers the height in pixels of a repeating section of the wrapped
1667 * alignment, including space above, scale above if shown, sequences, and
1668 * annotation panel if shown
1672 protected int getRepeatHeightWrapped()
1674 // gap (and maybe scale) above
1675 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1678 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1680 // add annotations panel height if shown
1681 repeatHeight += getAnnotationHeight();
1683 return repeatHeight;