Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:56:54 GMT
  2. Package jalview.gui

File SeqCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart7.png
29% of files have more coverage

Code metrics

282
679
37
1
2,196
1,271
211
0.31
18.35
37
5.7

Classes

Class Line # Actions
SeqCanvas 58 679 211
0.629258562.9%
 

Contributing tests

This file is covered by 195 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
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.
11    *
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.
16    *
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.
20    */
21    package jalview.gui;
22   
23    import java.awt.BasicStroke;
24    import java.awt.BorderLayout;
25    import java.awt.Color;
26    import java.awt.FontMetrics;
27    import java.awt.Graphics;
28    import java.awt.Graphics2D;
29    import java.awt.Rectangle;
30    import java.awt.RenderingHints;
31    import java.awt.image.BufferedImage;
32    import java.beans.PropertyChangeEvent;
33    import java.util.Iterator;
34    import java.util.List;
35   
36    import javax.swing.JPanel;
37   
38    import jalview.api.SequenceRenderer;
39    import jalview.datamodel.AlignmentI;
40    import jalview.datamodel.HiddenColumns;
41    import jalview.datamodel.SearchResultsI;
42    import jalview.datamodel.SequenceGroup;
43    import jalview.datamodel.SequenceI;
44    import jalview.datamodel.VisibleContigsIterator;
45    import jalview.renderer.ScaleRenderer;
46    import jalview.renderer.ScaleRenderer.ScaleMark;
47    import jalview.util.Comparison;
48    import jalview.viewmodel.ViewportListenerI;
49    import jalview.viewmodel.ViewportRanges;
50   
51    /**
52    * The Swing component on which the alignment sequences, and annotations (if
53    * shown), are drawn. This includes scales above, left and right (if shown) in
54    * Wrapped mode, but not the scale above in Unwrapped mode.
55    *
56    */
57    @SuppressWarnings("serial")
 
58    public class SeqCanvas extends JPanel implements ViewportListenerI
59    {
60    /**
61    * vertical gap in pixels between sequences and annotations when in wrapped
62    * mode
63    */
64    static final int SEQS_ANNOTATION_GAP = 3;
65   
66    private static final String ZEROS = "0000000000";
67   
68    final FeatureRenderer fr;
69   
70    BufferedImage img;
71   
72    AlignViewport av;
73   
74    int cursorX = 0;
75   
76    int cursorY = 0;
77   
78    private final SequenceRenderer seqRdr;
79   
80    boolean fastPaint = false;
81   
82    private boolean fastpainting = false;
83   
84    private AnnotationPanel annotations;
85   
86    /*
87    * measurements for drawing a wrapped alignment
88    */
89    private int labelWidthEast; // label right width in pixels if shown
90   
91    private int labelWidthWest; // label left width in pixels if shown
92   
93    int wrappedSpaceAboveAlignment; // gap between widths
94   
95    int wrappedRepeatHeightPx; // height in pixels of wrapped width
96   
97    private int wrappedVisibleWidths; // number of wrapped widths displayed
98   
99    // Don't do this! Graphics handles are supposed to be transient
100    // private Graphics2D gg;
101   
102    /**
103    * Creates a new SeqCanvas object.
104    *
105    * @param ap
106    */
 
107  475 toggle public SeqCanvas(AlignmentPanel ap)
108    {
109  475 this.av = ap.av;
110  475 fr = new FeatureRenderer(ap);
111  475 seqRdr = new jalview.gui.SequenceRenderer(av);
112  475 setLayout(new BorderLayout());
113  475 PaintRefresher.Register(this, av.getSequenceSetId());
114  475 setBackground(Color.white);
115   
116  475 av.getRanges().addPropertyChangeListener(this);
117    }
118   
 
119  6 toggle public SequenceRenderer getSequenceRenderer()
120    {
121  6 return seqRdr;
122    }
123   
 
124  653 toggle public FeatureRenderer getFeatureRenderer()
125    {
126  653 return fr;
127    }
128   
129    /**
130    * Draws the scale above a region of a wrapped alignment, consisting of a
131    * column number every major interval (10 columns).
132    *
133    * @param g
134    * the graphics context to draw on, positioned at the start (bottom
135    * left) of the line on which to draw any scale marks
136    * @param startx
137    * start alignment column (0..)
138    * @param endx
139    * end alignment column (0..)
140    * @param ypos
141    * y offset to draw at
142    */
 
143  0 toggle private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
144    {
145  0 int charHeight = av.getCharHeight();
146  0 int charWidth = av.getCharWidth();
147   
148    /*
149    * white fill the scale space (for the fastPaint case)
150    */
151  0 g.setColor(Color.white);
152  0 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
153    charHeight * 3 / 2 + 2);
154  0 g.setColor(Color.black);
155   
156  0 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
157    endx);
158  0 for (ScaleMark mark : marks)
159    {
160  0 int mpos = mark.column; // (i - startx - 1)
161  0 if (mpos < 0)
162    {
163  0 continue;
164    }
165  0 String mstring = mark.text;
166   
167  0 if (mark.major)
168    {
169  0 if (mstring != null)
170    {
171  0 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
172    }
173   
174    /*
175    * draw a tick mark below the column number, centred on the column;
176    * height of tick mark is 4 pixels less than half a character
177    */
178  0 int xpos = (mpos * charWidth) + (charWidth / 2);
179  0 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
180    }
181    }
182    }
183   
184    /**
185    * Draw the scale to the left or right of a wrapped alignment
186    *
187    * @param g
188    * graphics context, positioned at the start of the scale to be drawn
189    * @param startx
190    * first column of wrapped width (0.. excluding any hidden columns)
191    * @param endx
192    * last column of wrapped width (0.. excluding any hidden columns)
193    * @param ypos
194    * vertical offset at which to begin the scale
195    * @param left
196    * if true, scale is left of residues, if false, scale is right
197    */
 
198  478 toggle void drawVerticalScale(Graphics g, final int startx, final int endx,
199    final int ypos, final boolean left)
200    {
201  478 int charHeight = av.getCharHeight();
202  478 int charWidth = av.getCharWidth();
203   
204  478 int yPos = ypos + charHeight;
205  478 int startX = startx;
206  478 int endX = endx;
207   
208  478 if (av.hasHiddenColumns())
209    {
210  470 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
211  470 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
212  470 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
213    }
214  478 FontMetrics fm = getFontMetrics(av.getFont());
215   
216  1546 for (int i = 0; i < av.getAlignment().getHeight(); i++)
217    {
218  1068 SequenceI seq = av.getAlignment().getSequenceAt(i);
219   
220    /*
221    * find sequence position of first non-gapped position -
222    * to the right if scale left, to the left if scale right
223    */
224  1068 int index = left ? startX : endX;
225  1068 int value = -1;
226  1308 while (index >= startX && index <= endX)
227    {
228  1308 if (!Comparison.isGap(seq.getCharAt(index)))
229    {
230  1068 value = seq.findPosition(index);
231  1068 break;
232    }
233  240 if (left)
234    {
235  0 index++;
236    }
237    else
238    {
239  240 index--;
240    }
241    }
242   
243    /*
244    * white fill the space for the scale
245    */
246  1068 g.setColor(Color.white);
247  1068 int y = (yPos + (i * charHeight)) - (charHeight / 5);
248    // fillRect origin is top left of rectangle
249  1068 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
250    charHeight + 1);
251   
252  1068 if (value != -1)
253    {
254    /*
255    * draw scale value, right justified within its width less half a
256    * character width padding on the right
257    */
258  1068 int labelSpace = left ? labelWidthWest : labelWidthEast;
259  1068 labelSpace -= charWidth / 2; // leave space to the right
260  1068 String valueAsString = String.valueOf(value);
261  1068 int labelLength = fm.stringWidth(valueAsString);
262  1068 int xOffset = labelSpace - labelLength;
263  1068 g.setColor(Color.black);
264  1068 g.drawString(valueAsString, xOffset, y);
265    }
266    }
267   
268    }
269   
270    /**
271    * Does a fast paint of an alignment in response to a scroll. Most of the
272    * visible region is simply copied and shifted, and then any newly visible
273    * columns or rows are drawn. The scroll may be horizontal or vertical, but
274    * not both at once. Scrolling may be the result of
275    * <ul>
276    * <li>dragging a scroll bar</li>
277    * <li>clicking in the scroll bar</li>
278    * <li>scrolling by trackpad, middle mouse button, or other device</li>
279    * <li>by moving the box in the Overview window</li>
280    * <li>programmatically to make a highlighted position visible</li>
281    * <li>pasting a block of sequences</li>
282    * </ul>
283    *
284    * @param horizontal
285    * columns to shift right (positive) or left (negative)
286    * @param vertical
287    * rows to shift down (positive) or up (negative)
288    */
 
