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