Clover icon

Coverage Report

  1. Project Clover database Mon Dec 1 2025 15:35:32 GMT
  2. Package jalview.gui

File SeqCanvas.java

 

Coverage histogram

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

Code metrics

282
680
37
1
2,197
1,272
211
0.31
18.38
37
5.7

Classes

Class Line # Actions
SeqCanvas 58 680 211
0.629629663%
 

Contributing tests

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