Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.gui

File SeqCanvas.java

 

Coverage histogram

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

Code metrics

260
688
40
1
2,309
1,268
211
0.31
17.2
40
5.28

Classes

Class Line # Actions
SeqCanvas 59 688 211
0.685222768.5%
 

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