JAL-845 code/refactoring/tests related to linking DNA and protein
[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 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.showAnnotation)
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.showAnnotation)
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   void drawPanel(Graphics g1, int startRes, int endRes, int startSeq,
632           int endSeq, int offset)
633   {
634     if (!av.hasHiddenColumns())
635     {
636       draw(g1, startRes, endRes, startSeq, endSeq, offset);
637     }
638     else
639     {
640       List<int[]> regions = av.getColumnSelection().getHiddenColumns();
641
642       int screenY = 0;
643       int blockStart = startRes;
644       int blockEnd = endRes;
645
646       for (int[] region : regions)
647       {
648         int hideStart = region[0];
649         int hideEnd = region[1];
650
651         if (hideStart <= blockStart)
652         {
653           blockStart += (hideEnd - hideStart) + 1;
654           continue;
655         }
656
657         blockEnd = hideStart - 1;
658
659         g1.translate(screenY * av.charWidth, 0);
660
661         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
662
663         if (av.getShowHiddenMarkers())
664         {
665           g1.setColor(Color.blue);
666
667           g1.drawLine((blockEnd - blockStart + 1) * av.charWidth - 1,
668                   0 + offset, (blockEnd - blockStart + 1) * av.charWidth
669                           - 1, (endSeq - startSeq) * av.charHeight + offset);
670         }
671
672         g1.translate(-screenY * av.charWidth, 0);
673         screenY += blockEnd - blockStart + 1;
674         blockStart = hideEnd + 1;
675       }
676
677       if (screenY <= (endRes - startRes))
678       {
679         blockEnd = blockStart + (endRes - startRes) - screenY;
680         g1.translate(screenY * av.charWidth, 0);
681         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
682
683         g1.translate(-screenY * av.charWidth, 0);
684       }
685     }
686
687   }
688
689   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
690   // int x1, int x2, int y1, int y2, int startx, int starty,
691   void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
692           int offset)
693   {
694     g.setFont(av.getFont());
695     sr.prepare(g, av.renderGaps);
696
697     SequenceI nextSeq;
698
699     // / First draw the sequences
700     // ///////////////////////////
701     for (int i = startSeq; i < endSeq; i++)
702     {
703       nextSeq = av.getAlignment().getSequenceAt(i);
704       if (nextSeq == null)
705       {
706         // occasionally, a race condition occurs such that the alignment row is
707         // empty
708         continue;
709       }
710       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
711               startRes, endRes, offset + ((i - startSeq) * av.charHeight));
712
713       if (av.showSequenceFeatures)
714       {
715         fr.drawSequence(g, nextSeq, startRes, endRes, offset
716                 + ((i - startSeq) * av.charHeight));
717       }
718
719       // / Highlight search Results once all sequences have been drawn
720       // ////////////////////////////////////////////////////////
721       if (searchResults != null)
722       {
723         int[] visibleResults = searchResults.getResults(nextSeq, startRes,
724                 endRes);
725         if (visibleResults != null)
726         {
727           for (int r = 0; r < visibleResults.length; r += 2)
728           {
729             sr.drawHighlightedText(nextSeq, visibleResults[r],
730                     visibleResults[r + 1], (visibleResults[r] - startRes)
731                             * av.charWidth, offset
732                             + ((i - startSeq) * av.charHeight));
733           }
734         }
735       }
736
737       if (av.cursorMode && cursorY == i && cursorX >= startRes
738               && cursorX <= endRes)
739       {
740         sr.drawCursor(nextSeq, cursorX,
741                 (cursorX - startRes) * av.charWidth, offset
742                         + ((i - startSeq) * av.charHeight));
743       }
744     }
745
746     if (av.getSelectionGroup() != null
747             || av.getAlignment().getGroups().size() > 0)
748     {
749       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
750     }
751
752   }
753
754   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
755           int startSeq, int endSeq, int offset)
756   {
757     Graphics2D g = (Graphics2D) g1;
758     //
759     // ///////////////////////////////////
760     // Now outline any areas if necessary
761     // ///////////////////////////////////
762     SequenceGroup group = av.getSelectionGroup();
763
764     int sx = -1;
765     int sy = -1;
766     int ex = -1;
767     int groupIndex = -1;
768     int visWidth = (endRes - startRes + 1) * av.charWidth;
769
770     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
771     {
772       group = av.getAlignment().getGroups().get(0);
773       groupIndex = 0;
774     }
775
776     if (group != null)
777     {
778       do
779       {
780         int oldY = -1;
781         int i = 0;
782         boolean inGroup = false;
783         int top = -1;
784         int bottom = -1;
785
786         for (i = startSeq; i < endSeq; i++)
787         {
788           sx = (group.getStartRes() - startRes) * av.charWidth;
789           sy = offset + ((i - startSeq) * av.charHeight);
790           ex = (((group.getEndRes() + 1) - group.getStartRes()) * av.charWidth) - 1;
791
792           if (sx + ex < 0 || sx > visWidth)
793           {
794             continue;
795           }
796
797           if ((sx <= (endRes - startRes) * av.charWidth)
798                   && group.getSequences(null).contains(
799                           av.getAlignment().getSequenceAt(i)))
800           {
801             if ((bottom == -1)
802                     && !group.getSequences(null).contains(
803                             av.getAlignment().getSequenceAt(i + 1)))
804             {
805               bottom = sy + av.charHeight;
806             }
807
808             if (!inGroup)
809             {
810               if (((top == -1) && (i == 0))
811                       || !group.getSequences(null).contains(
812                               av.getAlignment().getSequenceAt(i - 1)))
813               {
814                 top = sy;
815               }
816
817               oldY = sy;
818               inGroup = true;
819
820               if (group == av.getSelectionGroup())
821               {
822                 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
823                         BasicStroke.JOIN_ROUND, 3f, new float[]
824                         { 5f, 3f }, 0f));
825                 g.setColor(Color.RED);
826               }
827               else
828               {
829                 g.setStroke(new BasicStroke());
830                 g.setColor(group.getOutlineColour());
831               }
832             }
833           }
834           else
835           {
836             if (inGroup)
837             {
838               if (sx >= 0 && sx < visWidth)
839               {
840                 g.drawLine(sx, oldY, sx, sy);
841               }
842
843               if (sx + ex < visWidth)
844               {
845                 g.drawLine(sx + ex, oldY, sx + ex, sy);
846               }
847
848               if (sx < 0)
849               {
850                 ex += sx;
851                 sx = 0;
852               }
853
854               if (sx + ex > visWidth)
855               {
856                 ex = visWidth;
857               }
858
859               else if (sx + ex >= (endRes - startRes + 1) * av.charWidth)
860               {
861                 ex = (endRes - startRes + 1) * av.charWidth;
862               }
863
864               if (top != -1)
865               {
866                 g.drawLine(sx, top, sx + ex, top);
867                 top = -1;
868               }
869
870               if (bottom != -1)
871               {
872                 g.drawLine(sx, bottom, sx + ex, bottom);
873                 bottom = -1;
874               }
875
876               inGroup = false;
877             }
878           }
879         }
880
881         if (inGroup)
882         {
883           sy = offset + ((i - startSeq) * av.charHeight);
884           if (sx >= 0 && sx < visWidth)
885           {
886             g.drawLine(sx, oldY, sx, sy);
887           }
888
889           if (sx + ex < visWidth)
890           {
891             g.drawLine(sx + ex, oldY, sx + ex, sy);
892           }
893
894           if (sx < 0)
895           {
896             ex += sx;
897             sx = 0;
898           }
899
900           if (sx + ex > visWidth)
901           {
902             ex = visWidth;
903           }
904           else if (sx + ex >= (endRes - startRes + 1) * av.charWidth)
905           {
906             ex = (endRes - startRes + 1) * av.charWidth;
907           }
908
909           if (top != -1)
910           {
911             g.drawLine(sx, top, sx + ex, top);
912             top = -1;
913           }
914
915           if (bottom != -1)
916           {
917             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
918             bottom = -1;
919           }
920
921           inGroup = false;
922         }
923
924         groupIndex++;
925
926         g.setStroke(new BasicStroke());
927
928         if (groupIndex >= av.getAlignment().getGroups().size())
929         {
930           break;
931         }
932
933         group = av.getAlignment().getGroups()
934                 .get(groupIndex);
935
936       } while (groupIndex < av.getAlignment().getGroups().size());
937
938     }
939
940   }
941
942   /**
943    * DOCUMENT ME!
944    * 
945    * @param results
946    *          DOCUMENT ME!
947    */
948   public void highlightSearchResults(SearchResults results)
949   {
950     img = null;
951
952     searchResults = results;
953
954     repaint();
955   }
956 }