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