289  72 toggle public void fastPaint(int horizontal, int vertical)
290    {
291   
292    // effectively:
293    // if (horizontal != 0 && vertical != 0)
294    // throw new InvalidArgumentException();
295  72 if (fastpainting || img == null)
296    {
297  58 return;
298    }
299  14 fastpainting = true;
300  14 fastPaint = true;
301  14 try
302    {
303  14 int charHeight = av.getCharHeight();
304  14 int charWidth = av.getCharWidth();
305   
306  14 ViewportRanges ranges = av.getRanges();
307  14 int startRes = ranges.getStartRes();
308  14 int endRes = ranges.getEndRes();
309  14 int startSeq = ranges.getStartSeq();
310  14 int endSeq = ranges.getEndSeq();
311  14 int transX = 0;
312  14 int transY = 0;
313   
314  14 if (horizontal > 0) // scrollbar pulled right, image to the left
315    {
316  1 transX = (endRes - startRes - horizontal) * charWidth;
317  1 startRes = endRes - horizontal;
318    }
319  13 else if (horizontal < 0)
320    {
321  0 endRes = startRes - horizontal;
322    }
323   
324  14 if (vertical > 0) // scroll down
325    {
326  8 startSeq = endSeq - vertical + 1;
327   
328  8 if (startSeq < ranges.getStartSeq())
329    { // ie scrolling too fast, more than a page at a time
330  1 startSeq = ranges.getStartSeq();
331    }
332    else
333    {
334  7 transY = img.getHeight() - (vertical * charHeight);
335    }
336    }
337  6 else if (vertical < 0) // scroll up
338    {
339  5 endSeq = startSeq - vertical - 1;
340   
341  5 if (endSeq > ranges.getEndSeq())
342    {
343  0 endSeq = ranges.getEndSeq();
344    }
345    }
346   
347    // jalview.bin.Console.errPrintln(">>> FastPaint to " + transX + " " +
348    // transY + " "
349    // + horizontal + " " + vertical + " " + startRes + " " + endRes
350    // + " " + startSeq + " " + endSeq);
351   
352  14 Graphics gg = img.getGraphics();
353  14 gg.copyArea(horizontal * charWidth, vertical * charHeight,
354    img.getWidth(), img.getHeight(), -horizontal * charWidth,
355    -vertical * charHeight);
356   
357    /** @j2sNative xxi = this.img */
358   
359  14 gg.translate(transX, transY);
360  14 drawPanel(seqRdr, gg, startRes, endRes, startSeq, endSeq, 0);
361  14 gg.translate(-transX, -transY);
362  14 gg.dispose();
363   
364    // Call repaint on alignment panel so that repaints from other alignment
365    // panel components can be aggregated. Otherwise performance of the
366    // overview window and others may be adversely affected.
367    // jalview.bin.Console.outPrintln("SeqCanvas fastPaint() repaint()
368    // request...");
369  14 av.getAlignPanel().repaint();
370    } finally
371    {
372  14 fastpainting = false;
373    }
374    }
375   
 
376  2584 toggle @Override
377    public void paintComponent(Graphics g)
378    {
379   
380  2584 int charHeight = av.getCharHeight();
381  2584 int charWidth = av.getCharWidth();
382   
383  2584 int width = getWidth();
384  2584 int height = getHeight();
385   
386  2584 width -= (width % charWidth);
387  2584 height -= (height % charHeight);
388   
389    // BH 2019 can't possibly fastPaint if either width or height is 0
390   
391  2584 if (width == 0 || height == 0)
392    {
393  2 return;
394    }
395   
396  2582 ViewportRanges ranges = av.getRanges();
397  2582 int startRes = ranges.getStartRes();
398  2582 int startSeq = ranges.getStartSeq();
399  2582 int endRes = ranges.getEndRes();
400  2582 int endSeq = ranges.getEndSeq();
401   
402    // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
403    // repaint() requests in unpredictable ways. In this case, the issue was
404    // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
405    // repaint request preceded two full requests, thus resulting
406    // in a full request for paint. In constrast, in JavaScript, the three
407    // requests were bundled together into one, so the fastPaint flag was
408    // still present for the second and third request.
409    //
410    // This resulted in incomplete painting.
411    //
412    // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
413    // in PaintRefresher when the target to be painted is one of those two
414    // components.
415    //
416    // BH 2019.04.22
417    //
418    // An initial idea; can be removed once we determine this issue is closed:
419    // if (av.isFastPaintDisabled())
420    // {
421    // fastPaint = false;
422    // }
423   
424  2582 Rectangle vis, clip;
425  ? if (img != null
426    && (fastPaint
427    || (vis = getVisibleRect()).width != (clip = g
428    .getClipBounds()).width
429    || vis.height != clip.height))
430    {
431  193 g.drawImage(img, 0, 0, this);
432  193 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
433    endSeq);
434  193 fastPaint = false;
435    }
436    else
437    {
438    // img is a cached version of the last view we drew.
439    // If we have no img or the size has changed, make a new one.
440    //
441  2389 if (img == null || width != img.getWidth()
442    || height != img.getHeight())
443    {
444  422 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
445    }
446   
447  2389 Graphics2D gg = (Graphics2D) img.getGraphics();
448  2389 gg.setFont(av.getFont());
449   
450  2389 if (av.antiAlias)
451    {
452  705 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
453    RenderingHints.VALUE_ANTIALIAS_ON);
454    }
455   
456  2389 gg.setColor(Color.white);
457  2389 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
458   
459  2389 if (av.getWrapAlignment())
460    {
461  239 drawWrappedPanel(seqRdr, gg, getWidth(), getHeight(),
462    ranges.getStartRes());
463    }
464    else
465    {
466  2150 drawPanel(seqRdr, gg, startRes, endRes, startSeq, endSeq, 0);
467    }
468   
469  2389 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
470   
471  2389 g.drawImage(img, 0, 0, this);
472  2389 gg.dispose();
473    }
474   
475  2582 if (av.cursorMode)
476    {
477  0 drawCursor(g, startRes, endRes, startSeq, endSeq);
478    }
479    }
480   
481    /**
482    * Draw an alignment panel for printing
483    *
484    * @param g1
485    * Graphics object to draw with
486    * @param startRes
487    * start residue of print area
488    * @param endRes
489    * end residue of print area
490    * @param startSeq
491    * start sequence of print area
492    * @param endSeq
493    * end sequence of print area
494    */
 
495  38 toggle public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
496    int startSeq, int endSeq)
497    {
498  38 SequenceRenderer localSeqR = new jalview.gui.SequenceRenderer(av);
499  38 drawPanel(localSeqR, g1, startRes, endRes, startSeq, endSeq, 0);
500   
501  38 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
502    }
503   
504    /**
505    * Draw a wrapped alignment panel for printing
506    *
507    * @param g
508    * Graphics object to draw with
509    * @param canvasWidth
510    * width of drawing area
511    * @param canvasHeight
512    * height of drawing area
513    * @param startRes
514    * start residue of print area
515    */
 
516  0 toggle public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
517    int canvasHeight, int startRes)
518    {
519  0 SequenceRenderer localSeqR = new jalview.gui.SequenceRenderer(av);
520  0 drawWrappedPanel(localSeqR, g, canvasWidth, canvasHeight, startRes);
521   
522  0 SequenceGroup group = av.getSelectionGroup();
523  0 if (group != null)
524    {
525  0 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
526    startRes);
527    }
528    }
529   
530    /**
531    * Returns the visible width of the canvas in residues, after allowing for
532    * East or West scales (if shown)
533    *
534    * @param canvasWidth
535    * the width in pixels (possibly including scales)
536    *
537    * @return
538    */
 
539  645 toggle public int getWrappedCanvasWidth(int canvasWidth)
540    {
541  645 int charWidth = av.getCharWidth();
542   
543  645 FontMetrics fm = getFontMetrics(av.getFont());
544   
545  645 int labelWidth = 0;
546   
547  645 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
548    {
549  535 labelWidth = getLabelWidth(fm);
550    }
551   
552  645 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
553   
554  645 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
555   
556  645 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
557    }
558   
 
