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