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