559  243 toggle public int getMinimumWrappedCanvasWidth()
560    {
561  243 int charWidth = av.getCharWidth();
562  243 FontMetrics fm = getFontMetrics(av.getFont());
563  243 int labelWidth = 0;
564  243 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
565    {
566  243 labelWidth = getLabelWidth(fm);
567    }
568  243 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
569  243 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
570  243 return labelWidthEast + labelWidthWest + charWidth;
571    }
572   
573    /**
574    * Returns a pixel width sufficient to show the largest sequence coordinate
575    * (end position) in the alignment, calculated as the FontMetrics width of
576    * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
577    * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
578    * half a character width space on either side.
579    *
580    * @param fm
581    * @return
582    */
 
583  778 toggle protected int getLabelWidth(FontMetrics fm)
584    {
585    /*
586    * find the biggest sequence end position we need to show
587    * (note this is not necessarily the sequence length)
588    */
589  778 int maxWidth = 0;
590  778 AlignmentI alignment = av.getAlignment();
591  2875 for (int i = 0; i < alignment.getHeight(); i++)
592    {
593  2097 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
594    }
595   
596  778 int length = 0;
597  3135 for (int i = maxWidth; i > 0; i /= 10)
598    {
599  2357 length++;
600    }
601   
602  778 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
603    }
604   
605    /**
606    * Draws as many widths of a wrapped alignment as can fit in the visible
607    * window
608    *
609    * @param g
610    * @param canvasWidth
611    * available width in pixels
612    * @param canvasHeight
613    * available height in pixels
614    * @param startColumn
615    * the first column (0...) of the alignment to draw
616    */
 
617  239 toggle public void drawWrappedPanel(SequenceRenderer seqRdr, Graphics g,
618    int canvasWidth, int canvasHeight, final int startColumn)
619    {
620  239 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
621    canvasHeight);
622   
623  239 av.setWrappedWidth(wrappedWidthInResidues);
624   
625  239 ViewportRanges ranges = av.getRanges();
626  239 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
627   
628    // we need to call this again to make sure the startColumn +
629    // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
630    // correctly.
631  239 calculateWrappedGeometry(canvasWidth, canvasHeight);
632   
633    /*
634    * draw one width at a time (excluding any scales shown),
635    * until we have run out of either alignment or vertical space available
636    */
637  239 int ypos = wrappedSpaceAboveAlignment;
638  239 int maxWidth = ranges.getVisibleAlignmentWidth();
639   
640  239 int start = startColumn;
641  239 int currentWidth = 0;
642  478 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
643    {
644  239 int endColumn = Math.min(maxWidth,
645    start + wrappedWidthInResidues - 1);
646  239 drawWrappedWidth(seqRdr, g, ypos, start, endColumn, canvasHeight);
647  239 ypos += wrappedRepeatHeightPx;
648  239 start += wrappedWidthInResidues;
649  239 currentWidth++;
650    }
651   
652  239 drawWrappedDecorators(g, startColumn);
653    }
654   
655    /**
656    * Calculates and saves values needed when rendering a wrapped alignment.
657    * These depend on many factors, including
658    * <ul>
659    * <li>canvas width and height</li>
660    * <li>number of visible sequences, and height of annotations if shown</li>
661    * <li>font and character width</li>
662    * <li>whether scales are shown left, right or above the alignment</li>
663    * </ul>
664    *
665    * @param canvasWidth
666    * @param canvasHeight
667    * @return the number of residue columns in each width
668    */
 
669  558 toggle protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
670    {
671  558 int charHeight = av.getCharHeight();
672   
673    /*
674    * vertical space in pixels between wrapped widths of alignment
675    * - one character height, or two if scale above is drawn
676    */
677  558 wrappedSpaceAboveAlignment = charHeight
678  558 * (av.getScaleAboveWrapped() ? 2 : 1);
679   
680    /*
681    * compute height in pixels of the wrapped widths
682    * - start with space above plus sequences
683    */
684  558 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
685  558 wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight;
686   
687    /*
688    * add annotations panel height if shown
689    * also gap between sequences and annotations
690    */
691  558 if (av.isShowAnnotation())
692    {
693  526 wrappedRepeatHeightPx += getAnnotationHeight();
694  526 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
695    }
696   
697    /*
698    * number of visible widths (the last one may be part height),
699    * ensuring a part height includes at least one sequence
700    */
701  558 ViewportRanges ranges = av.getRanges();
702  558 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
703  558 int remainder = canvasHeight % wrappedRepeatHeightPx;
704  558 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
705    {
706  532 wrappedVisibleWidths++;
707    }
708   
709    /*
710    * compute width in residues; this also sets East and West label widths
711    */
712  558 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
713  558 av.setWrappedWidth(wrappedWidthInResidues); // update model accordingly
714    /*
715    * limit visibleWidths to not exceed width of alignment
716    */
717  558 int xMax = ranges.getVisibleAlignmentWidth();
718  558 int startToEnd = xMax - ranges.getStartRes();
719  558 int maxWidths = startToEnd / wrappedWidthInResidues;
720  558 if (startToEnd % wrappedWidthInResidues > 0)
721    {
722  557 maxWidths++;
723    }
724  558 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
725   
726  558 return wrappedWidthInResidues;
727    }
728   
729    /**
730    * Draws one width of a wrapped alignment, including sequences and
731    * annnotations, if shown, but not scales or hidden column markers
732    *
733    * @param g
734    * @param ypos
735    * @param startColumn
736    * @param endColumn
737    * @param canvasHeight
738    */
 
739  239 toggle protected void drawWrappedWidth(SequenceRenderer seqRdr, Graphics g,
740    final int ypos, final int startColumn, final int endColumn,
741    final int canvasHeight)
742    {
743  239 ViewportRanges ranges = av.getRanges();
744  239 int viewportWidth = ranges.getViewportWidth();
745   
746  239 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
747   
748    /*
749    * move right before drawing by the width of the scale left (if any)
750    * plus column offset from left margin (usually zero, but may be non-zero
751    * when fast painting is drawing just a few columns)
752    */
753  239 int charWidth = av.getCharWidth();
754  239 int xOffset = labelWidthWest
755    + ((startColumn - ranges.getStartRes()) % viewportWidth)
756    * charWidth;
757   
758  239 g.translate(xOffset, 0);
759   
760    /*
761    * white fill the region to be drawn (so incremental fast paint doesn't
762    * scribble over an existing image)
763    */
764  239 g.setColor(Color.white);
765  239 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
766    wrappedRepeatHeightPx);
767   
768  239 drawPanel(seqRdr, g, startColumn, endx, 0,
769    av.getAlignment().getHeight() - 1, ypos);
770   
771  239 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
772   
773  239 if (av.isShowAnnotation())
774    {
775  239 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
776  239 g.translate(0, yShift);
777  239 if (annotations == null)
778    {
779  0 annotations = new AnnotationPanel(av);
780    }
781   
782  239 annotations.renderer.drawComponent(annotations, av, g, -1,
783    startColumn, endx + 1);
784  239 g.translate(0, -yShift);
785    }
786  239 g.translate(-xOffset, 0);
787    }
788   
789    /**
790    * Draws scales left, right and above (if shown), and any hidden column
791    * markers, on all widths of the wrapped alignment
792    *
793    * @param g
794    * @param startColumn
795    */
 
796  239 toggle protected void drawWrappedDecorators(Graphics g, final int startColumn)
797    {
798  239 int charWidth = av.getCharWidth();
799   
800  239 g.setFont(av.getFont());
801   
802  239 g.setColor(Color.black);
803   
804  239 int ypos = wrappedSpaceAboveAlignment;
805  239 ViewportRanges ranges = av.getRanges();
806  239 int viewportWidth = ranges.getViewportWidth();
807  239 int maxWidth = ranges.getVisibleAlignmentWidth();
808  239 int widthsDrawn = 0;
809  239 int startCol = startColumn;
810   
811  478 while (widthsDrawn < wrappedVisibleWidths)
812    {
813  239 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
814   
815  239 if (av.getScaleLeftWrapped())
816    {
817  239 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
818    }
819   
820  239 if (av.getScaleRightWrapped())
821    {
822  239 int x = labelWidthWest + viewportWidth * charWidth;
823   
824  239 g.translate(x, 0);
825  239 drawVerticalScale(g, startCol, endColumn, ypos, false);
826  239 g.translate(-x, 0);
827    }
828   
829    /*
830    * white fill region of scale above and hidden column markers
831    * (to support incremental fast paint of image)
832    */
833  239 g.translate(labelWidthWest, 0);
834  239 g.setColor(Color.white);
835  239 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
836    viewportWidth * charWidth + labelWidthWest,
837    wrappedSpaceAboveAlignment);
838  239 g.setColor(Color.black);
839  239 g.translate(-labelWidthWest, 0);
840   
841  239 g.translate(labelWidthWest, 0);
842   
843  239 if (av.getScaleAboveWrapped())
844    {
845  0 drawNorthScale(g, startCol, endColumn, ypos);
846    }
847   
848  239 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
849    {
850  235 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
851    }
852   
853  239 g.translate(-labelWidthWest, 0);
854   
855  239 ypos += wrappedRepeatHeightPx;
856  239 startCol += viewportWidth;
857  239 widthsDrawn++;
858    }
859    }
860   
861    /**
862    * Draws markers (triangles) above hidden column positions between startColumn
863    * and endColumn.
864    *
865    * @param g
866    * @param ypos
867    * @param startColumn
868    * @param endColumn
869    */
 
