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