Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.gui

File SeqCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart6.png
33% of files have more coverage

Code metrics

272
666
36
1
2,168
1,248
204
0.31
18.5
36
5.67

Classes

Class Line # Actions
SeqCanvas 57 666 204
0.5944558459.4%
 

Contributing tests

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