870  235 toggle protected void drawHiddenColumnMarkers(Graphics g, int ypos,
871    int startColumn, int endColumn)
872    {
873  235 int charHeight = av.getCharHeight();
874  235 int charWidth = av.getCharWidth();
875   
876  235 g.setColor(Color.blue);
877  235 int res;
878  235 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
879   
880  235 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
881    endColumn);
882  470 while (it.hasNext())
883    {
884  235 res = it.next() - startColumn;
885   
886  235 if (res < 0 || res > endColumn - startColumn + 1)
887    {
888  0 continue;
889    }
890   
891    /*
892    * draw a downward-pointing triangle at the hidden columns location
893    * (before the following visible column)
894    */
895  235 int xMiddle = res * charWidth;
896  235 int[] xPoints = new int[] { xMiddle - charHeight / 4,
897    xMiddle + charHeight / 4, xMiddle };
898  235 int yTop = ypos - (charHeight / 2);
899  235 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
900  235 g.fillPolygon(xPoints, yPoints, 3);
901    }
902    }
903   
904    /*
905    * Draw a selection group over a wrapped alignment
906    */
 
907  0 toggle private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
908    int canvasWidth, int canvasHeight, int startRes)
909    {
910    // chop the wrapped alignment extent up into panel-sized blocks and treat
911    // each block as if it were a block from an unwrapped alignment
912  0 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
913    BasicStroke.JOIN_ROUND, 3f, new float[]
914    { 5f, 3f }, 0f));
915  0 g.setColor(Color.RED);
916   
917  0 int charWidth = av.getCharWidth();
918  0 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
919    / charWidth;
920  0 int startx = startRes;
921  0 int maxwidth = av.getAlignment().getVisibleWidth();
922  0 int ypos = wrappedSpaceAboveAlignment;
923   
924  0 while ((ypos <= canvasHeight) && (startx < maxwidth))
925    {
926    // set end value to be start + width, or maxwidth, whichever is smaller
927  0 int endx = startx + cWidth - 1;
928   
929  0 if (endx > maxwidth)
930    {
931  0 endx = maxwidth;
932    }
933   
934  0 g.translate(labelWidthWest, 0);
935  0 drawUnwrappedSelection(g, group, startx, endx, 0,
936    av.getAlignment().getHeight() - 1, ypos);
937  0 g.translate(-labelWidthWest, 0);
938   
939  0 ypos += wrappedRepeatHeightPx;
940   
941  0 startx += cWidth;
942    }
943  0 g.setStroke(new BasicStroke());
944    }
945   
946    /**
947    * Answers zero if annotations are not shown, otherwise recalculates and
948    * answers the total height of all annotation rows in pixels
949    *
950    * @return
951    */
 
952  598 toggle int getAnnotationHeight()
953    {
954  598 if (!av.isShowAnnotation())
955    {
956  15 return 0;
957    }
958   
959  583 if (annotations == null)
960    {
961  13 annotations = new AnnotationPanel(av);
962    }
963   
964  583 return annotations.adjustPanelHeight();
965    }
966   
967    /**
968    * Draws the visible region of the alignment on the graphics context. If there
969    * are hidden column markers in the visible region, then each sub-region
970    * between the markers is drawn separately, followed by the hidden column
971    * marker.
972    *
973    * @param localSeqR
974    * - sequence renderer implementation - when null, uses the one used
975    * for rendering interactive GUI
976    *
977    * @param g1
978    * the graphics context, positioned at the first residue to be drawn
979    * @param startRes
980    * offset of the first column to draw (0..)
981    * @param endRes
982    * offset of the last column to draw (0..)
983    * @param startSeq
984    * offset of the first sequence to draw (0..)
985    * @param endSeq
986    * offset of the last sequence to draw (0..)
987    * @param yOffset
988    * vertical offset at which to draw (for wrapped alignments)
989    */
 
990  2442 toggle public void drawPanel(SequenceRenderer localSeqR, Graphics g1,
991    final int startRes, final int endRes, final int startSeq,
992    final int endSeq, final int yOffset)
993    {
994  2442 int charHeight = av.getCharHeight();
995  2442 int charWidth = av.getCharWidth();
996   
997  2442 if (localSeqR == null)
998    {
999  0 localSeqR = seqRdr;
1000    }
1001  2442 if (!av.hasHiddenColumns())
1002    {
1003  1939 draw(localSeqR, g1, startRes, endRes, startSeq, endSeq, yOffset);
1004    }
1005    else
1006    {
1007  503 int screenY = 0;
1008  503 int blockStart;
1009  503 int blockEnd;
1010   
1011  503 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1012  503 VisibleContigsIterator regions = hidden
1013    .getVisContigsIterator(startRes, endRes + 1, true);
1014   
1015  1050 while (regions.hasNext())
1016    {
1017  547 int[] region = regions.next();
1018  547 blockEnd = region[1];
1019  547 blockStart = region[0];
1020   
1021    /*
1022    * draw up to just before the next hidden region, or the end of
1023    * the visible region, whichever comes first
1024    */
1025  547 g1.translate(screenY * charWidth, 0);
1026   
1027  547 draw(localSeqR, g1, blockStart, blockEnd, startSeq, endSeq,
1028    yOffset);
1029   
1030    /*
1031    * draw the downline of the hidden column marker (ScalePanel draws the
1032    * triangle on top) if we reached it
1033    */
1034  547 if (av.getShowHiddenMarkers()
1035    && (regions.hasNext() || regions.endsAtHidden()))
1036    {
1037  44 g1.setColor(Color.blue);
1038   
1039  44 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1040    0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1041    (endSeq - startSeq + 1) * charHeight + yOffset);
1042    }
1043   
1044  547 g1.translate(-screenY * charWidth, 0);
1045  547 screenY += blockEnd - blockStart + 1;
1046    }
1047    }
1048   
1049    }
1050   
1051    /**
1052    * Draws a region of the visible alignment
1053    *
1054    * @param seqRdr
1055    *
1056    * @param g1
1057    * @param startRes
1058    * offset of the first column in the visible region (0..)
1059    * @param endRes
1060    * offset of the last column in the visible region (0..)
1061    * @param startSeq
1062    * offset of the first sequence in the visible region (0..)
1063    * @param endSeq
1064    * offset of the last sequence in the visible region (0..)
1065    * @param yOffset
1066    * vertical offset at which to draw (for wrapped alignments)
1067    */
 
1068  2486 toggle private void draw(SequenceRenderer seqRdr, Graphics g, int startRes,
1069    int endRes, int startSeq, int endSeq, int offset)
1070    {
1071  2486 int charHeight = av.getCharHeight();
1072  2486 int charWidth = av.getCharWidth();
1073   
1074  2486 g.setFont(av.getFont());
1075  2486 seqRdr.prepare(g, av.isRenderGaps());
1076   
1077  2486 SequenceI nextSeq;
1078   
1079    // / First draw the sequences
1080    // ///////////////////////////
1081  13844 for (int i = startSeq; i <= endSeq; i++)
1082    {
1083  11358 nextSeq = av.getAlignment().getSequenceAt(i);
1084  11358 if (nextSeq == null)
1085    {
1086    // occasionally, a race condition occurs such that the alignment row is
1087    // empty
1088  0 continue;
1089    }
1090  11358 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1091    startRes, endRes, offset + ((i - startSeq) * charHeight));
1092   
1093  11358 if (av.isShowSequenceFeatures())
1094    {
1095  943 fr.drawSequence(g, nextSeq, startRes, endRes,
1096    offset + ((i - startSeq) * charHeight), false);
1097    }
1098   
1099    /*
1100    * highlight search Results once sequence has been drawn
1101    */
1102  11358 if (av.hasSearchResults())
1103    {
1104  52 SearchResultsI searchResults = av.getSearchResults();
1105  52 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1106    endRes);
1107  52 if (visibleResults != null)
1108    {
1109  38 for (int r = 0; r < visibleResults.length; r += 2)
1110    {
1111  19 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1112    visibleResults[r + 1],
1113    (visibleResults[r] - startRes) * charWidth,
1114    offset + ((i - startSeq) * charHeight));
1115    }
1116    }
1117    }
1118    }
1119   
1120  2486 if (av.getSelectionGroup() != null
1121    || av.getAlignment().getGroups().size() > 0)
1122    {
1123  993 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1124    }
1125   
1126    }
1127   
1128    /**
1129    * Draws the outlines of any groups defined on the alignment (excluding the
1130    * current selection group, if any)
1131    *
1132    * @param g1
1133    * @param startRes
1134    * @param endRes
1135    * @param startSeq
1136    * @param endSeq
1137    * @param offset
1138    */
 
