JAL-2600 fast painting on resize
[jalview.git] / src / jalview / gui / SeqCanvas.java
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.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.viewmodel.ViewportListenerI;
31 import jalview.viewmodel.ViewportRanges;
32
33 import java.awt.BasicStroke;
34 import java.awt.BorderLayout;
35 import java.awt.Color;
36 import java.awt.FontMetrics;
37 import java.awt.Graphics;
38 import java.awt.Graphics2D;
39 import java.awt.RenderingHints;
40 import java.awt.Shape;
41 import java.awt.image.BufferedImage;
42 import java.beans.PropertyChangeEvent;
43 import java.util.List;
44
45 import javax.swing.JComponent;
46
47 /**
48  * DOCUMENT ME!
49  * 
50  * @author $author$
51  * @version $Revision$
52  */
53 public class SeqCanvas extends JComponent implements ViewportListenerI
54 {
55   final FeatureRenderer fr;
56
57   final SequenceRenderer sr;
58
59   BufferedImage img;
60
61   Graphics2D gg;
62
63   int imgWidth;
64
65   int imgHeight;
66
67   AlignViewport av;
68
69   boolean fastPaint = false;
70
71   int LABEL_WEST;
72
73   int LABEL_EAST;
74
75   int cursorX = 0;
76
77   int cursorY = 0;
78
79   /**
80    * Creates a new SeqCanvas object.
81    * 
82    * @param av
83    *          DOCUMENT ME!
84    */
85   public SeqCanvas(AlignmentPanel ap)
86   {
87     this.av = ap.av;
88     updateViewport();
89     fr = new FeatureRenderer(ap);
90     sr = new SequenceRenderer(av);
91     setLayout(new BorderLayout());
92     PaintRefresher.Register(this, av.getSequenceSetId());
93     setBackground(Color.white);
94
95     av.getRanges().addPropertyChangeListener(this);
96   }
97
98   public SequenceRenderer getSequenceRenderer()
99   {
100     return sr;
101   }
102
103   public FeatureRenderer getFeatureRenderer()
104   {
105     return fr;
106   }
107
108   int charHeight = 0, charWidth = 0;
109
110   private void updateViewport()
111   {
112     charHeight = av.getCharHeight();
113     charWidth = av.getCharWidth();
114   }
115
116   /**
117    * DOCUMENT ME!
118    * 
119    * @param g
120    *          DOCUMENT ME!
121    * @param startx
122    *          DOCUMENT ME!
123    * @param endx
124    *          DOCUMENT ME!
125    * @param ypos
126    *          DOCUMENT ME!
127    */
128   private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
129   {
130     updateViewport();
131     for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
132             endx))
133     {
134       int mpos = mark.column; // (i - startx - 1)
135       if (mpos < 0)
136       {
137         continue;
138       }
139       String mstring = mark.text;
140
141       if (mark.major)
142       {
143         if (mstring != null)
144         {
145           g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
146         }
147         g.drawLine((mpos * charWidth) + (charWidth / 2), (ypos + 2)
148                 - (charHeight / 2), (mpos * charWidth) + (charWidth / 2),
149                 ypos - 2);
150       }
151     }
152   }
153
154   /**
155    * DOCUMENT ME!
156    * 
157    * @param g
158    *          DOCUMENT ME!
159    * @param startx
160    *          DOCUMENT ME!
161    * @param endx
162    *          DOCUMENT ME!
163    * @param ypos
164    *          DOCUMENT ME!
165    */
166   void drawWestScale(Graphics g, int startx, int endx, int ypos)
167   {
168     FontMetrics fm = getFontMetrics(av.getFont());
169     ypos += charHeight;
170
171     if (av.hasHiddenColumns())
172     {
173       startx = av.getAlignment().getHiddenColumns()
174               .adjustForHiddenColumns(startx);
175       endx = av.getAlignment().getHiddenColumns()
176               .adjustForHiddenColumns(endx);
177     }
178
179     int maxwidth = av.getAlignment().getWidth();
180     if (av.hasHiddenColumns())
181     {
182       maxwidth = av.getAlignment().getHiddenColumns()
183               .findColumnPosition(maxwidth) - 1;
184     }
185
186     // WEST SCALE
187     for (int i = 0; i < av.getAlignment().getHeight(); i++)
188     {
189       SequenceI seq = av.getAlignment().getSequenceAt(i);
190       int index = startx;
191       int value = -1;
192
193       while (index < endx)
194       {
195         if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
196         {
197           index++;
198
199           continue;
200         }
201
202         value = av.getAlignment().getSequenceAt(i).findPosition(index);
203
204         break;
205       }
206
207       if (value != -1)
208       {
209         int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
210                 - charWidth / 2;
211         g.drawString(value + "", x, (ypos + (i * charHeight))
212                 - (charHeight / 5));
213       }
214     }
215   }
216
217   /**
218    * DOCUMENT ME!
219    * 
220    * @param g
221    *          DOCUMENT ME!
222    * @param startx
223    *          DOCUMENT ME!
224    * @param endx
225    *          DOCUMENT ME!
226    * @param ypos
227    *          DOCUMENT ME!
228    */
229   void drawEastScale(Graphics g, int startx, int endx, int ypos)
230   {
231     ypos += charHeight;
232
233     if (av.hasHiddenColumns())
234     {
235       endx = av.getAlignment().getHiddenColumns()
236               .adjustForHiddenColumns(endx);
237     }
238
239     SequenceI seq;
240     // EAST SCALE
241     for (int i = 0; i < av.getAlignment().getHeight(); i++)
242     {
243       seq = av.getAlignment().getSequenceAt(i);
244       int index = endx;
245       int value = -1;
246
247       while (index > startx)
248       {
249         if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
250         {
251           index--;
252
253           continue;
254         }
255
256         value = seq.findPosition(index);
257
258         break;
259       }
260
261       if (value != -1)
262       {
263         g.drawString(String.valueOf(value), 0, (ypos + (i * charHeight))
264                 - (charHeight / 5));
265       }
266     }
267   }
268
269   boolean fastpainting = false;
270
271   /**
272    * need to make this thread safe move alignment rendering in response to
273    * slider adjustment
274    * 
275    * @param horizontal
276    *          shift along
277    * @param vertical
278    *          shift up or down in repaint
279    */
280   public void fastPaint(int horizontal, int vertical, boolean isresize)
281   {
282     if (fastpainting || gg == null)
283     {
284       return;
285     }
286     fastpainting = true;
287     fastPaint = true;
288     updateViewport();
289
290     ViewportRanges ranges = av.getRanges();
291     int sr = ranges.getStartRes();
292     int er = ranges.getEndRes();
293     int ss = ranges.getStartSeq();
294     int es = ranges.getEndSeq();
295     int transX = 0;
296     int transY = 0;
297
298     if (isresize)
299     {
300       imgWidth = getWidth();
301       imgHeight = getHeight();
302
303       imgWidth -= (imgWidth % charWidth);
304       imgHeight -= (imgHeight % charHeight);
305
306       BufferedImage newimg = new BufferedImage(imgWidth, imgHeight,
307               BufferedImage.TYPE_INT_ARGB);
308
309       gg = (Graphics2D) newimg.getGraphics();
310       gg.setFont(av.getFont());
311       gg.drawImage(img, null, 0, 0);
312       img = newimg;
313
314       transX = (er - horizontal - sr) * charWidth;
315       sr = er - horizontal - sr;
316     }
317     else
318     {
319       gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
320               imgHeight, -horizontal * charWidth, -vertical * charHeight);
321
322       if (horizontal > 0) // scrollbar pulled right, image to the left
323       {
324         transX = (er - sr - horizontal) * charWidth;
325         sr = er - horizontal;
326       }
327       else if (horizontal < 0)
328       {
329         er = sr - horizontal;
330       }
331       else if (vertical > 0) // scroll down
332       {
333         ss = es - vertical;
334
335         if (ss < ranges.getStartSeq())
336         { // ie scrolling too fast, more than a page at a time
337           ss = ranges.getStartSeq();
338         }
339         else
340         {
341           transY = imgHeight - ((vertical + 1) * charHeight);
342         }
343       }
344       else if (vertical < 0)
345       {
346         es = ss - vertical;
347
348         if (es > ranges.getEndSeq())
349         {
350           es = ranges.getEndSeq();
351         }
352       }
353     }
354
355     gg.translate(transX, transY);
356     drawPanel(gg, sr, er, ss, es, 0);
357     gg.translate(-transX, -transY);
358
359     repaint();
360     fastpainting = false;
361   }
362
363   /**
364    * Definitions of startx and endx (hopefully): SMJS This is what I'm working
365    * towards! startx is the first residue (starting at 0) to display. endx is
366    * the last residue to display (starting at 0). starty is the first sequence
367    * to display (starting at 0). endy is the last sequence to display (starting
368    * at 0). NOTE 1: The av limits are set in setFont in this class and in the
369    * adjustment listener in SeqPanel when the scrollbars move.
370    */
371
372   // Set this to false to force a full panel paint
373   @Override
374   public void paintComponent(Graphics g)
375   {
376     updateViewport();
377     BufferedImage lcimg = img; // take reference since other threads may null
378     // img and call later.
379     super.paintComponent(g);
380
381     if (lcimg != null
382             && (fastPaint
383                     || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
384                     .getClipBounds().height)))
385     {
386       g.drawImage(lcimg, 0, 0, this);
387       fastPaint = false;
388       return;
389     }
390
391     // this draws the whole of the alignment
392     imgWidth = getWidth();
393     imgHeight = getHeight();
394
395     imgWidth -= (imgWidth % charWidth);
396     imgHeight -= (imgHeight % charHeight);
397
398     if ((imgWidth < 1) || (imgHeight < 1))
399     {
400       return;
401     }
402
403     if (lcimg == null || imgWidth != lcimg.getWidth()
404             || imgHeight != lcimg.getHeight())
405     {
406       try
407       {
408         lcimg = img = new BufferedImage(imgWidth, imgHeight,
409                 BufferedImage.TYPE_INT_RGB);
410         gg = (Graphics2D) img.getGraphics();
411         gg.setFont(av.getFont());
412       } catch (OutOfMemoryError er)
413       {
414         System.gc();
415         System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
416         new OOMWarning("Creating alignment image for display", er);
417
418         return;
419       }
420     }
421
422     if (av.antiAlias)
423     {
424       gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
425               RenderingHints.VALUE_ANTIALIAS_ON);
426     }
427
428     gg.setColor(Color.white);
429     gg.fillRect(0, 0, imgWidth, imgHeight);
430
431     ViewportRanges ranges = av.getRanges();
432     if (av.getWrapAlignment())
433     {
434       drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
435     }
436     else
437     {
438       drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
439               ranges.getStartSeq(), ranges.getEndSeq(), 0);
440     }
441
442     g.drawImage(lcimg, 0, 0, this);
443
444   }
445
446   /**
447    * DOCUMENT ME!
448    * 
449    * @param cwidth
450    *          DOCUMENT ME!
451    * 
452    * @return DOCUMENT ME!
453    */
454   public int getWrappedCanvasWidth(int cwidth)
455   {
456     FontMetrics fm = getFontMetrics(av.getFont());
457
458     LABEL_EAST = 0;
459     LABEL_WEST = 0;
460
461     if (av.getScaleRightWrapped())
462     {
463       LABEL_EAST = fm.stringWidth(getMask());
464     }
465
466     if (av.getScaleLeftWrapped())
467     {
468       LABEL_WEST = fm.stringWidth(getMask());
469     }
470
471     return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
472   }
473
474   /**
475    * Generates a string of zeroes.
476    * 
477    * @return String
478    */
479   String getMask()
480   {
481     String mask = "00";
482     int maxWidth = 0;
483     int tmp;
484     for (int i = 0; i < av.getAlignment().getHeight(); i++)
485     {
486       tmp = av.getAlignment().getSequenceAt(i).getEnd();
487       if (tmp > maxWidth)
488       {
489         maxWidth = tmp;
490       }
491     }
492
493     for (int i = maxWidth; i > 0; i /= 10)
494     {
495       mask += "0";
496     }
497     return mask;
498   }
499
500   /**
501    * DOCUMENT ME!
502    * 
503    * @param g
504    *          DOCUMENT ME!
505    * @param canvasWidth
506    *          DOCUMENT ME!
507    * @param canvasHeight
508    *          DOCUMENT ME!
509    * @param startRes
510    *          DOCUMENT ME!
511    */
512   public void drawWrappedPanel(Graphics g, int canvasWidth,
513           int canvasHeight, int startRes)
514   {
515     updateViewport();
516     AlignmentI al = av.getAlignment();
517
518     FontMetrics fm = getFontMetrics(av.getFont());
519
520     if (av.getScaleRightWrapped())
521     {
522       LABEL_EAST = fm.stringWidth(getMask());
523     }
524
525     if (av.getScaleLeftWrapped())
526     {
527       LABEL_WEST = fm.stringWidth(getMask());
528     }
529
530     int hgap = charHeight;
531     if (av.getScaleAboveWrapped())
532     {
533       hgap += charHeight;
534     }
535
536     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
537     int cHeight = av.getAlignment().getHeight() * charHeight;
538
539     av.setWrappedWidth(cWidth);
540
541     av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
542
543     int endx;
544     int ypos = hgap;
545     int maxwidth = av.getAlignment().getWidth() - 1;
546
547     if (av.hasHiddenColumns())
548     {
549       maxwidth = av.getAlignment().getHiddenColumns()
550               .findColumnPosition(maxwidth) - 1;
551     }
552
553     while ((ypos <= canvasHeight) && (startRes < maxwidth))
554     {
555       endx = startRes + cWidth - 1;
556
557       if (endx > maxwidth)
558       {
559         endx = maxwidth;
560       }
561
562       g.setFont(av.getFont());
563       g.setColor(Color.black);
564
565       if (av.getScaleLeftWrapped())
566       {
567         drawWestScale(g, startRes, endx, ypos);
568       }
569
570       if (av.getScaleRightWrapped())
571       {
572         g.translate(canvasWidth - LABEL_EAST, 0);
573         drawEastScale(g, startRes, endx, ypos);
574         g.translate(-(canvasWidth - LABEL_EAST), 0);
575       }
576
577       g.translate(LABEL_WEST, 0);
578
579       if (av.getScaleAboveWrapped())
580       {
581         drawNorthScale(g, startRes, endx, ypos);
582       }
583
584       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
585       {
586         g.setColor(Color.blue);
587         int res;
588         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
589         List<Integer> positions = hidden.findHiddenRegionPositions();
590         for (int pos : positions)
591         {
592           res = pos - startRes;
593
594           if (res < 0 || res > endx - startRes)
595           {
596             continue;
597           }
598
599           gg.fillPolygon(
600                   new int[] { res * charWidth - charHeight / 4,
601                       res * charWidth + charHeight / 4, res * charWidth },
602                   new int[] { ypos - (charHeight / 2),
603                       ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
604                   3);
605
606         }
607       }
608
609       // When printing we have an extra clipped region,
610       // the Printable page which we need to account for here
611       Shape clip = g.getClip();
612
613       if (clip == null)
614       {
615         g.setClip(0, 0, cWidth * charWidth, canvasHeight);
616       }
617       else
618       {
619         g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
620                 (int) clip.getBounds().getHeight());
621       }
622
623       drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
624
625       if (av.isShowAnnotation())
626       {
627         g.translate(0, cHeight + ypos + 3);
628         if (annotations == null)
629         {
630           annotations = new AnnotationPanel(av);
631         }
632
633         annotations.renderer.drawComponent(annotations, av, g, -1,
634                 startRes, endx + 1);
635         g.translate(0, -cHeight - ypos - 3);
636       }
637       g.setClip(clip);
638       g.translate(-LABEL_WEST, 0);
639
640       ypos += cHeight + getAnnotationHeight() + hgap;
641
642       startRes += cWidth;
643     }
644   }
645
646   AnnotationPanel annotations;
647
648   int getAnnotationHeight()
649   {
650     if (!av.isShowAnnotation())
651     {
652       return 0;
653     }
654
655     if (annotations == null)
656     {
657       annotations = new AnnotationPanel(av);
658     }
659
660     return annotations.adjustPanelHeight();
661   }
662
663   /**
664    * DOCUMENT ME!
665    * 
666    * @param g1
667    *          DOCUMENT ME!
668    * @param startRes
669    *          DOCUMENT ME!
670    * @param endRes
671    *          DOCUMENT ME!
672    * @param startSeq
673    *          DOCUMENT ME!
674    * @param endSeq
675    *          DOCUMENT ME!
676    * @param offset
677    *          DOCUMENT ME!
678    */
679   public void drawPanel(Graphics g1, int startRes, int endRes,
680           int startSeq, int endSeq, int offset)
681   {
682     updateViewport();
683     if (!av.hasHiddenColumns())
684     {
685       draw(g1, startRes, endRes, startSeq, endSeq, offset);
686     }
687     else
688     {
689       int screenY = 0;
690       int blockStart = startRes;
691       int blockEnd = endRes;
692
693       for (int[] region : av.getAlignment().getHiddenColumns()
694               .getHiddenColumnsCopy())
695       {
696         int hideStart = region[0];
697         int hideEnd = region[1];
698
699         if (hideStart <= blockStart)
700         {
701           blockStart += (hideEnd - hideStart) + 1;
702           continue;
703         }
704
705         blockEnd = hideStart - 1;
706
707         g1.translate(screenY * charWidth, 0);
708
709         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
710
711         if (av.getShowHiddenMarkers())
712         {
713           g1.setColor(Color.blue);
714
715           g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
716                   0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
717                   (endSeq - startSeq + 1) * charHeight + offset);
718         }
719
720         g1.translate(-screenY * charWidth, 0);
721         screenY += blockEnd - blockStart + 1;
722         blockStart = hideEnd + 1;
723
724         if (screenY > (endRes - startRes))
725         {
726           // already rendered last block
727           return;
728         }
729       }
730
731       if (screenY <= (endRes - startRes))
732       {
733         // remaining visible region to render
734         blockEnd = blockStart + (endRes - startRes) - screenY;
735         g1.translate(screenY * charWidth, 0);
736         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
737
738         g1.translate(-screenY * charWidth, 0);
739       }
740     }
741
742   }
743
744   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
745   // int x1, int x2, int y1, int y2, int startx, int starty,
746   private void draw(Graphics g, int startRes, int endRes, int startSeq,
747           int endSeq, int offset)
748   {
749     g.setFont(av.getFont());
750     sr.prepare(g, av.isRenderGaps());
751
752     SequenceI nextSeq;
753
754     // / First draw the sequences
755     // ///////////////////////////
756     for (int i = startSeq; i <= endSeq; i++)
757     {
758       nextSeq = av.getAlignment().getSequenceAt(i);
759       if (nextSeq == null)
760       {
761         // occasionally, a race condition occurs such that the alignment row is
762         // empty
763         continue;
764       }
765       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
766               startRes, endRes, offset + ((i - startSeq) * charHeight));
767
768       if (av.isShowSequenceFeatures())
769       {
770         fr.drawSequence(g, nextSeq, startRes, endRes, offset
771                 + ((i - startSeq) * charHeight), false);
772       }
773
774       // / Highlight search Results once all sequences have been drawn
775       // ////////////////////////////////////////////////////////
776       if (av.hasSearchResults())
777       {
778         int[] visibleResults = av.getSearchResults().getResults(nextSeq,
779                 startRes, endRes);
780         if (visibleResults != null)
781         {
782           for (int r = 0; r < visibleResults.length; r += 2)
783           {
784             sr.drawHighlightedText(nextSeq, visibleResults[r],
785                     visibleResults[r + 1], (visibleResults[r] - startRes)
786                             * charWidth, offset
787                             + ((i - startSeq) * charHeight));
788           }
789         }
790       }
791
792       if (av.cursorMode && cursorY == i && cursorX >= startRes
793               && cursorX <= endRes)
794       {
795         sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
796                 offset + ((i - startSeq) * charHeight));
797       }
798     }
799
800     if (av.getSelectionGroup() != null
801             || av.getAlignment().getGroups().size() > 0)
802     {
803       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
804     }
805
806   }
807
808   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
809           int startSeq, int endSeq, int offset)
810   {
811     Graphics2D g = (Graphics2D) g1;
812     //
813     // ///////////////////////////////////
814     // Now outline any areas if necessary
815     // ///////////////////////////////////
816     SequenceGroup group = av.getSelectionGroup();
817
818     int sx = -1;
819     int sy = -1;
820     int ex = -1;
821     int groupIndex = -1;
822     int visWidth = (endRes - startRes + 1) * charWidth;
823
824     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
825     {
826       group = av.getAlignment().getGroups().get(0);
827       groupIndex = 0;
828     }
829
830     if (group != null)
831     {
832       do
833       {
834         int oldY = -1;
835         int i = 0;
836         boolean inGroup = false;
837         int top = -1;
838         int bottom = -1;
839
840         for (i = startSeq; i <= endSeq; i++)
841         {
842           sx = (group.getStartRes() - startRes) * charWidth;
843           sy = offset + ((i - startSeq) * charHeight);
844           ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
845
846           if (sx + ex < 0 || sx > visWidth)
847           {
848             continue;
849           }
850
851           if ((sx <= (endRes - startRes) * charWidth)
852                   && group.getSequences(null).contains(
853                           av.getAlignment().getSequenceAt(i)))
854           {
855             if ((bottom == -1)
856                     && !group.getSequences(null).contains(
857                             av.getAlignment().getSequenceAt(i + 1)))
858             {
859               bottom = sy + charHeight;
860             }
861
862             if (!inGroup)
863             {
864               if (((top == -1) && (i == 0))
865                       || !group.getSequences(null).contains(
866                               av.getAlignment().getSequenceAt(i - 1)))
867               {
868                 top = sy;
869               }
870
871               oldY = sy;
872               inGroup = true;
873
874               if (group == av.getSelectionGroup())
875               {
876                 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
877                         BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
878                         0f));
879                 g.setColor(Color.RED);
880               }
881               else
882               {
883                 g.setStroke(new BasicStroke());
884                 g.setColor(group.getOutlineColour());
885               }
886             }
887           }
888           else
889           {
890             if (inGroup)
891             {
892               if (sx >= 0 && sx < visWidth)
893               {
894                 g.drawLine(sx, oldY, sx, sy);
895               }
896
897               if (sx + ex < visWidth)
898               {
899                 g.drawLine(sx + ex, oldY, sx + ex, sy);
900               }
901
902               if (sx < 0)
903               {
904                 ex += sx;
905                 sx = 0;
906               }
907
908               if (sx + ex > visWidth)
909               {
910                 ex = visWidth;
911               }
912
913               else if (sx + ex >= (endRes - startRes + 1) * charWidth)
914               {
915                 ex = (endRes - startRes + 1) * charWidth;
916               }
917
918               if (top != -1)
919               {
920                 g.drawLine(sx, top, sx + ex, top);
921                 top = -1;
922               }
923
924               if (bottom != -1)
925               {
926                 g.drawLine(sx, bottom, sx + ex, bottom);
927                 bottom = -1;
928               }
929
930               inGroup = false;
931             }
932           }
933         }
934
935         if (inGroup)
936         {
937           sy = offset + ((i - startSeq) * charHeight);
938           if (sx >= 0 && sx < visWidth)
939           {
940             g.drawLine(sx, oldY, sx, sy);
941           }
942
943           if (sx + ex < visWidth)
944           {
945             g.drawLine(sx + ex, oldY, sx + ex, sy);
946           }
947
948           if (sx < 0)
949           {
950             ex += sx;
951             sx = 0;
952           }
953
954           if (sx + ex > visWidth)
955           {
956             ex = visWidth;
957           }
958           else if (sx + ex >= (endRes - startRes + 1) * charWidth)
959           {
960             ex = (endRes - startRes + 1) * charWidth;
961           }
962
963           if (top != -1)
964           {
965             g.drawLine(sx, top, sx + ex, top);
966             top = -1;
967           }
968
969           if (bottom != -1)
970           {
971             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
972             bottom = -1;
973           }
974
975           inGroup = false;
976         }
977
978         groupIndex++;
979
980         g.setStroke(new BasicStroke());
981
982         if (groupIndex >= av.getAlignment().getGroups().size())
983         {
984           break;
985         }
986
987         group = av.getAlignment().getGroups().get(groupIndex);
988
989       } while (groupIndex < av.getAlignment().getGroups().size());
990
991     }
992
993   }
994
995   /**
996    * DOCUMENT ME!
997    * 
998    * @param results
999    *          DOCUMENT ME!
1000    */
1001   public void highlightSearchResults(SearchResultsI results)
1002   {
1003     img = null;
1004
1005     av.setSearchResults(results);
1006
1007     repaint();
1008   }
1009
1010   @Override
1011   public void propertyChange(PropertyChangeEvent evt)
1012   {
1013     if (!av.getWrapAlignment())
1014     {
1015       if (evt.getPropertyName().equals("startres"))
1016       {
1017         // scroll - startres and endres both change
1018
1019         // Make sure we're not trying to draw a panel
1020         // larger than the visible window
1021         ViewportRanges vpRanges = av.getRanges();
1022         int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1023         if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
1024         {
1025           scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
1026         }
1027         else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
1028         {
1029           scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
1030         }
1031         fastPaint(scrollX, 0, false);
1032       }
1033       else if (evt.getPropertyName().equals("endres"))
1034       {
1035         // resize - only endres changes
1036         int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1037         fastPaint(scrollX, 0, true);
1038       }
1039       else if (evt.getPropertyName().equals("startseq"))
1040       {
1041         // scroll
1042         fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue(), false);
1043       }
1044       else if (evt.getPropertyName().equals("endseq"))
1045       {
1046         // resize
1047       }
1048     }
1049   }
1050 }