26f8e9e31e19f86451c0ae1387fd45cf6494268b
[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       transX = (er - sr - horizontal) * charWidth;
298       sr = er - horizontal;
299     }
300     else if (horizontal < 0)
301     {
302       er = sr - horizontal;
303     }
304     else if (vertical > 0) // scroll down
305     {
306       ss = es - vertical;
307
308       if (ss < ranges.getStartSeq())
309       { // ie scrolling too fast, more than a page at a time
310         ss = ranges.getStartSeq();
311       }
312       else
313       {
314         transY = imgHeight - ((vertical + 1) * charHeight);
315       }
316     }
317     else if (vertical < 0)
318     {
319       es = ss - vertical;
320
321       if (es > ranges.getEndSeq())
322       {
323         es = ranges.getEndSeq();
324       }
325     }
326
327     gg.translate(transX, transY);
328     drawPanel(gg, sr, er, ss, es, 0);
329     gg.translate(-transX, -transY);
330
331     repaint();
332     fastpainting = false;
333   }
334
335   /**
336    * Definitions of startx and endx (hopefully): SMJS This is what I'm working
337    * towards! startx is the first residue (starting at 0) to display. endx is
338    * the last residue to display (starting at 0). starty is the first sequence
339    * to display (starting at 0). endy is the last sequence to display (starting
340    * at 0). NOTE 1: The av limits are set in setFont in this class and in the
341    * adjustment listener in SeqPanel when the scrollbars move.
342    */
343
344   // Set this to false to force a full panel paint
345   @Override
346   public void paintComponent(Graphics g)
347   {
348     updateViewport();
349     BufferedImage lcimg = img; // take reference since other threads may null
350     // img and call later.
351     super.paintComponent(g);
352
353     if (lcimg != null
354             && (fastPaint
355                     || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
356                     .getClipBounds().height)))
357     {
358       g.drawImage(lcimg, 0, 0, this);
359       fastPaint = false;
360       return;
361     }
362
363     // this draws the whole of the alignment
364     imgWidth = getWidth();
365     imgHeight = getHeight();
366
367     imgWidth -= (imgWidth % charWidth);
368     imgHeight -= (imgHeight % charHeight);
369
370     if ((imgWidth < 1) || (imgHeight < 1))
371     {
372       return;
373     }
374
375     if (lcimg == null || imgWidth != lcimg.getWidth()
376             || imgHeight != lcimg.getHeight())
377     {
378       try
379       {
380         lcimg = img = new BufferedImage(imgWidth, imgHeight,
381                 BufferedImage.TYPE_INT_RGB);
382         gg = (Graphics2D) img.getGraphics();
383         gg.setFont(av.getFont());
384       } catch (OutOfMemoryError er)
385       {
386         System.gc();
387         System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
388         new OOMWarning("Creating alignment image for display", er);
389
390         return;
391       }
392     }
393
394     if (av.antiAlias)
395     {
396       gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
397               RenderingHints.VALUE_ANTIALIAS_ON);
398     }
399
400     gg.setColor(Color.white);
401     gg.fillRect(0, 0, imgWidth, imgHeight);
402
403     ViewportRanges ranges = av.getRanges();
404     if (av.getWrapAlignment())
405     {
406       drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
407     }
408     else
409     {
410       drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
411               ranges.getStartSeq(), ranges.getEndSeq(), 0);
412     }
413
414     g.drawImage(lcimg, 0, 0, this);
415
416   }
417
418   /**
419    * DOCUMENT ME!
420    * 
421    * @param cwidth
422    *          DOCUMENT ME!
423    * 
424    * @return DOCUMENT ME!
425    */
426   public int getWrappedCanvasWidth(int cwidth)
427   {
428     FontMetrics fm = getFontMetrics(av.getFont());
429
430     LABEL_EAST = 0;
431     LABEL_WEST = 0;
432
433     if (av.getScaleRightWrapped())
434     {
435       LABEL_EAST = fm.stringWidth(getMask());
436     }
437
438     if (av.getScaleLeftWrapped())
439     {
440       LABEL_WEST = fm.stringWidth(getMask());
441     }
442
443     return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
444   }
445
446   /**
447    * Generates a string of zeroes.
448    * 
449    * @return String
450    */
451   String getMask()
452   {
453     String mask = "00";
454     int maxWidth = 0;
455     int tmp;
456     for (int i = 0; i < av.getAlignment().getHeight(); i++)
457     {
458       tmp = av.getAlignment().getSequenceAt(i).getEnd();
459       if (tmp > maxWidth)
460       {
461         maxWidth = tmp;
462       }
463     }
464
465     for (int i = maxWidth; i > 0; i /= 10)
466     {
467       mask += "0";
468     }
469     return mask;
470   }
471
472   /**
473    * DOCUMENT ME!
474    * 
475    * @param g
476    *          DOCUMENT ME!
477    * @param canvasWidth
478    *          DOCUMENT ME!
479    * @param canvasHeight
480    *          DOCUMENT ME!
481    * @param startRes
482    *          DOCUMENT ME!
483    */
484   public void drawWrappedPanel(Graphics g, int canvasWidth,
485           int canvasHeight, int startRes)
486   {
487     updateViewport();
488     AlignmentI al = av.getAlignment();
489
490     FontMetrics fm = getFontMetrics(av.getFont());
491
492     if (av.getScaleRightWrapped())
493     {
494       LABEL_EAST = fm.stringWidth(getMask());
495     }
496
497     if (av.getScaleLeftWrapped())
498     {
499       LABEL_WEST = fm.stringWidth(getMask());
500     }
501
502     int hgap = charHeight;
503     if (av.getScaleAboveWrapped())
504     {
505       hgap += charHeight;
506     }
507
508     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
509     int cHeight = av.getAlignment().getHeight() * charHeight;
510
511     av.setWrappedWidth(cWidth);
512
513     av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
514
515     int endx;
516     int ypos = hgap;
517     int maxwidth = av.getAlignment().getWidth() - 1;
518
519     if (av.hasHiddenColumns())
520     {
521       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
522     }
523
524     while ((ypos <= canvasHeight) && (startRes < maxwidth))
525     {
526       endx = startRes + cWidth - 1;
527
528       if (endx > maxwidth)
529       {
530         endx = maxwidth;
531       }
532
533       g.setFont(av.getFont());
534       g.setColor(Color.black);
535
536       if (av.getScaleLeftWrapped())
537       {
538         drawWestScale(g, startRes, endx, ypos);
539       }
540
541       if (av.getScaleRightWrapped())
542       {
543         g.translate(canvasWidth - LABEL_EAST, 0);
544         drawEastScale(g, startRes, endx, ypos);
545         g.translate(-(canvasWidth - LABEL_EAST), 0);
546       }
547
548       g.translate(LABEL_WEST, 0);
549
550       if (av.getScaleAboveWrapped())
551       {
552         drawNorthScale(g, startRes, endx, ypos);
553       }
554
555       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
556       {
557         g.setColor(Color.blue);
558         int res;
559         for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
560                 .size(); i++)
561         {
562           res = av.getColumnSelection().findHiddenRegionPosition(i)
563                   - startRes;
564
565           if (res < 0 || res > endx - startRes)
566           {
567             continue;
568           }
569
570           gg.fillPolygon(
571                   new int[] { res * charWidth - charHeight / 4,
572                       res * charWidth + charHeight / 4, res * charWidth },
573                   new int[] { ypos - (charHeight / 2),
574                       ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
575                   3);
576
577         }
578       }
579
580       // When printing we have an extra clipped region,
581       // the Printable page which we need to account for here
582       Shape clip = g.getClip();
583
584       if (clip == null)
585       {
586         g.setClip(0, 0, cWidth * charWidth, canvasHeight);
587       }
588       else
589       {
590         g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
591                 (int) clip.getBounds().getHeight());
592       }
593
594       drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
595
596       if (av.isShowAnnotation())
597       {
598         g.translate(0, cHeight + ypos + 3);
599         if (annotations == null)
600         {
601           annotations = new AnnotationPanel(av);
602         }
603
604         annotations.renderer.drawComponent(annotations, av, g, -1,
605                 startRes, endx + 1);
606         g.translate(0, -cHeight - ypos - 3);
607       }
608       g.setClip(clip);
609       g.translate(-LABEL_WEST, 0);
610
611       ypos += cHeight + getAnnotationHeight() + hgap;
612
613       startRes += cWidth;
614     }
615   }
616
617   AnnotationPanel annotations;
618
619   int getAnnotationHeight()
620   {
621     if (!av.isShowAnnotation())
622     {
623       return 0;
624     }
625
626     if (annotations == null)
627     {
628       annotations = new AnnotationPanel(av);
629     }
630
631     return annotations.adjustPanelHeight();
632   }
633
634   /**
635    * DOCUMENT ME!
636    * 
637    * @param g1
638    *          DOCUMENT ME!
639    * @param startRes
640    *          DOCUMENT ME!
641    * @param endRes
642    *          DOCUMENT ME!
643    * @param startSeq
644    *          DOCUMENT ME!
645    * @param endSeq
646    *          DOCUMENT ME!
647    * @param offset
648    *          DOCUMENT ME!
649    */
650   public void drawPanel(Graphics g1, int startRes, int endRes,
651           int startSeq, int endSeq, int offset)
652   {
653     updateViewport();
654     if (!av.hasHiddenColumns())
655     {
656       draw(g1, startRes, endRes, startSeq, endSeq, offset);
657     }
658     else
659     {
660       List<int[]> regions = av.getColumnSelection().getHiddenColumns();
661
662       int screenY = 0;
663       int blockStart = startRes;
664       int blockEnd = endRes;
665
666       for (int[] region : regions)
667       {
668         int hideStart = region[0];
669         int hideEnd = region[1];
670
671         if (hideStart <= blockStart)
672         {
673           blockStart += (hideEnd - hideStart) + 1;
674           continue;
675         }
676
677         blockEnd = hideStart - 1;
678
679         g1.translate(screenY * charWidth, 0);
680
681         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
682
683         if (av.getShowHiddenMarkers())
684         {
685           g1.setColor(Color.blue);
686
687           g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
688                   0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
689                   (endSeq - startSeq + 1) * charHeight + offset);
690         }
691
692         g1.translate(-screenY * charWidth, 0);
693         screenY += blockEnd - blockStart + 1;
694         blockStart = hideEnd + 1;
695
696         if (screenY > (endRes - startRes))
697         {
698           // already rendered last block
699           return;
700         }
701       }
702
703       if (screenY <= (endRes - startRes))
704       {
705         // remaining visible region to render
706         blockEnd = blockStart + (endRes - startRes) - screenY;
707         g1.translate(screenY * charWidth, 0);
708         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
709
710         g1.translate(-screenY * charWidth, 0);
711       }
712     }
713
714   }
715
716   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
717   // int x1, int x2, int y1, int y2, int startx, int starty,
718   private void draw(Graphics g, int startRes, int endRes, int startSeq,
719           int endSeq, int offset)
720   {
721     g.setFont(av.getFont());
722     sr.prepare(g, av.isRenderGaps());
723
724     SequenceI nextSeq;
725
726     // / First draw the sequences
727     // ///////////////////////////
728     for (int i = startSeq; i <= endSeq; i++)
729     {
730       nextSeq = av.getAlignment().getSequenceAt(i);
731       if (nextSeq == null)
732       {
733         // occasionally, a race condition occurs such that the alignment row is
734         // empty
735         continue;
736       }
737       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
738               startRes, endRes, offset + ((i - startSeq) * charHeight));
739
740       if (av.isShowSequenceFeatures())
741       {
742         fr.drawSequence(g, nextSeq, startRes, endRes, offset
743                 + ((i - startSeq) * charHeight), false);
744       }
745
746       // / Highlight search Results once all sequences have been drawn
747       // ////////////////////////////////////////////////////////
748       if (av.hasSearchResults())
749       {
750         int[] visibleResults = av.getSearchResults().getResults(nextSeq,
751                 startRes, endRes);
752         if (visibleResults != null)
753         {
754           for (int r = 0; r < visibleResults.length; r += 2)
755           {
756             sr.drawHighlightedText(nextSeq, visibleResults[r],
757                     visibleResults[r + 1], (visibleResults[r] - startRes)
758                             * charWidth, offset
759                             + ((i - startSeq) * charHeight));
760           }
761         }
762       }
763
764       if (av.cursorMode && cursorY == i && cursorX >= startRes
765               && cursorX <= endRes)
766       {
767         sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
768                 offset + ((i - startSeq) * charHeight));
769       }
770     }
771
772     if (av.getSelectionGroup() != null
773             || av.getAlignment().getGroups().size() > 0)
774     {
775       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
776     }
777
778   }
779
780   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
781           int startSeq, int endSeq, int offset)
782   {
783     Graphics2D g = (Graphics2D) g1;
784     //
785     // ///////////////////////////////////
786     // Now outline any areas if necessary
787     // ///////////////////////////////////
788     SequenceGroup group = av.getSelectionGroup();
789
790     int sx = -1;
791     int sy = -1;
792     int ex = -1;
793     int groupIndex = -1;
794     int visWidth = (endRes - startRes + 1) * charWidth;
795
796     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
797     {
798       group = av.getAlignment().getGroups().get(0);
799       groupIndex = 0;
800     }
801
802     if (group != null)
803     {
804       do
805       {
806         int oldY = -1;
807         int i = 0;
808         boolean inGroup = false;
809         int top = -1;
810         int bottom = -1;
811
812         for (i = startSeq; i <= endSeq; i++)
813         {
814           sx = (group.getStartRes() - startRes) * charWidth;
815           sy = offset + ((i - startSeq) * charHeight);
816           ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
817
818           if (sx + ex < 0 || sx > visWidth)
819           {
820             continue;
821           }
822
823           if ((sx <= (endRes - startRes) * charWidth)
824                   && group.getSequences(null).contains(
825                           av.getAlignment().getSequenceAt(i)))
826           {
827             if ((bottom == -1)
828                     && !group.getSequences(null).contains(
829                             av.getAlignment().getSequenceAt(i + 1)))
830             {
831               bottom = sy + charHeight;
832             }
833
834             if (!inGroup)
835             {
836               if (((top == -1) && (i == 0))
837                       || !group.getSequences(null).contains(
838                               av.getAlignment().getSequenceAt(i - 1)))
839               {
840                 top = sy;
841               }
842
843               oldY = sy;
844               inGroup = true;
845
846               if (group == av.getSelectionGroup())
847               {
848                 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
849                         BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
850                         0f));
851                 g.setColor(Color.RED);
852               }
853               else
854               {
855                 g.setStroke(new BasicStroke());
856                 g.setColor(group.getOutlineColour());
857               }
858             }
859           }
860           else
861           {
862             if (inGroup)
863             {
864               if (sx >= 0 && sx < visWidth)
865               {
866                 g.drawLine(sx, oldY, sx, sy);
867               }
868
869               if (sx + ex < visWidth)
870               {
871                 g.drawLine(sx + ex, oldY, sx + ex, sy);
872               }
873
874               if (sx < 0)
875               {
876                 ex += sx;
877                 sx = 0;
878               }
879
880               if (sx + ex > visWidth)
881               {
882                 ex = visWidth;
883               }
884
885               else if (sx + ex >= (endRes - startRes + 1) * charWidth)
886               {
887                 ex = (endRes - startRes + 1) * charWidth;
888               }
889
890               if (top != -1)
891               {
892                 g.drawLine(sx, top, sx + ex, top);
893                 top = -1;
894               }
895
896               if (bottom != -1)
897               {
898                 g.drawLine(sx, bottom, sx + ex, bottom);
899                 bottom = -1;
900               }
901
902               inGroup = false;
903             }
904           }
905         }
906
907         if (inGroup)
908         {
909           sy = offset + ((i - startSeq) * charHeight);
910           if (sx >= 0 && sx < visWidth)
911           {
912             g.drawLine(sx, oldY, sx, sy);
913           }
914
915           if (sx + ex < visWidth)
916           {
917             g.drawLine(sx + ex, oldY, sx + ex, sy);
918           }
919
920           if (sx < 0)
921           {
922             ex += sx;
923             sx = 0;
924           }
925
926           if (sx + ex > visWidth)
927           {
928             ex = visWidth;
929           }
930           else if (sx + ex >= (endRes - startRes + 1) * charWidth)
931           {
932             ex = (endRes - startRes + 1) * charWidth;
933           }
934
935           if (top != -1)
936           {
937             g.drawLine(sx, top, sx + ex, top);
938             top = -1;
939           }
940
941           if (bottom != -1)
942           {
943             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
944             bottom = -1;
945           }
946
947           inGroup = false;
948         }
949
950         groupIndex++;
951
952         g.setStroke(new BasicStroke());
953
954         if (groupIndex >= av.getAlignment().getGroups().size())
955         {
956           break;
957         }
958
959         group = av.getAlignment().getGroups().get(groupIndex);
960
961       } while (groupIndex < av.getAlignment().getGroups().size());
962
963     }
964
965   }
966
967   /**
968    * DOCUMENT ME!
969    * 
970    * @param results
971    *          DOCUMENT ME!
972    */
973   public void highlightSearchResults(SearchResultsI results)
974   {
975     img = null;
976
977     av.setSearchResults(results);
978
979     repaint();
980   }
981
982   @Override
983   public void propertyChange(PropertyChangeEvent evt)
984   {
985     if (!av.getWrapAlignment())
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 }