1139  993 toggle void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1140    int startSeq, int endSeq, int offset)
1141    {
1142  993 Graphics2D g = (Graphics2D) g1;
1143   
1144  993 SequenceGroup group = null;
1145  993 int groupIndex = -1;
1146   
1147  993 if (av.getAlignment().getGroups().size() > 0)
1148    {
1149  987 group = av.getAlignment().getGroups().get(0);
1150  987 groupIndex = 0;
1151    }
1152   
1153  993 if (group != null)
1154    {
1155  987 do
1156    {
1157  1413 g.setColor(group.getOutlineColour());
1158  1413 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1159    endSeq, offset);
1160   
1161  1413 groupIndex++;
1162  1413 if (groupIndex >= av.getAlignment().getGroups().size())
1163    {
1164  987 break;
1165    }
1166  426 group = av.getAlignment().getGroups().get(groupIndex);
1167  426 } while (groupIndex < av.getAlignment().getGroups().size());
1168    }
1169    }
1170   
1171    /**
1172    * Draws the outline of the current selection group (if any)
1173    *
1174    * @param g
1175    * @param startRes
1176    * @param endRes
1177    * @param startSeq
1178    * @param endSeq
1179    */
 
1180  2620 toggle private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1181    int startSeq, int endSeq)
1182    {
1183  2620 SequenceGroup group = av.getSelectionGroup();
1184  2620 if (group == null)
1185    {
1186  2607 return;
1187    }
1188   
1189  13 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1190    BasicStroke.JOIN_ROUND, 3f, new float[]
1191    { 5f, 3f }, 0f));
1192  13 g.setColor(Color.RED);
1193  13 if (!av.getWrapAlignment())
1194    {
1195  13 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1196    0);
1197    }
1198    else
1199    {
1200  0 drawWrappedSelection(g, group, getWidth(), getHeight(),
1201    av.getRanges().getStartRes());
1202    }
1203  13 g.setStroke(new BasicStroke());
1204    }
1205   
1206    /**
1207    * Draw the cursor as a separate image and overlay
1208    *
1209    * @param startRes
1210    * start residue of area to draw cursor in
1211    * @param endRes
1212    * end residue of area to draw cursor in
1213    * @param startSeq
1214    * start sequence of area to draw cursor in
1215    * @param endSeq
1216    * end sequence of are to draw cursor in
1217    * @return a transparent image of the same size as the sequence canvas, with
1218    * the cursor drawn on it, if any
1219    */
 
1220  0 toggle private void drawCursor(Graphics g, int startRes, int endRes,
1221    int startSeq, int endSeq)
1222    {
1223    // convert the cursorY into a position on the visible alignment
1224  0 int cursor_ypos = cursorY;
1225   
1226    // don't do work unless we have to
1227  0 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1228    {
1229  0 int yoffset = 0;
1230  0 int xoffset = 0;
1231  0 int startx = startRes;
1232  0 int endx = endRes;
1233   
1234    // convert the cursorX into a position on the visible alignment
1235  0 int cursor_xpos = av.getAlignment().getHiddenColumns()
1236    .absoluteToVisibleColumn(cursorX);
1237   
1238  0 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1239    {
1240   
1241  0 if (av.getWrapAlignment())
1242    {
1243    // work out the correct offsets for the cursor
1244  0 int charHeight = av.getCharHeight();
1245  0 int charWidth = av.getCharWidth();
1246  0 int canvasWidth = getWidth();
1247  0 int canvasHeight = getHeight();
1248   
1249    // height gap above each panel
1250  0 int hgap = charHeight;
1251  0 if (av.getScaleAboveWrapped())
1252    {
1253  0 hgap += charHeight;
1254    }
1255   
1256  0 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1257    / charWidth;
1258  0 int cHeight = av.getAlignment().getHeight() * charHeight;
1259   
1260  0 endx = startx + cWidth - 1;
1261  0 int ypos = hgap; // vertical offset
1262   
1263    // iterate down the wrapped panels
1264  0 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1265    {
1266    // update vertical offset
1267  0 ypos += cHeight + getAnnotationHeight() + hgap;
1268   
1269    // update horizontal offset
1270  0 startx += cWidth;
1271  0 endx = startx + cWidth - 1;
1272    }
1273  0 yoffset = ypos;
1274  0 xoffset = labelWidthWest;
1275    }
1276   
1277    // now check if cursor is within range for x values
1278  0 if (cursor_xpos >= startx && cursor_xpos <= endx)
1279    {
1280    // get the character the cursor is drawn at
1281  0 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1282  0 char s = seq.getCharAt(cursorX);
1283   
1284  0 seqRdr.drawCursor(g, s,
1285    xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1286    yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1287    }
1288    }
1289    }
1290    }
1291   
1292    /**
1293    * Draw a selection group over an unwrapped alignment
1294    *
1295    * @param g
1296    * graphics object to draw with
1297    * @param group
1298    * selection group
1299    * @param startRes
1300    * start residue of area to draw
1301    * @param endRes
1302    * end residue of area to draw
1303    * @param startSeq
1304    * start sequence of area to draw
1305    * @param endSeq
1306    * end sequence of area to draw
1307    * @param offset
1308    * vertical offset (used when called from wrapped alignment code)
1309    */
 
1310  13 toggle private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1311    int startRes, int endRes, int startSeq, int endSeq, int offset)
1312    {
1313  13 int charWidth = av.getCharWidth();
1314   
1315  13 if (!av.hasHiddenColumns())
1316    {
1317  13 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1318    offset);
1319    }
1320    else
1321    {
1322    // package into blocks of visible columns
1323  0 int screenY = 0;
1324  0 int blockStart;
1325  0 int blockEnd;
1326   
1327  0 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1328  0 VisibleContigsIterator regions = hidden
1329    .getVisContigsIterator(startRes, endRes + 1, true);
1330  0 while (regions.hasNext())
1331    {
1332  0 int[] region = regions.next();
1333  0 blockEnd = region[1];
1334  0 blockStart = region[0];
1335   
1336  0 g.translate(screenY * charWidth, 0);
1337  0 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1338    endSeq, offset);
1339   
1340  0 g.translate(-screenY * charWidth, 0);
1341  0 screenY += blockEnd - blockStart + 1;
1342    }
1343    }
1344    }
1345   
1346    /**
1347    * Draws part of a selection group outline
1348    *
1349    * @param g
1350    * @param group
1351    * @param startRes
1352    * @param endRes
1353    * @param startSeq
1354    * @param endSeq
1355    * @param verticalOffset
1356    */
 
