Clover icon

Coverage Report

  1. Project Clover database Wed Dec 3 2025 16:47:11 GMT
  2. Package jalview.gui

File SeqCanvas.java

 

Coverage histogram

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

Code metrics

282
683
37
1
2,201
1,275
211
0.31
18.46
37
5.7

Classes

Class Line # Actions
SeqCanvas 58 683 211
0.6367265663.7%
 

Contributing tests

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