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