1357  1426 toggle private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1358    int startRes, int endRes, int startSeq, int endSeq,
1359    int verticalOffset)
1360    {
1361  1426 int charHeight = av.getCharHeight();
1362  1426 int charWidth = av.getCharWidth();
1363  1426 int visWidth = (endRes - startRes + 1) * charWidth;
1364   
1365  1426 int oldY = -1;
1366  1426 int i = 0;
1367  1426 boolean inGroup = false;
1368  1426 int top = -1;
1369  1426 int bottom = -1;
1370  1426 int sy = -1;
1371   
1372  1426 List<SequenceI> seqs = group.getSequences(null);
1373   
1374    // position of start residue of group relative to startRes, in pixels
1375  1426 int sx = (group.getStartRes() - startRes) * charWidth;
1376   
1377    // width of group in pixels
1378  1426 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1379    * charWidth) - 1;
1380   
1381  1426 if (!(sx + xwidth < 0 || sx > visWidth))
1382    {
1383  7751 for (i = startSeq; i <= endSeq; i++)
1384    {
1385  6325 sy = verticalOffset + (i - startSeq) * charHeight;
1386   
1387  6325 if ((sx <= (endRes - startRes) * charWidth)
1388    && seqs.contains(av.getAlignment().getSequenceAt(i)))
1389    {
1390  4460 if ((bottom == -1)
1391    && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1392    {
1393  238 bottom = sy + charHeight;
1394    }
1395   
1396  4460 if (!inGroup)
1397    {
1398  1005 if (((top == -1) && (i == 0)) || !seqs
1399    .contains(av.getAlignment().getSequenceAt(i - 1)))
1400    {
1401  1005 top = sy;
1402    }
1403   
1404  1005 oldY = sy;
1405  1005 inGroup = true;
1406    }
1407    }
1408  1865 else if (inGroup)
1409    {
1410  30 drawVerticals(g, sx, xwidth, visWidth, oldY, bottom);
1411  30 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1412   
1413    // reset top and bottom
1414  30 top = -1;
1415  30 bottom = -1;
1416  30 inGroup = false;
1417    }
1418    }
1419  1426 if (inGroup)
1420    {
1421  975 sy = verticalOffset + ((i - startSeq) * charHeight);
1422  975 drawVerticals(g, sx, xwidth, visWidth, oldY,
1423  975 bottom == -1 ? sy : bottom);
1424  975 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1425    }
1426    }
1427    }
1428   
1429    /**
1430    * Draw horizontal selection group boundaries at top and bottom positions
1431    *
1432    * @param g
1433    * graphics object to draw on
1434    * @param sx
1435    * start x position
1436    * @param xwidth
1437    * width of gap
1438    * @param visWidth
1439    * visWidth maximum available width
1440    * @param top
1441    * position to draw top of group at
1442    * @param bottom
1443    * position to draw bottom of group at
1444    */
 
1445  1005 toggle private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1446    int visWidth, int top, int bottom)
1447    {
1448  1005 int width = xwidth;
1449  1005 int startx = sx;
1450  1005 if (startx < 0)
1451    {
1452  321 width += startx;
1453  321 startx = 0;
1454    }
1455   
1456    // don't let width extend beyond current block, or group extent
1457    // fixes JAL-2672
1458  1005 if (startx + width >= visWidth)
1459    {
1460  980 width = visWidth - startx;
1461    }
1462   
1463  1005 if (top != -1)
1464    {
1465  1005 g.drawLine(startx, top, startx + width, top);
1466    }
1467   
1468  1005 if (bottom != -1)
1469    {
1470  238 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1471    }
1472    }
1473   
1474    /**
1475    * Draw vertical lines at sx and sx+xwidth providing they lie within
1476    * [0,visWidth)
1477    *
1478    * @param g
1479    * graphics object to draw on
1480    * @param sx
1481    * start x position
1482    * @param xwidth
1483    * width of gap
1484    * @param visWidth
1485    * visWidth maximum available width
1486    * @param oldY
1487    * top y value
1488    * @param sy
1489    * bottom y value
1490    */
 
1491  1005 toggle private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1492    int oldY, int sy)
1493    {
1494    // if start position is visible, draw vertical line to left of
1495    // group
1496  1005 if (sx >= 0 && sx < visWidth)
1497    {
1498  684 g.drawLine(sx, oldY, sx, sy - 1);
1499    }
1500   
1501    // if end position is visible, draw vertical line to right of
1502    // group
1503  1005 if (sx + xwidth < visWidth)
1504    {
1505  25 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy - 1);
1506    }
1507    }
1508   
1509    /**
1510    * Highlights search results in the visible region by rendering as white text
1511    * on a black background. Any previous highlighting is removed. Answers true
1512    * if any highlight was left on the visible alignment (so status bar should be
1513    * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1514    * so allows the next repaint to update the whole display.
1515    *
1516    * @param results
1517    * @return
1518    */
 
1519  0 toggle public boolean highlightSearchResults(SearchResultsI results)
1520    {
1521  0 return highlightSearchResults(results, false);
1522   
1523    }
1524   
1525    /**
1526    * Highlights search results in the visible region by rendering as white text
1527    * on a black background. Any previous highlighting is removed. Answers true
1528    * if any highlight was left on the visible alignment (so status bar should be
1529    * set to match), else false.
1530    * <p>
1531    * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1532    * highlighted regions are modified. This speeds up highlighting across linked
1533    * alignments.
1534    * <p>
1535    * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1536    * a wrapped alignment had to be scrolled to show the highlighted region, then
1537    * it should be fully redrawn, otherwise a fast paint can be performed. This
1538    * argument could be removed if fast paint of scrolled wrapped alignment is
1539    * coded in future (JAL-2609).
1540    *
1541    * @param results
1542    * @param doFastPaint
1543    * if true, sets a flag so the next repaint only redraws the modified
1544    * image
1545    * @return
1546    */
 
1547  2 toggle public boolean highlightSearchResults(SearchResultsI results,
1548    boolean doFastPaint)
1549    {
1550  2 if (fastpainting)
1551    {
1552  0 return false;
1553    }
1554  2 boolean wrapped = av.getWrapAlignment();
1555  2 try
1556    {
1557  2 fastPaint = doFastPaint;
1558  2 fastpainting = fastPaint;
1559   
1560    /*
1561    * to avoid redrawing the whole visible region, we instead
1562    * redraw just the minimal regions to remove previous highlights
1563    * and add new ones
1564    */
1565  2 SearchResultsI previous = av.getSearchResults();
1566  2 av.setSearchResults(results);
1567  2 boolean redrawn = false;
1568  2 boolean drawn = false;
1569  2 if (wrapped)
1570    {
1571  0 redrawn = drawMappedPositionsWrapped(previous);
1572  0 drawn = drawMappedPositionsWrapped(results);
1573  0 redrawn |= drawn;
1574    }
1575    else
1576    {
1577  2 redrawn = drawMappedPositions(previous);
1578  2 drawn = drawMappedPositions(results);
1579  2 redrawn |= drawn;
1580    }
1581   
1582    /*
1583    * if highlights were either removed or added, repaint
1584    */
1585  2 if (redrawn)
1586    {
1587  1 repaint();
1588    }
1589   
1590    /*
1591    * return true only if highlights were added
1592    */
1593  2 return drawn;
1594   
1595    } finally
1596    {
1597  2 fastpainting = false;
1598    }
1599    }
1600   
1601    /**
1602    * Redraws the minimal rectangle in the visible region (if any) that includes
1603    * mapped positions of the given search results. Whether or not positions are
1604    * highlighted depends on the SearchResults set on the Viewport. This allows
1605    * this method to be called to either clear or set highlighting. Answers true
1606    * if any positions were drawn (in which case a repaint is still required),
1607    * else false.
1608    *
1609    * @param results
1610    * @return
1611    */
 
1612  4 toggle protected boolean drawMappedPositions(SearchResultsI results)
1613    {
1614  4 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1615    {
1616  3 return false;
1617    }
1618   
1619    /*
1620    * calculate the minimal rectangle to redraw that
1621    * includes both new and existing search results
1622    */
1623  1 int firstSeq = Integer.MAX_VALUE;
1624  1 int lastSeq = -1;
1625  1 int firstCol = Integer.MAX_VALUE;
1626  1 int lastCol = -1;
1627  1 boolean matchFound = false;
1628   
1629  1 ViewportRanges ranges = av.getRanges();
1630  1 int firstVisibleColumn = ranges.getStartRes();
1631  1 int lastVisibleColumn = ranges.getEndRes();
1632  1 AlignmentI alignment = av.getAlignment();
1633  1 if (av.hasHiddenColumns())
1634    {
1635  0 firstVisibleColumn = alignment.getHiddenColumns()
1636    .visibleToAbsoluteColumn(firstVisibleColumn);
1637  0 lastVisibleColumn = alignment.getHiddenColumns()
1638    .visibleToAbsoluteColumn(lastVisibleColumn);
1639    }
1640   
1641  4 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1642    .getEndSeq(); seqNo++)
1643    {
1644  3 SequenceI seq = alignment.getSequenceAt(seqNo);
1645   
1646  3 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1647    lastVisibleColumn);
1648  3 if (visibleResults != null)
1649    {
1650  2 for (int i = 0; i < visibleResults.length - 1; i += 2)
1651    {
1652  1 int firstMatchedColumn = visibleResults[i];
1653  1 int lastMatchedColumn = visibleResults[i + 1];
1654  1 if (firstMatchedColumn <= lastVisibleColumn
1655    && lastMatchedColumn >= firstVisibleColumn)
1656    {
1657    /*
1658    * found a search results match in the visible region -
1659    * remember the first and last sequence matched, and the first
1660    * and last visible columns in the matched positions
1661    */
1662  1 matchFound = true;
1663  1 firstSeq = Math.min(firstSeq, seqNo);
1664  1 lastSeq = Math.max(lastSeq, seqNo);
1665  1 firstMatchedColumn = Math.max(firstMatchedColumn,
1666    firstVisibleColumn);
1667  1 lastMatchedColumn = Math.min(lastMatchedColumn,
1668    lastVisibleColumn);
1669  1 firstCol = Math.min(firstCol, firstMatchedColumn);
1670  1 lastCol = Math.max(lastCol, lastMatchedColumn);
1671    }
1672    }
1673    }
1674    }
1675   
1676  1 if (matchFound)
1677    {
1678  1 if (av.hasHiddenColumns())
1679    {
1680  0 firstCol = alignment.getHiddenColumns()
1681    .absoluteToVisibleColumn(firstCol);
1682  0 lastCol = alignment.getHiddenColumns()
1683    .absoluteToVisibleColumn(lastCol);
1684    }
1685  1 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1686  1 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1687  1 Graphics gg = img.getGraphics();
1688  1 gg.translate(transX, transY);
1689  1 drawPanel(seqRdr, gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1690  1 gg.translate(-transX, -transY);
1691  1 gg.dispose();
1692    }
1693   
1694  1 return matchFound;
1695    }
1696   
 
