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