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.util.Comparison;
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.List;
47 import javax.swing.JComponent;
50 * The Swing component on which the alignment sequences, and annotations (if
51 * shown), are drawn. This includes scales above, left and right (if shown) in
52 * Wrapped mode, but not the scale above in Unwrapped mode.
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static final String ZEROS = "0000000000";
59 final FeatureRenderer fr;
69 private final SequenceRenderer seqRdr;
71 private boolean fastPaint = false;
73 private boolean fastpainting = false;
75 private AnnotationPanel annotations;
78 * measurements for drawing a wrapped alignment
80 private int labelWidthEast; // label right width in pixels if shown
82 private int labelWidthWest; // label left width in pixels if shown
84 private int wrappedSpaceAboveAlignment; // gap between widths
86 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
88 private int wrappedVisibleWidths; // number of wrapped widths displayed
90 private Graphics2D gg;
93 * Creates a new SeqCanvas object.
97 public SeqCanvas(AlignmentPanel ap)
100 fr = new FeatureRenderer(ap);
101 seqRdr = new SequenceRenderer(av);
102 setLayout(new BorderLayout());
103 PaintRefresher.Register(this, av.getSequenceSetId());
104 setBackground(Color.white);
106 av.getRanges().addPropertyChangeListener(this);
109 public SequenceRenderer getSequenceRenderer()
114 public FeatureRenderer getFeatureRenderer()
120 * Draws the scale above a region of a wrapped alignment, consisting of a
121 * column number every major interval (10 columns).
124 * the graphics context to draw on, positioned at the start (bottom
125 * left) of the line on which to draw any scale marks
127 * start alignment column (0..)
129 * end alignment column (0..)
131 * y offset to draw at
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
135 int charHeight = av.getCharHeight();
136 int charWidth = av.getCharWidth();
139 * white fill the scale space (for the fastPaint case)
141 g.setColor(Color.white);
142 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
143 charHeight * 3 / 2 + 2);
144 g.setColor(Color.black);
146 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
148 for (ScaleMark mark : marks)
150 int mpos = mark.column; // (i - startx - 1)
155 String mstring = mark.text;
161 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
165 * draw a tick mark below the column number, centred on the column;
166 * height of tick mark is 4 pixels less than half a character
168 int xpos = (mpos * charWidth) + (charWidth / 2);
169 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
175 * Draw the scale to the left or right of a wrapped alignment
178 * graphics context, positioned at the start of the scale to be drawn
180 * first column of wrapped width (0.. excluding any hidden columns)
182 * last column of wrapped width (0.. excluding any hidden columns)
184 * vertical offset at which to begin the scale
186 * if true, scale is left of residues, if false, scale is right
188 void drawVerticalScale(Graphics g, final int startx, final int endx,
189 final int ypos, final boolean left)
191 int charHeight = av.getCharHeight();
192 int charWidth = av.getCharWidth();
194 int yPos = ypos + charHeight;
198 if (av.hasHiddenColumns())
200 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
201 startX = hiddenColumns.adjustForHiddenColumns(startx);
202 endX = hiddenColumns.adjustForHiddenColumns(endx);
204 FontMetrics fm = getFontMetrics(av.getFont());
206 for (int i = 0; i < av.getAlignment().getHeight(); i++)
208 SequenceI seq = av.getAlignment().getSequenceAt(i);
211 * find sequence position of first non-gapped position -
212 * to the right if scale left, to the left if scale right
214 int index = left ? startX : endX;
216 while (index >= startX && index <= endX)
218 if (!Comparison.isGap(seq.getCharAt(index)))
220 value = seq.findPosition(index);
234 * white fill the space for the scale
236 g.setColor(Color.white);
237 int y = (yPos + (i * charHeight)) - (charHeight / 5);
238 // fillRect origin is top left of rectangle
239 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
245 * draw scale value, right justified within its width less half a
246 * character width padding on the right
248 int labelSpace = left ? labelWidthWest : labelWidthEast;
249 labelSpace -= charWidth / 2; // leave space to the right
250 String valueAsString = String.valueOf(value);
251 int labelLength = fm.stringWidth(valueAsString);
252 int xOffset = labelSpace - labelLength;
253 g.setColor(Color.black);
254 g.drawString(valueAsString, xOffset, y);
260 * Does a fast paint of an alignment in response to a scroll. Most of the
261 * visible region is simply copied and shifted, and then any newly visible
262 * columns or rows are drawn. The scroll may be horizontal or vertical, but
263 * not both at once. Scrolling may be the result of
265 * <li>dragging a scroll bar</li>
266 * <li>clicking in the scroll bar</li>
267 * <li>scrolling by trackpad, middle mouse button, or other device</li>
268 * <li>by moving the box in the Overview window</li>
269 * <li>programmatically to make a highlighted position visible</li>
273 * columns to shift right (positive) or left (negative)
275 * rows to shift down (positive) or up (negative)
277 public void fastPaint(int horizontal, int vertical)
279 if (fastpainting || gg == null || img == null)
288 int charHeight = av.getCharHeight();
289 int charWidth = av.getCharWidth();
291 ViewportRanges ranges = av.getRanges();
292 int startRes = ranges.getStartRes();
293 int endRes = ranges.getEndRes();
294 int startSeq = ranges.getStartSeq();
295 int endSeq = ranges.getEndSeq();
299 gg.copyArea(horizontal * charWidth, vertical * charHeight,
300 img.getWidth(), img.getHeight(), -horizontal * charWidth,
301 -vertical * charHeight);
303 if (horizontal > 0) // scrollbar pulled right, image to the left
305 transX = (endRes - startRes - horizontal) * charWidth;
306 startRes = endRes - horizontal;
308 else if (horizontal < 0)
310 endRes = startRes - horizontal;
313 if (vertical > 0) // scroll down
315 startSeq = endSeq - vertical;
317 if (startSeq < ranges.getStartSeq())
318 { // ie scrolling too fast, more than a page at a time
319 startSeq = ranges.getStartSeq();
323 transY = img.getHeight() - ((vertical + 1) * charHeight);
326 else if (vertical < 0)
328 endSeq = startSeq - vertical;
330 if (endSeq > ranges.getEndSeq())
332 endSeq = ranges.getEndSeq();
336 gg.translate(transX, transY);
337 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
338 gg.translate(-transX, -transY);
343 fastpainting = false;
348 public void paintComponent(Graphics g)
350 super.paintComponent(g);
352 int charHeight = av.getCharHeight();
353 int charWidth = av.getCharWidth();
355 ViewportRanges ranges = av.getRanges();
357 int width = getWidth();
358 int height = getHeight();
360 width -= (width % charWidth);
361 height -= (height % charHeight);
363 // selectImage is the selection group outline image
364 BufferedImage selectImage = drawSelectionGroup(
365 ranges.getStartRes(), ranges.getEndRes(),
366 ranges.getStartSeq(), ranges.getEndSeq());
368 if ((img != null) && (fastPaint
369 || (getVisibleRect().width != g.getClipBounds().width)
370 || (getVisibleRect().height != g.getClipBounds().height)))
372 BufferedImage lcimg = buildLocalImage(selectImage);
373 g.drawImage(lcimg, 0, 0, this);
376 else if ((width > 0) && (height > 0))
378 // img is a cached version of the last view we drew, if any
379 // if we have no img or the size has changed, make a new one
380 if (img == null || width != img.getWidth()
381 || height != img.getHeight())
388 gg = (Graphics2D) img.getGraphics();
389 gg.setFont(av.getFont());
394 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
395 RenderingHints.VALUE_ANTIALIAS_ON);
398 gg.setColor(Color.white);
399 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
401 if (av.getWrapAlignment())
403 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
407 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
408 ranges.getStartSeq(), ranges.getEndSeq(), 0);
411 // lcimg is a local *copy* of img which we'll draw selectImage on top of
412 BufferedImage lcimg = buildLocalImage(selectImage);
413 g.drawImage(lcimg, 0, 0, this);
416 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
417 ranges.getStartSeq(), ranges.getEndSeq());
421 * Draw an alignment panel for printing
424 * Graphics object to draw with
426 * start residue of print area
428 * end residue of print area
430 * start sequence of print area
432 * end sequence of print area
434 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
435 int startSeq, int endSeq)
437 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
439 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
441 if (selectImage != null)
443 ((Graphics2D) g1).setComposite(AlphaComposite
444 .getInstance(AlphaComposite.SRC_OVER));
445 g1.drawImage(selectImage, 0, 0, this);
450 * Draw a wrapped alignment panel for printing
453 * Graphics object to draw with
455 * width of drawing area
456 * @param canvasHeight
457 * height of drawing area
459 * start residue of print area
461 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
462 int canvasHeight, int startRes)
464 SequenceGroup group = av.getSelectionGroup();
466 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
470 BufferedImage selectImage = null;
473 selectImage = new BufferedImage(canvasWidth, canvasHeight,
474 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
475 } catch (OutOfMemoryError er)
478 System.err.println("Print image OutOfMemory Error.\n" + er);
479 new OOMWarning("Creating wrapped alignment image for printing", er);
481 if (selectImage != null)
483 Graphics2D g2 = selectImage.createGraphics();
484 setupSelectionGroup(g2, selectImage);
485 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
489 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
490 g.drawImage(selectImage, 0, 0, this);
497 * Make a local image by combining the cached image img
500 private BufferedImage buildLocalImage(BufferedImage selectImage)
502 // clone the cached image
503 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
505 Graphics2D g2d = lcimg.createGraphics();
506 g2d.drawImage(img, 0, 0, null);
508 // overlay selection group on lcimg
509 if (selectImage != null)
512 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
513 g2d.drawImage(selectImage, 0, 0, this);
522 * Set up a buffered image of the correct height and size for the sequence canvas
524 private BufferedImage setupImage()
526 BufferedImage lcimg = null;
528 int charWidth = av.getCharWidth();
529 int charHeight = av.getCharHeight();
531 int width = getWidth();
532 int height = getHeight();
534 width -= (width % charWidth);
535 height -= (height % charHeight);
537 if ((width < 1) || (height < 1))
544 lcimg = new BufferedImage(width, height,
545 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
546 } catch (OutOfMemoryError er)
550 "Group image OutOfMemory Redraw Error.\n" + er);
551 new OOMWarning("Creating alignment image for display", er);
560 * Returns the visible width of the canvas in residues, after allowing for
561 * East or West scales (if shown)
564 * the width in pixels (possibly including scales)
568 public int getWrappedCanvasWidth(int canvasWidth)
570 int charWidth = av.getCharWidth();
572 FontMetrics fm = getFontMetrics(av.getFont());
576 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
578 labelWidth = getLabelWidth(fm);
581 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
583 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
585 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
589 * Returns a pixel width sufficient to show the largest sequence coordinate
590 * (end position) in the alignment, calculated as the FontMetrics width of
591 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
592 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
593 * half a character width space on either side.
598 protected int getLabelWidth(FontMetrics fm)
601 * find the biggest sequence end position we need to show
602 * (note this is not necessarily the sequence length)
605 AlignmentI alignment = av.getAlignment();
606 for (int i = 0; i < alignment.getHeight(); i++)
608 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
612 for (int i = maxWidth; i > 0; i /= 10)
617 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
621 * Draws as many widths of a wrapped alignment as can fit in the visible
626 * available width in pixels
627 * @param canvasHeight
628 * available height in pixels
630 * the first column (0...) of the alignment to draw
632 public void drawWrappedPanel(Graphics g, int canvasWidth,
633 int canvasHeight, final int startColumn)
635 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
638 av.setWrappedWidth(wrappedWidthInResidues);
640 ViewportRanges ranges = av.getRanges();
641 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
644 * draw one width at a time (including any scales or annotation shown),
645 * until we have run out of either alignment or vertical space available
647 int ypos = wrappedSpaceAboveAlignment;
648 int maxWidth = ranges.getVisibleAlignmentWidth();
650 int start = startColumn;
651 int currentWidth = 0;
652 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
655 .min(maxWidth, start + wrappedWidthInResidues - 1);
656 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
657 ypos += wrappedRepeatHeightPx;
658 start += wrappedWidthInResidues;
662 drawWrappedDecorators(g, startColumn);
666 * Calculates and saves values needed when rendering a wrapped alignment.
667 * These depend on many factors, including
669 * <li>canvas width and height</li>
670 * <li>number of visible sequences, and height of annotations if shown</li>
671 * <li>font and character width</li>
672 * <li>whether scales are shown left, right or above the alignment</li>
676 * @param canvasHeight
677 * @return the number of residue columns in each width
679 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
681 int charHeight = av.getCharHeight();
684 * vertical space in pixels between wrapped widths of alignment
685 * - one character height, or two if scale above is drawn
687 wrappedSpaceAboveAlignment = charHeight
688 * (av.getScaleAboveWrapped() ? 2 : 1);
691 * height in pixels of the wrapped widths
693 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
695 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
697 // add annotations panel height if shown
698 wrappedRepeatHeightPx += getAnnotationHeight();
701 * number of visible widths (the last one may be part height),
702 * ensuring a part height includes at least one sequence
704 ViewportRanges ranges = av.getRanges();
705 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
706 int remainder = canvasHeight % wrappedRepeatHeightPx;
707 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
709 wrappedVisibleWidths++;
713 * compute width in residues; this also sets East and West label widths
715 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
718 * limit visibleWidths to not exceed width of alignment
720 int xMax = ranges.getVisibleAlignmentWidth();
721 int startToEnd = xMax - ranges.getStartRes();
722 int maxWidths = startToEnd / wrappedWidthInResidues;
723 if (startToEnd % wrappedWidthInResidues > 0)
727 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
729 return wrappedWidthInResidues;
733 * Draws one width of a wrapped alignment, including sequences and
734 * annnotations, if shown, but not scales or hidden column markers
740 * @param canvasHeight
742 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
743 int endColumn, int canvasHeight)
745 ViewportRanges ranges = av.getRanges();
746 int viewportWidth = ranges.getViewportWidth();
748 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
751 * move right before drawing by the width of the scale left (if any)
752 * plus column offset from left margin (usually zero, but may be non-zero
753 * when fast painting is drawing just a few columns)
755 int charWidth = av.getCharWidth();
756 int xOffset = labelWidthWest
757 + ((startColumn - ranges.getStartRes()) % viewportWidth)
759 g.translate(xOffset, 0);
761 // When printing we have an extra clipped region,
762 // the Printable page which we need to account for here
763 Shape clip = g.getClip();
767 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
771 g.setClip(0, (int) clip.getBounds().getY(),
772 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
776 * white fill the region to be drawn (so incremental fast paint doesn't
777 * scribble over an existing image)
779 gg.setColor(Color.white);
780 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
781 wrappedRepeatHeightPx);
783 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
786 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
788 if (av.isShowAnnotation())
790 g.translate(0, cHeight + ypos + 3);
791 if (annotations == null)
793 annotations = new AnnotationPanel(av);
796 annotations.renderer.drawComponent(annotations, av, g, -1,
797 startColumn, endx + 1);
798 g.translate(0, -cHeight - ypos - 3);
801 g.translate(-xOffset, 0);
805 * Draws scales left, right and above (if shown), and any hidden column
806 * markers, on all widths of the wrapped alignment
811 protected void drawWrappedDecorators(Graphics g, final int startColumn)
813 int charWidth = av.getCharWidth();
815 g.setFont(av.getFont());
816 g.setColor(Color.black);
818 int ypos = wrappedSpaceAboveAlignment;
819 ViewportRanges ranges = av.getRanges();
820 int viewportWidth = ranges.getViewportWidth();
821 int maxWidth = ranges.getVisibleAlignmentWidth();
823 int startCol = startColumn;
825 while (widthsDrawn < wrappedVisibleWidths)
827 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
829 if (av.getScaleLeftWrapped())
831 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
834 if (av.getScaleRightWrapped())
836 int x = labelWidthWest + viewportWidth * charWidth;
838 drawVerticalScale(g, startCol, endColumn, ypos, false);
843 * white fill region of scale above and hidden column markers
844 * (to support incremental fast paint of image)
846 g.translate(labelWidthWest, 0);
847 g.setColor(Color.white);
848 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
849 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
850 g.setColor(Color.black);
851 g.translate(-labelWidthWest, 0);
853 g.translate(labelWidthWest, 0);
855 if (av.getScaleAboveWrapped())
857 drawNorthScale(g, startCol, endColumn, ypos);
860 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
862 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
865 g.translate(-labelWidthWest, 0);
867 ypos += wrappedRepeatHeightPx;
868 startCol += viewportWidth;
874 * Draws markers (triangles) above hidden column positions between startColumn
882 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
883 int startColumn, int endColumn)
885 int charHeight = av.getCharHeight();
886 int charWidth = av.getCharWidth();
888 g.setColor(Color.blue);
889 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
890 List<Integer> positions = hidden.findHiddenRegionPositions();
891 for (int pos : positions)
893 int res = pos - startColumn;
895 if (res < 0 || res > endColumn - startColumn + 1)
901 * draw a downward-pointing triangle at the hidden columns location
902 * (before the following visible column)
904 int xMiddle = res * charWidth;
905 int[] xPoints = new int[] { xMiddle - charHeight / 4,
906 xMiddle + charHeight / 4, xMiddle };
907 int yTop = ypos - (charHeight / 2);
908 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
909 g.fillPolygon(xPoints, yPoints, 3);
914 * Draw a selection group over a wrapped alignment
916 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
918 int canvasHeight, int startRes)
920 int charHeight = av.getCharHeight();
921 int charWidth = av.getCharWidth();
923 // height gap above each panel
924 int hgap = charHeight;
925 if (av.getScaleAboveWrapped())
930 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
932 int cHeight = av.getAlignment().getHeight() * charHeight;
934 int startx = startRes;
936 int ypos = hgap; // vertical offset
937 int maxwidth = av.getAlignment().getWidth();
939 if (av.hasHiddenColumns())
941 maxwidth = av.getAlignment().getHiddenColumns()
942 .findColumnPosition(maxwidth);
945 // chop the wrapped alignment extent up into panel-sized blocks and treat
946 // each block as if it were a block from an unwrapped alignment
947 while ((ypos <= canvasHeight) && (startx < maxwidth))
949 // set end value to be start + width, or maxwidth, whichever is smaller
950 endx = startx + cWidth - 1;
957 g.translate(labelWidthWest, 0);
959 drawUnwrappedSelection(g, group, startx, endx, 0,
960 av.getAlignment().getHeight() - 1,
963 g.translate(-labelWidthWest, 0);
965 // update vertical offset
966 ypos += cHeight + getAnnotationHeight() + hgap;
968 // update horizontal offset
973 int getAnnotationHeight()
975 if (!av.isShowAnnotation())
980 if (annotations == null)
982 annotations = new AnnotationPanel(av);
985 return annotations.adjustPanelHeight();
989 * Draws the visible region of the alignment on the graphics context. If there
990 * are hidden column markers in the visible region, then each sub-region
991 * between the markers is drawn separately, followed by the hidden column
995 * the graphics context, positioned at the first residue to be drawn
997 * offset of the first column to draw (0..)
999 * offset of the last column to draw (0..)
1001 * offset of the first sequence to draw (0..)
1003 * offset of the last sequence to draw (0..)
1005 * vertical offset at which to draw (for wrapped alignments)
1007 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1008 final int startSeq, final int endSeq, final int yOffset)
1010 int charHeight = av.getCharHeight();
1011 int charWidth = av.getCharWidth();
1013 if (!av.hasHiddenColumns())
1015 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1020 final int screenYMax = endRes - startRes;
1021 int blockStart = startRes;
1022 int blockEnd = endRes;
1024 for (int[] region : av.getAlignment().getHiddenColumns()
1025 .getHiddenColumnsCopy())
1027 int hideStart = region[0];
1028 int hideEnd = region[1];
1030 if (hideStart <= blockStart)
1032 blockStart += (hideEnd - hideStart) + 1;
1037 * draw up to just before the next hidden region, or the end of
1038 * the visible region, whichever comes first
1040 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1043 g1.translate(screenY * charWidth, 0);
1045 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1048 * draw the downline of the hidden column marker (ScalePanel draws the
1049 * triangle on top) if we reached it
1051 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1053 g1.setColor(Color.blue);
1055 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1056 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1057 (endSeq - startSeq + 1) * charHeight + yOffset);
1060 g1.translate(-screenY * charWidth, 0);
1061 screenY += blockEnd - blockStart + 1;
1062 blockStart = hideEnd + 1;
1064 if (screenY > screenYMax)
1066 // already rendered last block
1071 if (screenY <= screenYMax)
1073 // remaining visible region to render
1074 blockEnd = blockStart + screenYMax - screenY;
1075 g1.translate(screenY * charWidth, 0);
1076 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1078 g1.translate(-screenY * charWidth, 0);
1085 * Draws a region of the visible alignment
1089 * offset of the first column in the visible region (0..)
1091 * offset of the last column in the visible region (0..)
1093 * offset of the first sequence in the visible region (0..)
1095 * offset of the last sequence in the visible region (0..)
1097 * vertical offset at which to draw (for wrapped alignments)
1099 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1100 int endSeq, int offset)
1102 int charHeight = av.getCharHeight();
1103 int charWidth = av.getCharWidth();
1105 g.setFont(av.getFont());
1106 seqRdr.prepare(g, av.isRenderGaps());
1110 // / First draw the sequences
1111 // ///////////////////////////
1112 for (int i = startSeq; i <= endSeq; i++)
1114 nextSeq = av.getAlignment().getSequenceAt(i);
1115 if (nextSeq == null)
1117 // occasionally, a race condition occurs such that the alignment row is
1121 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1122 startRes, endRes, offset + ((i - startSeq) * charHeight));
1124 if (av.isShowSequenceFeatures())
1126 fr.drawSequence(g, nextSeq, startRes, endRes,
1127 offset + ((i - startSeq) * charHeight), false);
1131 * highlight search Results once sequence has been drawn
1133 if (av.hasSearchResults())
1135 SearchResultsI searchResults = av.getSearchResults();
1136 int[] visibleResults = searchResults.getResults(nextSeq,
1138 if (visibleResults != null)
1140 for (int r = 0; r < visibleResults.length; r += 2)
1142 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1143 visibleResults[r + 1], (visibleResults[r] - startRes)
1145 + ((i - startSeq) * charHeight));
1151 if (av.getSelectionGroup() != null
1152 || av.getAlignment().getGroups().size() > 0)
1154 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1159 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1160 int startSeq, int endSeq, int offset)
1162 Graphics2D g = (Graphics2D) g1;
1164 // ///////////////////////////////////
1165 // Now outline any areas if necessary
1166 // ///////////////////////////////////
1168 SequenceGroup group = null;
1169 int groupIndex = -1;
1171 if (av.getAlignment().getGroups().size() > 0)
1173 group = av.getAlignment().getGroups().get(0);
1179 g.setStroke(new BasicStroke());
1180 g.setColor(group.getOutlineColour());
1184 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1189 g.setStroke(new BasicStroke());
1191 if (groupIndex >= av.getAlignment().getGroups().size())
1196 group = av.getAlignment().getGroups().get(groupIndex);
1198 } while (groupIndex < av.getAlignment().getGroups().size());
1206 * Draw the selection group as a separate image and overlay
1208 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1209 int startSeq, int endSeq)
1211 // get a new image of the correct size
1212 BufferedImage selectionImage = setupImage();
1214 if (selectionImage == null)
1219 SequenceGroup group = av.getSelectionGroup();
1226 // set up drawing colour
1227 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1229 setupSelectionGroup(g, selectionImage);
1231 if (!av.getWrapAlignment())
1233 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1238 drawWrappedSelection(g, group, getWidth(), getHeight(),
1239 av.getRanges().getStartRes());
1243 return selectionImage;
1247 * Draw the cursor as a separate image and overlay
1250 * start residue of area to draw cursor in
1252 * end residue of area to draw cursor in
1254 * start sequence of area to draw cursor in
1256 * end sequence of are to draw cursor in
1257 * @return a transparent image of the same size as the sequence canvas, with
1258 * the cursor drawn on it, if any
1260 private void drawCursor(Graphics g, int startRes, int endRes,
1264 // convert the cursorY into a position on the visible alignment
1265 int cursor_ypos = av.getAlignment().getHiddenSequences()
1266 .findIndexWithoutHiddenSeqs(cursorY);
1268 // don't do work unless we have to
1269 if (av.cursorMode && cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1273 int startx = startRes;
1276 // convert the cursorX into a position on the visible alignment
1277 int cursor_xpos = av.getAlignment().getHiddenColumns()
1278 .findColumnPosition(cursorX);
1280 if (av.getWrapAlignment())
1282 // work out the correct offsets for the cursor
1283 int charHeight = av.getCharHeight();
1284 int charWidth = av.getCharWidth();
1285 int canvasWidth = getWidth();
1286 int canvasHeight = getHeight();
1288 // height gap above each panel
1289 int hgap = charHeight;
1290 if (av.getScaleAboveWrapped())
1295 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1297 int cHeight = av.getAlignment().getHeight() * charHeight;
1299 endx = startx + cWidth - 1;
1300 int ypos = hgap; // vertical offset
1302 // iterate down the wrapped panels
1303 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1305 // update vertical offset
1306 ypos += cHeight + getAnnotationHeight() + hgap;
1308 // update horizontal offset
1310 endx = startx + cWidth - 1;
1313 xoffset = labelWidthWest;
1316 // now check if cursor is within range for x values
1317 if (cursor_xpos >= startx && cursor_xpos <= endx)
1319 // get the character the cursor is drawn at
1320 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1321 char s = seq.getCharAt(cursorX);
1323 seqRdr.drawCursor(g, s,
1324 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1325 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1333 * Set up graphics for selection group
1335 private void setupSelectionGroup(Graphics2D g,
1336 BufferedImage selectionImage)
1338 // set background to transparent
1339 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1340 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1342 // set up foreground to draw red dashed line
1343 g.setComposite(AlphaComposite.Src);
1344 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1345 BasicStroke.JOIN_ROUND, 3f, new float[]
1347 g.setColor(Color.RED);
1351 * Draw a selection group over an unwrapped alignment
1352 * @param g graphics object to draw with
1353 * @param group selection group
1354 * @param startRes start residue of area to draw
1355 * @param endRes end residue of area to draw
1356 * @param startSeq start sequence of area to draw
1357 * @param endSeq end sequence of area to draw
1358 * @param offset vertical offset (used when called from wrapped alignment code)
1360 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1361 int startRes, int endRes, int startSeq, int endSeq, int offset)
1363 int charWidth = av.getCharWidth();
1365 if (!av.hasHiddenColumns())
1367 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1372 // package into blocks of visible columns
1374 int blockStart = startRes;
1375 int blockEnd = endRes;
1377 for (int[] region : av.getAlignment().getHiddenColumns()
1378 .getHiddenColumnsCopy())
1380 int hideStart = region[0];
1381 int hideEnd = region[1];
1383 if (hideStart <= blockStart)
1385 blockStart += (hideEnd - hideStart) + 1;
1389 blockEnd = hideStart - 1;
1391 g.translate(screenY * charWidth, 0);
1392 drawPartialGroupOutline(g, group,
1393 blockStart, blockEnd, startSeq, endSeq, offset);
1395 g.translate(-screenY * charWidth, 0);
1396 screenY += blockEnd - blockStart + 1;
1397 blockStart = hideEnd + 1;
1399 if (screenY > (endRes - startRes))
1401 // already rendered last block
1406 if (screenY <= (endRes - startRes))
1408 // remaining visible region to render
1409 blockEnd = blockStart + (endRes - startRes) - screenY;
1410 g.translate(screenY * charWidth, 0);
1411 drawPartialGroupOutline(g, group,
1412 blockStart, blockEnd, startSeq, endSeq, offset);
1414 g.translate(-screenY * charWidth, 0);
1420 * Draw the selection group as a separate image and overlay
1422 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1423 int startRes, int endRes, int startSeq, int endSeq,
1426 int charHeight = av.getCharHeight();
1427 int charWidth = av.getCharWidth();
1429 int visWidth = (endRes - startRes + 1) * charWidth;
1433 boolean inGroup = false;
1441 for (i = startSeq; i <= endSeq; i++)
1443 // position of start residue of group relative to startRes, in pixels
1444 sx = (group.getStartRes() - startRes) * charWidth;
1446 // width of group in pixels
1447 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1450 sy = verticalOffset + (i - startSeq) * charHeight;
1452 if (sx + xwidth < 0 || sx > visWidth)
1457 if ((sx <= (endRes - startRes) * charWidth)
1458 && group.getSequences(null)
1459 .contains(av.getAlignment().getSequenceAt(i)))
1461 if ((bottom == -1) && !group.getSequences(null)
1462 .contains(av.getAlignment().getSequenceAt(i + 1)))
1464 bottom = sy + charHeight;
1469 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1470 .contains(av.getAlignment().getSequenceAt(i - 1)))
1483 // if start position is visible, draw vertical line to left of
1485 if (sx >= 0 && sx < visWidth)
1487 g.drawLine(sx, oldY, sx, sy);
1490 // if end position is visible, draw vertical line to right of
1492 if (sx + xwidth < visWidth)
1494 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1503 // don't let width extend beyond current block, or group extent
1505 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1507 xwidth = (endRes - startRes + 1) * charWidth - sx;
1510 // draw horizontal line at top of group
1513 g.drawLine(sx, top, sx + xwidth, top);
1517 // draw horizontal line at bottom of group
1520 g.drawLine(sx, bottom, sx + xwidth, bottom);
1531 sy = verticalOffset + ((i - startSeq) * charHeight);
1532 if (sx >= 0 && sx < visWidth)
1534 g.drawLine(sx, oldY, sx, sy);
1537 if (sx + xwidth < visWidth)
1539 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1548 if (sx + xwidth > visWidth)
1552 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1554 xwidth = (endRes - startRes + 1) * charWidth;
1559 g.drawLine(sx, top, sx + xwidth, top);
1565 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1574 * Highlights search results in the visible region by rendering as white text
1575 * on a black background. Any previous highlighting is removed. Answers true
1576 * if any highlight was left on the visible alignment (so status bar should be
1577 * set to match), else false.
1579 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1580 * alignment had to be scrolled to show the highlighted region, then it should
1581 * be fully redrawn, otherwise a fast paint can be performed. This argument
1582 * could be removed if fast paint of scrolled wrapped alignment is coded in
1583 * future (JAL-2609).
1586 * @param noFastPaint
1589 public boolean highlightSearchResults(SearchResultsI results,
1590 boolean noFastPaint)
1596 boolean wrapped = av.getWrapAlignment();
1599 fastPaint = !noFastPaint;
1600 fastpainting = fastPaint;
1603 * to avoid redrawing the whole visible region, we instead
1604 * redraw just the minimal regions to remove previous highlights
1607 SearchResultsI previous = av.getSearchResults();
1608 av.setSearchResults(results);
1609 boolean redrawn = false;
1610 boolean drawn = false;
1613 redrawn = drawMappedPositionsWrapped(previous);
1614 drawn = drawMappedPositionsWrapped(results);
1619 redrawn = drawMappedPositions(previous);
1620 drawn = drawMappedPositions(results);
1625 * if highlights were either removed or added, repaint
1633 * return true only if highlights were added
1639 fastpainting = false;
1644 * Redraws the minimal rectangle in the visible region (if any) that includes
1645 * mapped positions of the given search results. Whether or not positions are
1646 * highlighted depends on the SearchResults set on the Viewport. This allows
1647 * this method to be called to either clear or set highlighting. Answers true
1648 * if any positions were drawn (in which case a repaint is still required),
1654 protected boolean drawMappedPositions(SearchResultsI results)
1656 if (results == null)
1662 * calculate the minimal rectangle to redraw that
1663 * includes both new and existing search results
1665 int firstSeq = Integer.MAX_VALUE;
1667 int firstCol = Integer.MAX_VALUE;
1669 boolean matchFound = false;
1671 ViewportRanges ranges = av.getRanges();
1672 int firstVisibleColumn = ranges.getStartRes();
1673 int lastVisibleColumn = ranges.getEndRes();
1674 AlignmentI alignment = av.getAlignment();
1675 if (av.hasHiddenColumns())
1677 firstVisibleColumn = alignment.getHiddenColumns()
1678 .adjustForHiddenColumns(firstVisibleColumn);
1679 lastVisibleColumn = alignment.getHiddenColumns()
1680 .adjustForHiddenColumns(lastVisibleColumn);
1683 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1684 .getEndSeq(); seqNo++)
1686 SequenceI seq = alignment.getSequenceAt(seqNo);
1688 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1690 if (visibleResults != null)
1692 for (int i = 0; i < visibleResults.length - 1; i += 2)
1694 int firstMatchedColumn = visibleResults[i];
1695 int lastMatchedColumn = visibleResults[i + 1];
1696 if (firstMatchedColumn <= lastVisibleColumn
1697 && lastMatchedColumn >= firstVisibleColumn)
1700 * found a search results match in the visible region -
1701 * remember the first and last sequence matched, and the first
1702 * and last visible columns in the matched positions
1705 firstSeq = Math.min(firstSeq, seqNo);
1706 lastSeq = Math.max(lastSeq, seqNo);
1707 firstMatchedColumn = Math.max(firstMatchedColumn,
1708 firstVisibleColumn);
1709 lastMatchedColumn = Math.min(lastMatchedColumn,
1711 firstCol = Math.min(firstCol, firstMatchedColumn);
1712 lastCol = Math.max(lastCol, lastMatchedColumn);
1720 if (av.hasHiddenColumns())
1722 firstCol = alignment.getHiddenColumns()
1723 .findColumnPosition(firstCol);
1724 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1726 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1727 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1728 gg.translate(transX, transY);
1729 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1730 gg.translate(-transX, -transY);
1737 public void propertyChange(PropertyChangeEvent evt)
1739 String eventName = evt.getPropertyName();
1741 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1747 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1755 if (eventName.equals(ViewportRanges.STARTRES)
1756 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1758 // Make sure we're not trying to draw a panel
1759 // larger than the visible window
1760 if (eventName.equals(ViewportRanges.STARTRES))
1762 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1766 scrollX = ((int[]) evt.getNewValue())[0]
1767 - ((int[]) evt.getOldValue())[0];
1769 ViewportRanges vpRanges = av.getRanges();
1771 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1772 if (scrollX > range)
1776 else if (scrollX < -range)
1781 // Both scrolling and resizing change viewport ranges: scrolling changes
1782 // both start and end points, but resize only changes end values.
1783 // Here we only want to fastpaint on a scroll, with resize using a normal
1784 // paint, so scroll events are identified as changes to the horizontal or
1785 // vertical start value.
1786 if (eventName.equals(ViewportRanges.STARTRES))
1788 if (av.getWrapAlignment())
1790 fastPaintWrapped(scrollX);
1794 fastPaint(scrollX, 0);
1797 else if (eventName.equals(ViewportRanges.STARTSEQ))
1800 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1802 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1804 if (av.getWrapAlignment())
1806 fastPaintWrapped(scrollX);
1810 fastPaint(scrollX, 0);
1812 // bizarrely, we only need to scroll on the x value here as fastpaint
1813 // copies the full height of the image anyway. Passing in the y value
1814 // causes nasty repaint artefacts, which only disappear on a full
1821 * Does a minimal update of the image for a scroll movement. This method
1822 * handles scroll movements of up to one width of the wrapped alignment (one
1823 * click in the vertical scrollbar). Larger movements (for example after a
1824 * scroll to highlight a mapped position) trigger a full redraw instead.
1827 * number of positions scrolled (right if positive, left if negative)
1829 protected void fastPaintWrapped(int scrollX)
1831 ViewportRanges ranges = av.getRanges();
1833 if (Math.abs(scrollX) > ranges.getViewportWidth())
1836 * shift of more than one view width is
1837 * overcomplicated to handle in this method
1844 if (fastpainting || gg == null)
1850 fastpainting = true;
1854 calculateWrappedGeometry(getWidth(), getHeight());
1857 * relocate the regions of the alignment that are still visible
1859 shiftWrappedAlignment(-scrollX);
1862 * add new columns (sequence, annotation)
1863 * - at top left if scrollX < 0
1864 * - at right of last two widths if scrollX > 0
1868 int startRes = ranges.getStartRes();
1869 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1870 - scrollX - 1, getHeight());
1874 fastPaintWrappedAddRight(scrollX);
1878 * draw all scales (if shown) and hidden column markers
1880 drawWrappedDecorators(gg, ranges.getStartRes());
1885 fastpainting = false;
1890 * Draws the specified number of columns at the 'end' (bottom right) of a
1891 * wrapped alignment view, including sequences and annotations if shown, but
1892 * not scales. Also draws the same number of columns at the right hand end of
1893 * the second last width shown, if the last width is not full height (so
1894 * cannot simply be copied from the graphics image).
1898 protected void fastPaintWrappedAddRight(int columns)
1905 ViewportRanges ranges = av.getRanges();
1906 int viewportWidth = ranges.getViewportWidth();
1907 int charWidth = av.getCharWidth();
1910 * draw full height alignment in the second last row, last columns, if the
1911 * last row was not full height
1913 int visibleWidths = wrappedVisibleWidths;
1914 int canvasHeight = getHeight();
1915 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1917 if (lastWidthPartHeight)
1919 int widthsAbove = Math.max(0, visibleWidths - 2);
1920 int ypos = wrappedRepeatHeightPx * widthsAbove
1921 + wrappedSpaceAboveAlignment;
1922 int endRes = ranges.getEndRes();
1923 endRes += widthsAbove * viewportWidth;
1924 int startRes = endRes - columns;
1925 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1929 * white fill first to erase annotations
1931 gg.translate(xOffset, 0);
1932 gg.setColor(Color.white);
1933 gg.fillRect(labelWidthWest, ypos,
1934 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1935 gg.translate(-xOffset, 0);
1937 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1941 * draw newly visible columns in last wrapped width (none if we
1942 * have reached the end of the alignment)
1943 * y-offset for drawing last width is height of widths above,
1946 int widthsAbove = visibleWidths - 1;
1947 int ypos = wrappedRepeatHeightPx * widthsAbove
1948 + wrappedSpaceAboveAlignment;
1949 int endRes = ranges.getEndRes();
1950 endRes += widthsAbove * viewportWidth;
1951 int startRes = endRes - columns + 1;
1954 * white fill first to erase annotations
1956 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1958 gg.translate(xOffset, 0);
1959 gg.setColor(Color.white);
1960 int width = viewportWidth * charWidth - xOffset;
1961 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1962 gg.translate(-xOffset, 0);
1964 gg.setFont(av.getFont());
1965 gg.setColor(Color.black);
1967 if (startRes < ranges.getVisibleAlignmentWidth())
1969 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1973 * and finally, white fill any space below the visible alignment
1975 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1976 if (heightBelow > 0)
1978 gg.setColor(Color.white);
1979 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1984 * Shifts the visible alignment by the specified number of columns - left if
1985 * negative, right if positive. Copies and moves sequences and annotations (if
1986 * shown). Scales, hidden column markers and any newly visible columns must be
1991 protected void shiftWrappedAlignment(int positions)
1997 int charWidth = av.getCharWidth();
1999 int canvasHeight = getHeight();
2000 ViewportRanges ranges = av.getRanges();
2001 int viewportWidth = ranges.getViewportWidth();
2002 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2004 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2005 int xMax = ranges.getVisibleAlignmentWidth();
2010 * shift right (after scroll left)
2011 * for each wrapped width (starting with the last), copy (width-positions)
2012 * columns from the left margin to the right margin, and copy positions
2013 * columns from the right margin of the row above (if any) to the
2014 * left margin of the current row
2018 * get y-offset of last wrapped width, first row of sequences
2020 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2021 y += wrappedSpaceAboveAlignment;
2022 int copyFromLeftStart = labelWidthWest;
2023 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2027 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2028 positions * charWidth, 0);
2031 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2032 positions * charWidth, heightToCopy, -widthToCopy,
2033 wrappedRepeatHeightPx);
2036 y -= wrappedRepeatHeightPx;
2042 * shift left (after scroll right)
2043 * for each wrapped width (starting with the first), copy (width-positions)
2044 * columns from the right margin to the left margin, and copy positions
2045 * columns from the left margin of the row below (if any) to the
2046 * right margin of the current row
2048 int xpos = av.getRanges().getStartRes();
2049 int y = wrappedSpaceAboveAlignment;
2050 int copyFromRightStart = labelWidthWest - positions * charWidth;
2052 while (y < canvasHeight)
2054 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2055 positions * charWidth, 0);
2056 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2057 && (xpos + viewportWidth <= xMax))
2059 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2060 * charWidth, heightToCopy, widthToCopy,
2061 -wrappedRepeatHeightPx);
2064 y += wrappedRepeatHeightPx;
2065 xpos += viewportWidth;
2072 * Redraws any positions in the search results in the visible region of a
2073 * wrapped alignment. Any highlights are drawn depending on the search results
2074 * set on the Viewport, not the <code>results</code> argument. This allows
2075 * this method to be called either to clear highlights (passing the previous
2076 * search results), or to draw new highlights.
2081 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2083 if (results == null)
2087 int charHeight = av.getCharHeight();
2089 boolean matchFound = false;
2091 calculateWrappedGeometry(getWidth(), getHeight());
2092 int wrappedWidth = av.getWrappedWidth();
2093 int wrappedHeight = wrappedRepeatHeightPx;
2095 ViewportRanges ranges = av.getRanges();
2096 int canvasHeight = getHeight();
2097 int repeats = canvasHeight / wrappedHeight;
2098 if (canvasHeight / wrappedHeight > 0)
2103 int firstVisibleColumn = ranges.getStartRes();
2104 int lastVisibleColumn = ranges.getStartRes() + repeats
2105 * ranges.getViewportWidth() - 1;
2107 AlignmentI alignment = av.getAlignment();
2108 if (av.hasHiddenColumns())
2110 firstVisibleColumn = alignment.getHiddenColumns()
2111 .adjustForHiddenColumns(firstVisibleColumn);
2112 lastVisibleColumn = alignment.getHiddenColumns()
2113 .adjustForHiddenColumns(lastVisibleColumn);
2116 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2118 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2119 .getEndSeq(); seqNo++)
2121 SequenceI seq = alignment.getSequenceAt(seqNo);
2123 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2125 if (visibleResults != null)
2127 for (int i = 0; i < visibleResults.length - 1; i += 2)
2129 int firstMatchedColumn = visibleResults[i];
2130 int lastMatchedColumn = visibleResults[i + 1];
2131 if (firstMatchedColumn <= lastVisibleColumn
2132 && lastMatchedColumn >= firstVisibleColumn)
2135 * found a search results match in the visible region
2137 firstMatchedColumn = Math.max(firstMatchedColumn,
2138 firstVisibleColumn);
2139 lastMatchedColumn = Math.min(lastMatchedColumn,
2143 * draw each mapped position separately (as contiguous positions may
2144 * wrap across lines)
2146 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2148 int displayColumn = mappedPos;
2149 if (av.hasHiddenColumns())
2151 displayColumn = alignment.getHiddenColumns()
2152 .findColumnPosition(displayColumn);
2156 * transX: offset from left edge of canvas to residue position
2158 int transX = labelWidthWest
2159 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2160 * av.getCharWidth();
2163 * transY: offset from top edge of canvas to residue position
2165 int transY = gapHeight;
2166 transY += (displayColumn - ranges.getStartRes())
2167 / wrappedWidth * wrappedHeight;
2168 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2171 * yOffset is from graphics origin to start of visible region
2173 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2174 if (transY < getHeight())
2177 gg.translate(transX, transY);
2178 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2180 gg.translate(-transX, -transY);
2192 * Answers the width in pixels of the left scale labels (0 if not shown)
2196 int getLabelWidthWest()
2198 return labelWidthWest;