1697  610 toggle @Override
1698    public void propertyChange(PropertyChangeEvent evt)
1699    {
1700  610 String eventName = evt.getPropertyName();
1701    // jalview.bin.Console.errPrintln(">>SeqCanvas propertyChange " +
1702    // eventName);
1703  610 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1704    {
1705  0 fastPaint = true;
1706  0 repaint();
1707  0 return;
1708    }
1709  610 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1710    {
1711  0 fastPaint = false;
1712    // jalview.bin.Console.errPrintln("!!!! fastPaint false from
1713    // MOVE_VIEWPORT");
1714  0 repaint();
1715  0 return;
1716    }
1717   
1718  610 int scrollX = 0;
1719  610 if (eventName.equals(ViewportRanges.STARTRES)
1720    || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1721    {
1722    // Make sure we're not trying to draw a panel
1723    // larger than the visible window
1724  32 if (eventName.equals(ViewportRanges.STARTRES))
1725    {
1726  32 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1727    }
1728    else
1729    {
1730  0 scrollX = ((int[]) evt.getNewValue())[0]
1731    - ((int[]) evt.getOldValue())[0];
1732    }
1733  32 ViewportRanges vpRanges = av.getRanges();
1734   
1735  32 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1736  32 if (scrollX > range)
1737    {
1738  2 scrollX = range;
1739    }
1740  30 else if (scrollX < -range)
1741    {
1742  0 scrollX = -range;
1743    }
1744    }
1745    // Both scrolling and resizing change viewport ranges: scrolling changes
1746    // both start and end points, but resize only changes end values.
1747    // Here we only want to fastpaint on a scroll, with resize using a normal
1748    // paint, so scroll events are identified as changes to the horizontal or
1749    // vertical start value.
1750  610 if (eventName.equals(ViewportRanges.STARTRES))
1751    {
1752  32 if (av.getWrapAlignment())
1753    {
1754  1 fastPaintWrapped(scrollX);
1755    }
1756    else
1757    {
1758  31 fastPaint(scrollX, 0);
1759    }
1760    }
1761  578 else if (eventName.equals(ViewportRanges.STARTSEQ))
1762    {
1763    // scroll
1764  41 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1765    }
1766  537 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1767    {
1768  0 if (av.getWrapAlignment())
1769    {
1770  0 fastPaintWrapped(scrollX);
1771    }
1772    else
1773    {
1774  0 fastPaint(scrollX, 0);
1775    }
1776    }
1777  537 else if (eventName.equals(ViewportRanges.STARTSEQ))
1778    {
1779    // scroll
1780  0 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1781    }
1782  537 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1783    {
1784  0 if (av.getWrapAlignment())
1785    {
1786  0 fastPaintWrapped(scrollX);
1787    }
1788    }
1789    }
1790   
1791    /**
1792    * Does a minimal update of the image for a scroll movement. This method
1793    * handles scroll movements of up to one width of the wrapped alignment (one
1794    * click in the vertical scrollbar). Larger movements (for example after a
1795    * scroll to highlight a mapped position) trigger a full redraw instead.
1796    *
1797    * @param scrollX
1798    * number of positions scrolled (right if positive, left if negative)
1799    */
 
1800  1 toggle protected void fastPaintWrapped(int scrollX)
1801    {
1802  1 ViewportRanges ranges = av.getRanges();
1803   
1804  1 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1805    {
1806    /*
1807    * shift of one view width or more is
1808    * overcomplicated to handle in this method
1809    */
1810  1 fastPaint = false;
1811  1 repaint();
1812  1 return;
1813    }
1814   
1815  0 if (fastpainting || img == null)
1816    {
1817  0 return;
1818    }
1819   
1820  0 fastPaint = true;
1821  0 fastpainting = true;
1822   
1823  0 try
1824    {
1825   
1826  0 Graphics gg = img.getGraphics();
1827   
1828  0 calculateWrappedGeometry(getWidth(), getHeight());
1829   
1830    /*
1831    * relocate the regions of the alignment that are still visible
1832    */
1833  0 shiftWrappedAlignment(-scrollX);
1834   
1835    /*
1836    * add new columns (sequence, annotation)
1837    * - at top left if scrollX < 0
1838    * - at right of last two widths if scrollX > 0
1839    */
1840  0 if (scrollX < 0)
1841    {
1842  0 int startRes = ranges.getStartRes();
1843  0 drawWrappedWidth(seqRdr, gg, wrappedSpaceAboveAlignment, startRes,
1844    startRes - scrollX - 1, getHeight());
1845    }
1846    else
1847    {
1848  0 fastPaintWrappedAddRight(scrollX);
1849    }
1850   
1851    /*
1852    * draw all scales (if shown) and hidden column markers
1853    */
1854  0 drawWrappedDecorators(gg, ranges.getStartRes());
1855   
1856  0 gg.dispose();
1857   
1858  0 repaint();
1859    } finally
1860    {
1861  0 fastpainting = false;
1862    }
1863    }
1864   
1865    /**
1866    * Draws the specified number of columns at the 'end' (bottom right) of a
1867    * wrapped alignment view, including sequences and annotations if shown, but
1868    * not scales. Also draws the same number of columns at the right hand end of
1869    * the second last width shown, if the last width is not full height (so
1870    * cannot simply be copied from the graphics image).
1871    *
1872    * @param columns
1873    */
 
1874  0 toggle protected void fastPaintWrappedAddRight(int columns)
1875    {
1876  0 if (columns == 0)
1877    {
1878  0 return;
1879    }
1880   
1881  0 Graphics gg = img.getGraphics();
1882   
1883  0 ViewportRanges ranges = av.getRanges();
1884  0 int viewportWidth = ranges.getViewportWidth();
1885  0 int charWidth = av.getCharWidth();
1886   
1887    /**
1888    * draw full height alignment in the second last row, last columns, if the
1889    * last row was not full height
1890    */
1891  0 int visibleWidths = wrappedVisibleWidths;
1892  0 int canvasHeight = getHeight();
1893  0 boolean lastWidthPartHeight = (wrappedVisibleWidths
1894    * wrappedRepeatHeightPx) > canvasHeight;
1895   
1896  0 if (lastWidthPartHeight)
1897    {
1898  0 int widthsAbove = Math.max(0, visibleWidths - 2);
1899  0 int ypos = wrappedRepeatHeightPx * widthsAbove
1900    + wrappedSpaceAboveAlignment;
1901  0 int endRes = ranges.getEndRes();
1902  0 endRes += widthsAbove * viewportWidth;
1903  0 int startRes = endRes - columns;
1904  0 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1905    * charWidth;
1906   
1907    /*
1908    * white fill first to erase annotations
1909    */
1910   
1911  0 gg.translate(xOffset, 0);
1912  0 gg.setColor(Color.white);
1913  0 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1914    wrappedRepeatHeightPx);
1915  0 gg.translate(-xOffset, 0);
1916   
1917  0 drawWrappedWidth(seqRdr, gg, ypos, startRes, endRes, canvasHeight);
1918   
1919    }
1920   
1921    /*
1922    * draw newly visible columns in last wrapped width (none if we
1923    * have reached the end of the alignment)
1924    * y-offset for drawing last width is height of widths above,
1925    * plus one gap row
1926    */
1927  0 int widthsAbove = visibleWidths - 1;
1928  0 int ypos = wrappedRepeatHeightPx * widthsAbove
1929    + wrappedSpaceAboveAlignment;
1930  0 int endRes = ranges.getEndRes();
1931  0 endRes += widthsAbove * viewportWidth;
1932  0 int startRes = endRes - columns + 1;
1933   
1934    /*
1935    * white fill first to erase annotations
1936    */
1937  0 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1938    * charWidth;
1939  0 gg.translate(xOffset, 0);
1940  0 gg.setColor(Color.white);
1941  0 int width = viewportWidth * charWidth - xOffset;
1942  0 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1943  0 gg.translate(-xOffset, 0);
1944   
1945  0 gg.setFont(av.getFont());
1946  0 gg.setColor(Color.black);
1947   
1948  0 if (startRes < ranges.getVisibleAlignmentWidth())
1949    {
1950  0 drawWrappedWidth(seqRdr, gg, ypos, startRes, endRes, canvasHeight);
1951    }
1952   
1953    /*
1954    * and finally, white fill any space below the visible alignment
1955    */
1956  0 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1957  0 if (heightBelow > 0)
1958    {
1959  0 gg.setColor(Color.white);
1960  0 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1961    }
1962  0 gg.dispose();
1963    }
1964   
1965    /**
1966    * Shifts the visible alignment by the specified number of columns - left if
1967    * negative, right if positive. Copies and moves sequences and annotations (if
1968    * shown). Scales, hidden column markers and any newly visible columns must be
1969    * drawn separately.
1970    *
1971    * @param positions
1972    */
 
