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