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);
855 final int screenYMax = endRes - startRes;
856 int blockStart = startRes;
857 int blockEnd = endRes; // equals blockStart + screenYMax - screenY;
859 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
860 VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
861 .getVisibleBlocksIterator(startRes, endRes, true);// hidden.iterator();
862 while (regions.hasNext())
864 int[] region = regions.next();
865 blockEnd = region[1];
866 blockStart = region[0];
867 /* int hideStart = region[0];
868 int hideEnd = region[1];
870 if (hideStart <= blockStart)
872 blockStart += (hideEnd - hideStart) + 1; // convert startRes to an
874 blockEnd += (hideEnd - hideStart) + 1;
879 * draw up to just before the next hidden region, or the end of
880 * the visible region, whichever comes first
882 // blockEnd = Math.min(hideStart - 1,
884 g1.translate(screenY * charWidth, 0);
886 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
889 * draw the downline of the hidden column marker (ScalePanel draws the
890 * triangle on top) if we reached it
892 if (av.getShowHiddenMarkers()
893 && (regions.hasNext() || regions.endsAtHidden()))// blockEnd ==
896 g1.setColor(Color.blue);
898 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
899 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
900 (endSeq - startSeq + 1) * charHeight + yOffset);
903 g1.translate(-screenY * charWidth, 0);
904 screenY += blockEnd - blockStart + 1;
905 /* blockStart = hideEnd + 1;
906 blockEnd = blockStart + screenYMax - screenY;
908 if (screenY > screenYMax)
910 // already rendered last block
915 /* if (screenY <= screenYMax)
917 // remaining visible region to render
918 blockEnd = blockStart + screenYMax - screenY;
919 g1.translate(screenY * charWidth, 0);
920 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
922 g1.translate(-screenY * charWidth, 0);
929 * Draws a region of the visible alignment
933 * offset of the first column in the visible region (0..)
935 * offset of the last column in the visible region (0..)
937 * offset of the first sequence in the visible region (0..)
939 * offset of the last sequence in the visible region (0..)
941 * vertical offset at which to draw (for wrapped alignments)
943 private void draw(Graphics g, int startRes, int endRes, int startSeq,
944 int endSeq, int offset)
946 g.setFont(av.getFont());
947 seqRdr.prepare(g, av.isRenderGaps());
951 // / First draw the sequences
952 // ///////////////////////////
953 for (int i = startSeq; i <= endSeq; i++)
955 nextSeq = av.getAlignment().getSequenceAt(i);
958 // occasionally, a race condition occurs such that the alignment row is
962 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
963 startRes, endRes, offset + ((i - startSeq) * charHeight));
965 if (av.isShowSequenceFeatures())
967 fr.drawSequence(g, nextSeq, startRes, endRes,
968 offset + ((i - startSeq) * charHeight), false);
972 * highlight search Results once sequence has been drawn
974 if (av.hasSearchResults())
976 SearchResultsI searchResults = av.getSearchResults();
977 int[] visibleResults = searchResults.getResults(nextSeq,
979 if (visibleResults != null)
981 for (int r = 0; r < visibleResults.length; r += 2)
983 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
984 visibleResults[r + 1], (visibleResults[r] - startRes)
986 + ((i - startSeq) * charHeight));
991 if (av.cursorMode && cursorY == i && cursorX >= startRes
992 && cursorX <= endRes)
994 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
995 offset + ((i - startSeq) * charHeight));
999 if (av.getSelectionGroup() != null
1000 || av.getAlignment().getGroups().size() > 0)
1002 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1007 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1008 int startSeq, int endSeq, int offset)
1010 Graphics2D g = (Graphics2D) g1;
1012 // ///////////////////////////////////
1013 // Now outline any areas if necessary
1014 // ///////////////////////////////////
1016 SequenceGroup group = null;
1017 int groupIndex = -1;
1019 if (av.getAlignment().getGroups().size() > 0)
1021 group = av.getAlignment().getGroups().get(0);
1027 g.setStroke(new BasicStroke());
1028 g.setColor(group.getOutlineColour());
1032 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1037 g.setStroke(new BasicStroke());
1039 if (groupIndex >= av.getAlignment().getGroups().size())
1044 group = av.getAlignment().getGroups().get(groupIndex);
1046 } while (groupIndex < av.getAlignment().getGroups().size());
1054 * Draw the selection group as a separate image and overlay
1056 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1057 int startSeq, int endSeq)
1059 // get a new image of the correct size
1060 BufferedImage selectionImage = setupImage();
1062 if (selectionImage == null)
1067 SequenceGroup group = av.getSelectionGroup();
1074 // set up drawing colour
1075 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1077 setupSelectionGroup(g, selectionImage);
1079 if (!av.getWrapAlignment())
1081 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1086 drawWrappedSelection(g, group, getWidth(), getHeight(),
1087 av.getRanges().getStartRes());
1091 return selectionImage;
1095 * Set up graphics for selection group
1097 private void setupSelectionGroup(Graphics2D g,
1098 BufferedImage selectionImage)
1100 // set background to transparent
1101 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1102 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1104 // set up foreground to draw red dashed line
1105 g.setComposite(AlphaComposite.Src);
1106 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1107 BasicStroke.JOIN_ROUND, 3f, new float[]
1109 g.setColor(Color.RED);
1113 * Draw a selection group over an unwrapped alignment
1114 * @param g graphics object to draw with
1115 * @param group selection group
1116 * @param startRes start residue of area to draw
1117 * @param endRes end residue of area to draw
1118 * @param startSeq start sequence of area to draw
1119 * @param endSeq end sequence of area to draw
1120 * @param offset vertical offset (used when called from wrapped alignment code)
1122 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1123 int startRes, int endRes, int startSeq, int endSeq, int offset)
1125 if (!av.hasHiddenColumns())
1127 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1132 // package into blocks of visible columns
1134 int blockStart = startRes;
1135 int blockEnd = endRes;
1137 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1138 Iterator<int[]> regions = hidden.iterator();
1139 while (regions.hasNext())
1141 int[] region = regions.next();
1142 int hideStart = region[0];
1143 int hideEnd = region[1];
1145 if (hideStart <= blockStart)
1147 blockStart += (hideEnd - hideStart) + 1;
1151 blockEnd = hideStart - 1;
1153 g.translate(screenY * charWidth, 0);
1154 drawPartialGroupOutline(g, group,
1155 blockStart, blockEnd, startSeq, endSeq, offset);
1157 g.translate(-screenY * charWidth, 0);
1158 screenY += blockEnd - blockStart + 1;
1159 blockStart = hideEnd + 1;
1161 if (screenY > (endRes - startRes))
1163 // already rendered last block
1168 if (screenY <= (endRes - startRes))
1170 // remaining visible region to render
1171 blockEnd = blockStart + (endRes - startRes) - screenY;
1172 g.translate(screenY * charWidth, 0);
1173 drawPartialGroupOutline(g, group,
1174 blockStart, blockEnd, startSeq, endSeq, offset);
1176 g.translate(-screenY * charWidth, 0);
1182 * Draw the selection group as a separate image and overlay
1184 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1185 int startRes, int endRes, int startSeq, int endSeq,
1188 int visWidth = (endRes - startRes + 1) * charWidth;
1192 boolean inGroup = false;
1200 for (i = startSeq; i <= endSeq; i++)
1202 // position of start residue of group relative to startRes, in pixels
1203 sx = (group.getStartRes() - startRes) * charWidth;
1205 // width of group in pixels
1206 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1209 sy = verticalOffset + (i - startSeq) * charHeight;
1211 if (sx + xwidth < 0 || sx > visWidth)
1216 if ((sx <= (endRes - startRes) * charWidth)
1217 && group.getSequences(null)
1218 .contains(av.getAlignment().getSequenceAt(i)))
1220 if ((bottom == -1) && !group.getSequences(null)
1221 .contains(av.getAlignment().getSequenceAt(i + 1)))
1223 bottom = sy + charHeight;
1228 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1229 .contains(av.getAlignment().getSequenceAt(i - 1)))
1242 // if start position is visible, draw vertical line to left of
1244 if (sx >= 0 && sx < visWidth)
1246 g.drawLine(sx, oldY, sx, sy);
1249 // if end position is visible, draw vertical line to right of
1251 if (sx + xwidth < visWidth)
1253 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1262 // don't let width extend beyond current block, or group extent
1264 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1266 xwidth = (endRes - startRes + 1) * charWidth - sx;
1269 // draw horizontal line at top of group
1272 g.drawLine(sx, top, sx + xwidth, top);
1276 // draw horizontal line at bottom of group
1279 g.drawLine(sx, bottom, sx + xwidth, bottom);
1290 sy = verticalOffset + ((i - startSeq) * charHeight);
1291 if (sx >= 0 && sx < visWidth)
1293 g.drawLine(sx, oldY, sx, sy);
1296 if (sx + xwidth < visWidth)
1298 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1307 if (sx + xwidth > visWidth)
1311 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1313 xwidth = (endRes - startRes + 1) * charWidth;
1318 g.drawLine(sx, top, sx + xwidth, top);
1324 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1333 * Highlights search results in the visible region by rendering as white text
1334 * on a black background. Any previous highlighting is removed. Answers true
1335 * if any highlight was left on the visible alignment (so status bar should be
1336 * set to match), else false.
1338 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1339 * alignment had to be scrolled to show the highlighted region, then it should
1340 * be fully redrawn, otherwise a fast paint can be performed. This argument
1341 * could be removed if fast paint of scrolled wrapped alignment is coded in
1342 * future (JAL-2609).
1345 * @param noFastPaint
1348 public boolean highlightSearchResults(SearchResultsI results,
1349 boolean noFastPaint)
1355 boolean wrapped = av.getWrapAlignment();
1359 fastPaint = !noFastPaint;
1360 fastpainting = fastPaint;
1365 * to avoid redrawing the whole visible region, we instead
1366 * redraw just the minimal regions to remove previous highlights
1369 SearchResultsI previous = av.getSearchResults();
1370 av.setSearchResults(results);
1371 boolean redrawn = false;
1372 boolean drawn = false;
1375 redrawn = drawMappedPositionsWrapped(previous);
1376 drawn = drawMappedPositionsWrapped(results);
1381 redrawn = drawMappedPositions(previous);
1382 drawn = drawMappedPositions(results);
1387 * if highlights were either removed or added, repaint
1395 * return true only if highlights were added
1401 fastpainting = false;
1406 * Redraws the minimal rectangle in the visible region (if any) that includes
1407 * mapped positions of the given search results. Whether or not positions are
1408 * highlighted depends on the SearchResults set on the Viewport. This allows
1409 * this method to be called to either clear or set highlighting. Answers true
1410 * if any positions were drawn (in which case a repaint is still required),
1416 protected boolean drawMappedPositions(SearchResultsI results)
1418 if (results == null)
1424 * calculate the minimal rectangle to redraw that
1425 * includes both new and existing search results
1427 int firstSeq = Integer.MAX_VALUE;
1429 int firstCol = Integer.MAX_VALUE;
1431 boolean matchFound = false;
1433 ViewportRanges ranges = av.getRanges();
1434 int firstVisibleColumn = ranges.getStartRes();
1435 int lastVisibleColumn = ranges.getEndRes();
1436 AlignmentI alignment = av.getAlignment();
1437 if (av.hasHiddenColumns())
1439 firstVisibleColumn = alignment.getHiddenColumns()
1440 .adjustForHiddenColumns(firstVisibleColumn);
1441 lastVisibleColumn = alignment.getHiddenColumns()
1442 .adjustForHiddenColumns(lastVisibleColumn);
1445 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1446 .getEndSeq(); seqNo++)
1448 SequenceI seq = alignment.getSequenceAt(seqNo);
1450 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1452 if (visibleResults != null)
1454 for (int i = 0; i < visibleResults.length - 1; i += 2)
1456 int firstMatchedColumn = visibleResults[i];
1457 int lastMatchedColumn = visibleResults[i + 1];
1458 if (firstMatchedColumn <= lastVisibleColumn
1459 && lastMatchedColumn >= firstVisibleColumn)
1462 * found a search results match in the visible region -
1463 * remember the first and last sequence matched, and the first
1464 * and last visible columns in the matched positions
1467 firstSeq = Math.min(firstSeq, seqNo);
1468 lastSeq = Math.max(lastSeq, seqNo);
1469 firstMatchedColumn = Math.max(firstMatchedColumn,
1470 firstVisibleColumn);
1471 lastMatchedColumn = Math.min(lastMatchedColumn,
1473 firstCol = Math.min(firstCol, firstMatchedColumn);
1474 lastCol = Math.max(lastCol, lastMatchedColumn);
1482 if (av.hasHiddenColumns())
1484 firstCol = alignment.getHiddenColumns()
1485 .findColumnPosition(firstCol);
1486 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1488 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1489 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1490 gg.translate(transX, transY);
1491 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1492 gg.translate(-transX, -transY);
1499 public void propertyChange(PropertyChangeEvent evt)
1501 String eventName = evt.getPropertyName();
1503 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1508 else if (av.getWrapAlignment())
1510 if (eventName.equals(ViewportRanges.STARTRES))
1518 if (eventName.equals(ViewportRanges.STARTRES))
1520 // Make sure we're not trying to draw a panel
1521 // larger than the visible window
1522 ViewportRanges vpRanges = av.getRanges();
1523 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1524 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1525 if (scrollX > range)
1529 else if (scrollX < -range)
1535 // Both scrolling and resizing change viewport ranges: scrolling changes
1536 // both start and end points, but resize only changes end values.
1537 // Here we only want to fastpaint on a scroll, with resize using a normal
1538 // paint, so scroll events are identified as changes to the horizontal or
1539 // vertical start value.
1540 if (eventName.equals(ViewportRanges.STARTRES))
1542 // scroll - startres and endres both change
1543 fastPaint(scrollX, 0);
1545 else if (eventName.equals(ViewportRanges.STARTSEQ))
1548 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1554 * Redraws any positions in the search results in the visible region of a
1555 * wrapped alignment. Any highlights are drawn depending on the search results
1556 * set on the Viewport, not the <code>results</code> argument. This allows
1557 * this method to be called either to clear highlights (passing the previous
1558 * search results), or to draw new highlights.
1563 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1565 if (results == null)
1570 boolean matchFound = false;
1572 int wrappedWidth = av.getWrappedWidth();
1573 int wrappedHeight = getRepeatHeightWrapped();
1575 ViewportRanges ranges = av.getRanges();
1576 int canvasHeight = getHeight();
1577 int repeats = canvasHeight / wrappedHeight;
1578 if (canvasHeight / wrappedHeight > 0)
1583 int firstVisibleColumn = ranges.getStartRes();
1584 int lastVisibleColumn = ranges.getStartRes() + repeats
1585 * ranges.getViewportWidth() - 1;
1587 AlignmentI alignment = av.getAlignment();
1588 if (av.hasHiddenColumns())
1590 firstVisibleColumn = alignment.getHiddenColumns()
1591 .adjustForHiddenColumns(firstVisibleColumn);
1592 lastVisibleColumn = alignment.getHiddenColumns()
1593 .adjustForHiddenColumns(lastVisibleColumn);
1596 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1598 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1599 .getEndSeq(); seqNo++)
1601 SequenceI seq = alignment.getSequenceAt(seqNo);
1603 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1605 if (visibleResults != null)
1607 for (int i = 0; i < visibleResults.length - 1; i += 2)
1609 int firstMatchedColumn = visibleResults[i];
1610 int lastMatchedColumn = visibleResults[i + 1];
1611 if (firstMatchedColumn <= lastVisibleColumn
1612 && lastMatchedColumn >= firstVisibleColumn)
1615 * found a search results match in the visible region
1617 firstMatchedColumn = Math.max(firstMatchedColumn,
1618 firstVisibleColumn);
1619 lastMatchedColumn = Math.min(lastMatchedColumn,
1623 * draw each mapped position separately (as contiguous positions may
1624 * wrap across lines)
1626 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1628 int displayColumn = mappedPos;
1629 if (av.hasHiddenColumns())
1631 displayColumn = alignment.getHiddenColumns()
1632 .findColumnPosition(displayColumn);
1636 * transX: offset from left edge of canvas to residue position
1638 int transX = labelWidthWest
1639 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1640 * av.getCharWidth();
1643 * transY: offset from top edge of canvas to residue position
1645 int transY = gapHeight;
1646 transY += (displayColumn - ranges.getStartRes())
1647 / wrappedWidth * wrappedHeight;
1648 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1651 * yOffset is from graphics origin to start of visible region
1653 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1654 if (transY < getHeight())
1657 gg.translate(transX, transY);
1658 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1660 gg.translate(-transX, -transY);
1672 * Answers the height in pixels of a repeating section of the wrapped
1673 * alignment, including space above, scale above if shown, sequences, and
1674 * annotation panel if shown
1678 protected int getRepeatHeightWrapped()
1680 // gap (and maybe scale) above
1681 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1684 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1686 // add annotations panel height if shown
1687 repeatHeight += getAnnotationHeight();
1689 return repeatHeight;