362638361474d1081dd469486de23207dcde6617
[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     super.paintComponent(g);
356
357     updateViewport();
358
359     // img is a cached version of the last view we drew
360     // selectImage will hold any selection we have
361     // lcimg is a local *copy* of img which we'll draw selectImage on top of
362
363     if (img == null)
364     {
365       setupImage();
366     }
367     if (img == null)
368     {
369       return;
370     }
371
372     BufferedImage selectImage = drawSelectionGroup();
373
374     if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
375             || (getVisibleRect().height != g.getClipBounds().height))
376     {
377       BufferedImage lcimg = buildLocalImage(selectImage);
378       g.drawImage(lcimg, 0, 0, this);
379
380       fastPaint = false;
381       return;
382     }
383     
384     if (av.antiAlias)
385     {
386       gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
387               RenderingHints.VALUE_ANTIALIAS_ON);
388     }
389     
390     gg.setColor(Color.white);
391     gg.fillRect(0, 0, imgWidth, imgHeight);
392     
393     ViewportRanges ranges = av.getRanges();
394     if (av.getWrapAlignment())
395     {
396       drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
397     }
398     else
399     {
400       drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
401               ranges.getStartSeq(), ranges.getEndSeq(), 0);
402     }
403
404     BufferedImage lcimg = buildLocalImage(selectImage);
405     g.drawImage(lcimg, 0, 0, this);
406   }
407
408   /*
409    * Make a local image by combining the cached image img
410    * with any selection
411    */
412   private BufferedImage buildLocalImage(BufferedImage selectImage)
413   {
414     // clone the cached image
415     BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
416             img.getType());
417     Graphics2D g2d = lcimg.createGraphics();
418     g2d.drawImage(img, 0, 0, null);
419
420     // overlay selection group on lcimg
421     if (selectImage != null)
422     {
423       g2d.setComposite(
424               AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
425       g2d.drawImage(selectImage, 0, 0, this);
426     }
427     g2d.dispose();
428
429     return lcimg;
430   }
431
432   private void paintSeqGroup()
433   {
434     fastPaint = true;
435     repaint();
436   }
437
438   private void setupImage()
439   {
440     // this draws the whole of the alignment
441     imgWidth = getWidth();
442     imgHeight = getHeight();
443
444     imgWidth -= (imgWidth % charWidth);
445     imgHeight -= (imgHeight % charHeight);
446
447     if ((imgWidth < 1) || (imgHeight < 1))
448     {
449       return;
450     }
451
452
453       try
454       {
455       img = new BufferedImage(imgWidth, imgHeight,
456                 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
457         gg = (Graphics2D) img.getGraphics();
458         gg.setFont(av.getFont());
459       } catch (OutOfMemoryError er)
460       {
461         System.gc();
462         System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
463         new OOMWarning("Creating alignment image for display", er);
464
465       return;
466       }
467
468   }
469
470   private BufferedImage setupSelectionImage()
471   {
472     BufferedImage lcimg = null;
473
474     int width = getWidth();
475     int height = getHeight();
476
477     width -= (width % charWidth);
478     height -= (height % charHeight);
479
480     if ((width < 1) || (height < 1))
481     {
482       return null;
483     }
484
485     try
486     {
487       lcimg = new BufferedImage(width, height,
488               BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
489     } catch (OutOfMemoryError er)
490     {
491       System.gc();
492       System.err.println(
493               "Selection Group image OutOfMemory Redraw Error.\n" + er);
494       new OOMWarning("Creating alignment image for display", er);
495
496       return null;
497     }
498
499     return lcimg;
500   }
501
502   /**
503    * DOCUMENT ME!
504    * 
505    * @param cwidth
506    *          DOCUMENT ME!
507    * 
508    * @return DOCUMENT ME!
509    */
510   public int getWrappedCanvasWidth(int cwidth)
511   {
512     FontMetrics fm = getFontMetrics(av.getFont());
513
514     LABEL_EAST = 0;
515     LABEL_WEST = 0;
516
517     if (av.getScaleRightWrapped())
518     {
519       LABEL_EAST = fm.stringWidth(getMask());
520     }
521
522     if (av.getScaleLeftWrapped())
523     {
524       LABEL_WEST = fm.stringWidth(getMask());
525     }
526
527     return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
528   }
529
530   /**
531    * Generates a string of zeroes.
532    * 
533    * @return String
534    */
535   String getMask()
536   {
537     String mask = "00";
538     int maxWidth = 0;
539     int tmp;
540     for (int i = 0; i < av.getAlignment().getHeight(); i++)
541     {
542       tmp = av.getAlignment().getSequenceAt(i).getEnd();
543       if (tmp > maxWidth)
544       {
545         maxWidth = tmp;
546       }
547     }
548
549     for (int i = maxWidth; i > 0; i /= 10)
550     {
551       mask += "0";
552     }
553     return mask;
554   }
555
556   /**
557    * DOCUMENT ME!
558    * 
559    * @param g
560    *          DOCUMENT ME!
561    * @param canvasWidth
562    *          DOCUMENT ME!
563    * @param canvasHeight
564    *          DOCUMENT ME!
565    * @param startRes
566    *          DOCUMENT ME!
567    */
568   public void drawWrappedPanel(Graphics g, int canvasWidth,
569           int canvasHeight, int startRes)
570   {
571     updateViewport();
572     AlignmentI al = av.getAlignment();
573
574     FontMetrics fm = getFontMetrics(av.getFont());
575
576     LABEL_EAST = 0;
577     LABEL_WEST = 0;
578
579     if (av.getScaleRightWrapped())
580     {
581       LABEL_EAST = fm.stringWidth(getMask());
582     }
583
584     if (av.getScaleLeftWrapped())
585     {
586       LABEL_WEST = fm.stringWidth(getMask());
587     }
588
589     int hgap = charHeight;
590     if (av.getScaleAboveWrapped())
591     {
592       hgap += charHeight;
593     }
594
595     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
596     int cHeight = av.getAlignment().getHeight() * charHeight;
597
598     av.setWrappedWidth(cWidth);
599
600     av.getRanges().setViewportStartAndWidth(startRes, cWidth);
601
602     int endx;
603     int ypos = hgap;
604     int maxwidth = av.getAlignment().getWidth();
605
606     if (av.hasHiddenColumns())
607     {
608       maxwidth = av.getAlignment().getHiddenColumns()
609               .findColumnPosition(maxwidth);
610     }
611
612     while ((ypos <= canvasHeight) && (startRes < maxwidth))
613     {
614       endx = startRes + cWidth - 1;
615
616       if (endx > maxwidth)
617       {
618         endx = maxwidth;
619       }
620
621       g.setFont(av.getFont());
622       g.setColor(Color.black);
623
624       if (av.getScaleLeftWrapped())
625       {
626         drawWestScale(g, startRes, endx, ypos);
627       }
628
629       if (av.getScaleRightWrapped())
630       {
631         g.translate(canvasWidth - LABEL_EAST, 0);
632         drawEastScale(g, startRes, endx, ypos);
633         g.translate(-(canvasWidth - LABEL_EAST), 0);
634       }
635
636       g.translate(LABEL_WEST, 0);
637
638       if (av.getScaleAboveWrapped())
639       {
640         drawNorthScale(g, startRes, endx, ypos);
641       }
642
643       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
644       {
645         g.setColor(Color.blue);
646         int res;
647         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
648         List<Integer> positions = hidden.findHiddenRegionPositions();
649         for (int pos : positions)
650         {
651           res = pos - startRes;
652
653           if (res < 0 || res > endx - startRes)
654           {
655             continue;
656           }
657
658           gg.fillPolygon(
659                   new int[] { res * charWidth - charHeight / 4,
660                       res * charWidth + charHeight / 4, res * charWidth },
661                   new int[] { ypos - (charHeight / 2),
662                       ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
663                   3);
664
665         }
666       }
667
668       // When printing we have an extra clipped region,
669       // the Printable page which we need to account for here
670       Shape clip = g.getClip();
671
672       if (clip == null)
673       {
674         g.setClip(0, 0, cWidth * charWidth, canvasHeight);
675       }
676       else
677       {
678         g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
679                 (int) clip.getBounds().getHeight());
680       }
681
682       drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
683
684       if (av.isShowAnnotation())
685       {
686         g.translate(0, cHeight + ypos + 3);
687         if (annotations == null)
688         {
689           annotations = new AnnotationPanel(av);
690         }
691
692         annotations.renderer.drawComponent(annotations, av, g, -1,
693                 startRes, endx + 1);
694         g.translate(0, -cHeight - ypos - 3);
695       }
696       g.setClip(clip);
697       g.translate(-LABEL_WEST, 0);
698
699       ypos += cHeight + getAnnotationHeight() + hgap;
700
701       startRes += cWidth;
702     }
703   }
704
705   AnnotationPanel annotations;
706
707   int getAnnotationHeight()
708   {
709     if (!av.isShowAnnotation())
710     {
711       return 0;
712     }
713
714     if (annotations == null)
715     {
716       annotations = new AnnotationPanel(av);
717     }
718
719     return annotations.adjustPanelHeight();
720   }
721
722   /**
723    * DOCUMENT ME!
724    * 
725    * @param g1
726    *          DOCUMENT ME!
727    * @param startRes
728    *          DOCUMENT ME!
729    * @param endRes
730    *          DOCUMENT ME!
731    * @param startSeq
732    *          DOCUMENT ME!
733    * @param endSeq
734    *          DOCUMENT ME!
735    * @param offset
736    *          DOCUMENT ME!
737    */
738   public void drawPanel(Graphics g1, int startRes, int endRes,
739           int startSeq, int endSeq, int offset)
740   {
741     updateViewport();
742     if (!av.hasHiddenColumns())
743     {
744       draw(g1, startRes, endRes, startSeq, endSeq, offset);
745     }
746     else
747     {
748       int screenY = 0;
749       int blockStart = startRes;
750       int blockEnd = endRes;
751
752       for (int[] region : av.getAlignment().getHiddenColumns()
753               .getHiddenColumnsCopy())
754       {
755         int hideStart = region[0];
756         int hideEnd = region[1];
757
758         if (hideStart <= blockStart)
759         {
760           blockStart += (hideEnd - hideStart) + 1;
761           continue;
762         }
763
764         blockEnd = hideStart - 1;
765
766         g1.translate(screenY * charWidth, 0);
767
768         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
769
770         if (av.getShowHiddenMarkers())
771         {
772           g1.setColor(Color.blue);
773
774           g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
775                   0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
776                   (endSeq - startSeq + 1) * charHeight + offset);
777         }
778
779         g1.translate(-screenY * charWidth, 0);
780         screenY += blockEnd - blockStart + 1;
781         blockStart = hideEnd + 1;
782
783         if (screenY > (endRes - startRes))
784         {
785           // already rendered last block
786           return;
787         }
788       }
789
790       if (screenY <= (endRes - startRes))
791       {
792         // remaining visible region to render
793         blockEnd = blockStart + (endRes - startRes) - screenY;
794         g1.translate(screenY * charWidth, 0);
795         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
796
797         g1.translate(-screenY * charWidth, 0);
798       }
799     }
800
801   }
802
803   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
804   // int x1, int x2, int y1, int y2, int startx, int starty,
805   private void draw(Graphics g, int startRes, int endRes, int startSeq,
806           int endSeq, int offset)
807   {
808     g.setFont(av.getFont());
809     sr.prepare(g, av.isRenderGaps());
810
811     SequenceI nextSeq;
812
813     // / First draw the sequences
814     // ///////////////////////////
815     for (int i = startSeq; i <= endSeq; i++)
816     {
817       nextSeq = av.getAlignment().getSequenceAt(i);
818       if (nextSeq == null)
819       {
820         // occasionally, a race condition occurs such that the alignment row is
821         // empty
822         continue;
823       }
824       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
825               startRes, endRes, offset + ((i - startSeq) * charHeight));
826
827       if (av.isShowSequenceFeatures())
828       {
829         fr.drawSequence(g, nextSeq, startRes, endRes, offset
830                 + ((i - startSeq) * charHeight), false);
831       }
832
833       // / Highlight search Results once all sequences have been drawn
834       // ////////////////////////////////////////////////////////
835       if (av.hasSearchResults())
836       {
837         int[] visibleResults = av.getSearchResults().getResults(nextSeq,
838                 startRes, endRes);
839         if (visibleResults != null)
840         {
841           for (int r = 0; r < visibleResults.length; r += 2)
842           {
843             sr.drawHighlightedText(nextSeq, visibleResults[r],
844                     visibleResults[r + 1], (visibleResults[r] - startRes)
845                             * charWidth, offset
846                             + ((i - startSeq) * charHeight));
847           }
848         }
849       }
850
851       if (av.cursorMode && cursorY == i && cursorX >= startRes
852               && cursorX <= endRes)
853       {
854         sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
855                 offset + ((i - startSeq) * charHeight));
856       }
857     }
858
859     if (av.getSelectionGroup() != null
860             || av.getAlignment().getGroups().size() > 0)
861     {
862       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
863     }
864
865   }
866
867   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
868           int startSeq, int endSeq, int offset)
869   {
870     Graphics2D g = (Graphics2D) g1;
871     //
872     // ///////////////////////////////////
873     // Now outline any areas if necessary
874     // ///////////////////////////////////
875
876     SequenceGroup group = null;
877
878     int sx = -1;
879     int sy = -1;
880     int ex = -1;
881     int groupIndex = -1;
882     int visWidth = (endRes - startRes + 1) * charWidth;
883
884     if (av.getAlignment().getGroups().size() > 0)
885     {
886       group = av.getAlignment().getGroups().get(0);
887       groupIndex = 0;
888     }
889
890     if (group != null)
891     {
892       do
893       {
894         int oldY = -1;
895         int i = 0;
896         boolean inGroup = false;
897         int top = -1;
898         int bottom = -1;
899
900         for (i = startSeq; i <= endSeq; i++)
901         {
902           // position of start residue of group relative to startRes, in pixels
903           sx = (group.getStartRes() - startRes) * charWidth;
904           sy = offset + ((i - startSeq) * charHeight);
905           // width of group in pixels
906           ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
907
908           if (sx + ex < 0 || sx > visWidth)
909           {
910             continue;
911           }
912
913           if ((sx <= (endRes - startRes) * charWidth)
914                   && group.getSequences(null).contains(
915                           av.getAlignment().getSequenceAt(i)))
916           {
917             if ((bottom == -1)
918                     && !group.getSequences(null).contains(
919                             av.getAlignment().getSequenceAt(i + 1)))
920             {
921               bottom = sy + charHeight;
922             }
923
924             if (!inGroup)
925             {
926               if (((top == -1) && (i == 0))
927                       || !group.getSequences(null).contains(
928                               av.getAlignment().getSequenceAt(i - 1)))
929               {
930                 top = sy;
931               }
932
933               oldY = sy;
934               inGroup = true;
935
936               g.setStroke(new BasicStroke());
937               g.setColor(group.getOutlineColour());
938             }
939           }
940           else
941           {
942             if (inGroup)
943             {
944               // if start position is visible, draw vertical line to left of
945               // group
946               if (sx >= 0 && sx < visWidth)
947               {
948                 g.drawLine(sx, oldY, sx, sy);
949               }
950
951               // if end position is visible, draw vertical line to right of
952               // group
953               if (sx + ex < visWidth)
954               {
955                 g.drawLine(sx + ex, oldY, sx + ex, sy);
956               }
957
958               if (sx < 0)
959               {
960                 // ex += sx;
961                 // sx = 0;
962               }
963
964               if (sx + ex > visWidth)
965               {
966                 ex = visWidth;
967               }
968
969               else if (sx + ex >= (endRes - startRes + 1) * charWidth)
970               {
971                 ex = (endRes - startRes + 1) * charWidth;
972               }
973
974               // draw horizontal line at top of group
975               if (top != -1)
976               {
977                 g.drawLine(sx, top, sx + ex, top);
978                 top = -1;
979               }
980
981               // draw horizontal line at bottom of group
982               if (bottom != -1)
983               {
984                 g.drawLine(sx, bottom, sx + ex, bottom);
985                 bottom = -1;
986               }
987
988               inGroup = false;
989             }
990           }
991         }
992
993         if (inGroup)
994         {
995           sy = offset + ((i - startSeq) * charHeight);
996           if (sx >= 0 && sx < visWidth)
997           {
998             g.drawLine(sx, oldY, sx, sy);
999           }
1000
1001           if (sx + ex < visWidth)
1002           {
1003             g.drawLine(sx + ex, oldY, sx + ex, sy);
1004           }
1005
1006           if (sx < 0)
1007           {
1008             ex += sx;
1009             sx = 0;
1010           }
1011
1012           if (sx + ex > visWidth)
1013           {
1014             ex = visWidth;
1015           }
1016           else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1017           {
1018             ex = (endRes - startRes + 1) * charWidth;
1019           }
1020
1021           if (top != -1)
1022           {
1023             g.drawLine(sx, top, sx + ex, top);
1024             top = -1;
1025           }
1026
1027           if (bottom != -1)
1028           {
1029             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1030             bottom = -1;
1031           }
1032
1033           inGroup = false;
1034         }
1035
1036         groupIndex++;
1037
1038         g.setStroke(new BasicStroke());
1039
1040         if (groupIndex >= av.getAlignment().getGroups().size())
1041         {
1042           break;
1043         }
1044
1045         group = av.getAlignment().getGroups().get(groupIndex);
1046
1047       } while (groupIndex < av.getAlignment().getGroups().size());
1048
1049     }
1050
1051   }
1052
1053   /*
1054    * Draw the selection group as a separate image and overlay
1055    */
1056   private BufferedImage drawSelectionGroup()
1057   {
1058     // get a new image of the correct size
1059     BufferedImage selectionImage = setupSelectionImage();
1060
1061     if (selectionImage == null)
1062     {
1063       return null;
1064     }
1065
1066     SequenceGroup group = av.getSelectionGroup();
1067     if (group == null)
1068     {
1069       // nothing to draw
1070       return null;
1071     }
1072
1073     // set up drawing colour
1074     Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1075     // set background to transparent
1076     g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1077     g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1078
1079     g.setComposite(AlphaComposite.Src);
1080     g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1081             BasicStroke.JOIN_ROUND, 3f, new float[]
1082     { 5f, 3f }, 0f));
1083     g.setColor(Color.RED);
1084
1085     int visWidth = av.getRanges().getViewportWidth() * charWidth;
1086
1087     int startRes = av.getRanges().getStartRes();
1088
1089     // set x start and end positions of group
1090     int startx = (group.getStartRes() - startRes) * charWidth;
1091     int endx = (group.getEndRes() - startRes + 1) * charWidth;
1092
1093     int oldY = -1;
1094     int i = 0;
1095     boolean inGroup = false;
1096     int top = -1;
1097     int bottom = -1;
1098
1099     // get sequences to determine y positions of group
1100     int startSeq = av.getRanges().getStartSeq();
1101     for (i = startSeq; i <= av.getRanges().getEndSeq(); ++i)
1102     {
1103       int sy = (i - startSeq) * charHeight;
1104
1105       if (group.getSequences(null)
1106               .contains(av.getAlignment().getSequenceAt(i)))
1107       {
1108         if ((bottom == -1) && !group.getSequences(null)
1109                 .contains(av.getAlignment().getSequenceAt(i + 1)))
1110         {
1111           bottom = sy + charHeight;
1112         }
1113
1114         if (!inGroup)
1115         {
1116           if (((top == -1) && (i == 0)) || !group.getSequences(null)
1117                   .contains(av.getAlignment().getSequenceAt(i - 1)))
1118           {
1119             top = sy;
1120           }
1121
1122           oldY = sy;
1123           inGroup = true;
1124         }
1125       }
1126       else
1127       {
1128         if (inGroup)
1129         {
1130           // if start position is visible, draw vertical line to left of
1131           // group
1132           if (startx >= 0 && startx < visWidth * charWidth)
1133           {
1134             g.drawLine(startx, oldY, startx, sy);
1135           }
1136
1137           // if end position is visible, draw vertical line to right of
1138           // group
1139           if (endx <= visWidth * charWidth)
1140           {
1141             g.drawLine(endx, oldY, endx, sy);
1142           }
1143
1144           if (endx > visWidth * charWidth)
1145           {
1146             endx = visWidth * charWidth;
1147           }
1148
1149           // draw horizontal line at top of group
1150           if (top != -1)
1151           {
1152             g.drawLine(startx, top, endx, top);
1153             top = -1;
1154           }
1155
1156           // draw horizontal line at bottom of group
1157           if (bottom != -1)
1158           {
1159             g.drawLine(startx, bottom, endx, bottom);
1160             bottom = -1;
1161           }
1162
1163           inGroup = false;
1164         }
1165       }
1166     }
1167     if (inGroup)
1168     {
1169       int sy = (i - startSeq) * charHeight;
1170       if (startx >= 0 && startx < visWidth)
1171       {
1172         g.drawLine(startx, oldY, startx, sy);
1173       }
1174
1175       if (endx < visWidth)
1176       {
1177         g.drawLine(endx, oldY, endx, sy);
1178       }
1179
1180       if (endx > visWidth)
1181       {
1182         endx = visWidth;
1183       }
1184
1185       if (top != -1)
1186       {
1187         g.drawLine(startx, top, endx, top);
1188         top = -1;
1189       }
1190
1191       if (bottom != -1)
1192       {
1193         g.drawLine(startx, bottom - 1, endx, bottom - 1);
1194         bottom = -1;
1195       }
1196
1197       inGroup = false;
1198     }
1199
1200     return selectionImage;
1201   }
1202
1203   /**
1204    * DOCUMENT ME!
1205    * 
1206    * @param results
1207    *          DOCUMENT ME!
1208    */
1209   public void highlightSearchResults(SearchResultsI results)
1210   {
1211     img = null;
1212
1213     av.setSearchResults(results);
1214
1215     repaint();
1216   }
1217
1218   @Override
1219   public void propertyChange(PropertyChangeEvent evt)
1220   {
1221     String eventName = evt.getPropertyName();
1222
1223     if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1224     {
1225       paintSeqGroup();
1226     }
1227     else if (av.getWrapAlignment())
1228     {
1229       if (eventName.equals(ViewportRanges.STARTRES))
1230       {
1231         repaint();
1232       }
1233     }
1234     else
1235     {
1236       int scrollX = 0;
1237       if (eventName.equals(ViewportRanges.STARTRES))
1238       {
1239         // Make sure we're not trying to draw a panel
1240         // larger than the visible window
1241         ViewportRanges vpRanges = av.getRanges();
1242         scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1243         int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1244         if (scrollX > range)
1245         {
1246           scrollX = range;
1247         }
1248         else if (scrollX < -range)
1249         {
1250           scrollX = -range;
1251         }
1252       }
1253
1254       // Both scrolling and resizing change viewport ranges: scrolling changes
1255       // both start and end points, but resize only changes end values.
1256       // Here we only want to fastpaint on a scroll, with resize using a normal
1257       // paint, so scroll events are identified as changes to the horizontal or
1258       // vertical start value.
1259       if (eventName.equals(ViewportRanges.STARTRES))
1260       {
1261         // scroll - startres and endres both change
1262         fastPaint(scrollX, 0);
1263       }
1264       else if (eventName.equals(ViewportRanges.STARTSEQ))
1265       {
1266         // scroll
1267         fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1268       }
1269     }
1270   }
1271 }