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