1973  0 toggle protected void shiftWrappedAlignment(int positions)
1974    {
1975  0 if (positions == 0)
1976    {
1977  0 return;
1978    }
1979   
1980  0 Graphics gg = img.getGraphics();
1981   
1982  0 int charWidth = av.getCharWidth();
1983   
1984  0 int canvasHeight = getHeight();
1985  0 ViewportRanges ranges = av.getRanges();
1986  0 int viewportWidth = ranges.getViewportWidth();
1987  0 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1988    * charWidth;
1989  0 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1990  0 int xMax = ranges.getVisibleAlignmentWidth();
1991   
1992  0 if (positions > 0)
1993    {
1994    /*
1995    * shift right (after scroll left)
1996    * for each wrapped width (starting with the last), copy (width-positions)
1997    * columns from the left margin to the right margin, and copy positions
1998    * columns from the right margin of the row above (if any) to the
1999    * left margin of the current row
2000    */
2001   
2002    /*
2003    * get y-offset of last wrapped width, first row of sequences
2004    */
2005  0 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2006  0 y += wrappedSpaceAboveAlignment;
2007  0 int copyFromLeftStart = labelWidthWest;
2008  0 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2009   
2010  0 while (y >= 0)
2011    {
2012    /*
2013    * shift 'widthToCopy' residues by 'positions' places to the right
2014    */
2015  0 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2016    positions * charWidth, 0);
2017  0 if (y > 0)
2018    {
2019    /*
2020    * copy 'positions' residue from the row above (right hand end)
2021    * to this row's left hand end
2022    */
2023  0 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2024    positions * charWidth, heightToCopy, -widthToCopy,
2025    wrappedRepeatHeightPx);
2026    }
2027   
2028  0 y -= wrappedRepeatHeightPx;
2029    }
2030    }
2031    else
2032    {
2033    /*
2034    * shift left (after scroll right)
2035    * for each wrapped width (starting with the first), copy (width-positions)
2036    * columns from the right margin to the left margin, and copy positions
2037    * columns from the left margin of the row below (if any) to the
2038    * right margin of the current row
2039    */
2040  0 int xpos = av.getRanges().getStartRes();
2041  0 int y = wrappedSpaceAboveAlignment;
2042  0 int copyFromRightStart = labelWidthWest - positions * charWidth;
2043   
2044  0 while (y < canvasHeight)
2045    {
2046  0 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2047    positions * charWidth, 0);
2048  0 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2049    && (xpos + viewportWidth <= xMax))
2050    {
2051  0 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2052    -positions * charWidth, heightToCopy, widthToCopy,
2053    -wrappedRepeatHeightPx);
2054    }
2055  0 y += wrappedRepeatHeightPx;
2056  0 xpos += viewportWidth;
2057    }
2058    }
2059  0 gg.dispose();
2060    }
2061   
2062    /**
2063    * Redraws any positions in the search results in the visible region of a
2064    * wrapped alignment. Any highlights are drawn depending on the search results
2065    * set on the Viewport, not the <code>results</code> argument. This allows
2066    * this method to be called either to clear highlights (passing the previous
2067    * search results), or to draw new highlights.
2068    *
2069    * @param results
2070    * @return
2071    */
 
2072  0 toggle protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2073    {
2074  0 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2075    {
2076  0 return false;
2077    }
2078  0 int charHeight = av.getCharHeight();
2079   
2080  0 boolean matchFound = false;
2081   
2082  0 calculateWrappedGeometry(getWidth(), getHeight());
2083  0 int wrappedWidth = av.getWrappedWidth();
2084  0 int wrappedHeight = wrappedRepeatHeightPx;
2085   
2086  0 ViewportRanges ranges = av.getRanges();
2087  0 int canvasHeight = getHeight();
2088  0 int repeats = canvasHeight / wrappedHeight;
2089  0 if (canvasHeight / wrappedHeight > 0)
2090    {
2091  0 repeats++;
2092    }
2093   
2094  0 int firstVisibleColumn = ranges.getStartRes();
2095  0 int lastVisibleColumn = ranges.getStartRes()
2096    + repeats * ranges.getViewportWidth() - 1;
2097   
2098  0 AlignmentI alignment = av.getAlignment();
2099  0 if (av.hasHiddenColumns())
2100    {
2101  0 firstVisibleColumn = alignment.getHiddenColumns()
2102    .visibleToAbsoluteColumn(firstVisibleColumn);
2103  0 lastVisibleColumn = alignment.getHiddenColumns()
2104    .visibleToAbsoluteColumn(lastVisibleColumn);
2105    }
2106   
2107  0 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2108   
2109  0 Graphics gg = img.getGraphics();
2110   
2111  0 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2112    .getEndSeq(); seqNo++)
2113    {
2114  0 SequenceI seq = alignment.getSequenceAt(seqNo);
2115   
2116  0 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2117    lastVisibleColumn);
2118  0 if (visibleResults != null)
2119    {
2120  0 for (int i = 0; i < visibleResults.length - 1; i += 2)
2121    {
2122  0 int firstMatchedColumn = visibleResults[i];
2123  0 int lastMatchedColumn = visibleResults[i + 1];
2124  0 if (firstMatchedColumn <= lastVisibleColumn
2125    && lastMatchedColumn >= firstVisibleColumn)
2126    {
2127    /*
2128    * found a search results match in the visible region
2129    */
2130  0 firstMatchedColumn = Math.max(firstMatchedColumn,
2131    firstVisibleColumn);
2132  0 lastMatchedColumn = Math.min(lastMatchedColumn,
2133    lastVisibleColumn);
2134   
2135    /*
2136    * draw each mapped position separately (as contiguous positions may
2137    * wrap across lines)
2138    */
2139  0 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2140    {
2141  0 int displayColumn = mappedPos;
2142  0 if (av.hasHiddenColumns())
2143    {
2144  0 displayColumn = alignment.getHiddenColumns()
2145    .absoluteToVisibleColumn(displayColumn);
2146    }
2147   
2148    /*
2149    * transX: offset from left edge of canvas to residue position
2150    */
2151  0 int transX = labelWidthWest
2152    + ((displayColumn - ranges.getStartRes())
2153    % wrappedWidth) * av.getCharWidth();
2154   
2155    /*
2156    * transY: offset from top edge of canvas to residue position
2157    */
2158  0 int transY = gapHeight;
2159  0 transY += (displayColumn - ranges.getStartRes())
2160    / wrappedWidth * wrappedHeight;
2161  0 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2162   
2163    /*
2164    * yOffset is from graphics origin to start of visible region
2165    */
2166  0 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2167  0 if (transY < getHeight())
2168    {
2169  0 matchFound = true;
2170  0 gg.translate(transX, transY);
2171  0 drawPanel(seqRdr, gg, displayColumn, displayColumn, seqNo,
2172    seqNo, yOffset);
2173  0 gg.translate(-transX, -transY);
2174    }
2175    }
2176    }
2177    }
2178    }
2179    }
2180   
2181  0 gg.dispose();
2182   
2183  0 return matchFound;
2184    }
2185   
2186    /**
2187    * Answers the width in pixels of the left scale labels (0 if not shown)
2188    *
2189    * @return
2190    */
 
2191  72 toggle int getLabelWidthWest()
2192    {
2193  72 return labelWidthWest;
2194    }
2195   
2196    }