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