update group annotation for new treegroups (JAL-552)
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.awt.print.*;
25 import javax.swing.*;
26
27 import jalview.analysis.*;
28 import jalview.datamodel.*;
29 import jalview.schemes.*;
30 import jalview.util.*;
31
32 /**
33  * DOCUMENT ME!
34  * 
35  * @author $author$
36  * @version $Revision$
37  */
38 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
39         Printable, MouseMotionListener
40 {
41   /** DOCUMENT ME!! */
42   public static final String PLACEHOLDER = " * ";
43
44   NJTree tree;
45
46   JScrollPane scrollPane;
47
48   TreePanel tp;
49
50   AlignViewport av;
51
52   AlignmentPanel ap;
53
54   Font font;
55
56   FontMetrics fm;
57
58   boolean fitToWindow = true;
59
60   boolean showDistances = false;
61
62   boolean showBootstrap = false;
63
64   boolean markPlaceholders = false;
65
66   int offx = 20;
67
68   int offy;
69
70   float threshold;
71
72   String longestName;
73
74   int labelLength = -1;
75
76   Hashtable nameHash = new Hashtable();
77
78   Hashtable nodeHash = new Hashtable();
79
80   SequenceNode highlightNode;
81
82   boolean applyToAllViews = false;
83
84   /**
85    * Creates a new TreeCanvas object.
86    * 
87    * @param av
88    *          DOCUMENT ME!
89    * @param tree
90    *          DOCUMENT ME!
91    * @param scroller
92    *          DOCUMENT ME!
93    * @param label
94    *          DOCUMENT ME!
95    */
96   public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
97   {
98     this.tp = tp;
99     this.av = ap.av;
100     this.ap = ap;
101     font = av.getFont();
102     scrollPane = scroller;
103     addMouseListener(this);
104     addMouseMotionListener(this);
105     ToolTipManager.sharedInstance().registerComponent(this);
106   }
107
108   /**
109    * DOCUMENT ME!
110    * 
111    * @param sequence
112    *          DOCUMENT ME!
113    */
114   public void treeSelectionChanged(SequenceI sequence)
115   {
116     AlignmentPanel[] aps = getAssociatedPanels();
117
118     for (int a = 0; a < aps.length; a++)
119     {
120       SequenceGroup selected = aps[a].av.getSelectionGroup();
121
122       if (selected == null)
123       {
124         selected = new SequenceGroup();
125         aps[a].av.setSelectionGroup(selected);
126       }
127
128       selected.setEndRes(aps[a].av.alignment.getWidth() - 1);
129       selected.addOrRemove(sequence, true);
130     }
131   }
132
133   /**
134    * DOCUMENT ME!
135    * 
136    * @param tree
137    *          DOCUMENT ME!
138    */
139   public void setTree(NJTree tree)
140   {
141     this.tree = tree;
142     tree.findHeight(tree.getTopNode());
143
144     // Now have to calculate longest name based on the leaves
145     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
146     boolean has_placeholders = false;
147     longestName = "";
148
149     for (int i = 0; i < leaves.size(); i++)
150     {
151       SequenceNode lf = (SequenceNode) leaves.elementAt(i);
152
153       if (lf.isPlaceholder())
154       {
155         has_placeholders = true;
156       }
157
158       if (longestName.length() < ((Sequence) lf.element()).getName()
159               .length())
160       {
161         longestName = TreeCanvas.PLACEHOLDER
162                 + ((Sequence) lf.element()).getName();
163       }
164     }
165
166     setMarkPlaceholders(has_placeholders);
167   }
168
169   /**
170    * DOCUMENT ME!
171    * 
172    * @param g
173    *          DOCUMENT ME!
174    * @param node
175    *          DOCUMENT ME!
176    * @param chunk
177    *          DOCUMENT ME!
178    * @param scale
179    *          DOCUMENT ME!
180    * @param width
181    *          DOCUMENT ME!
182    * @param offx
183    *          DOCUMENT ME!
184    * @param offy
185    *          DOCUMENT ME!
186    */
187   public void drawNode(Graphics g, SequenceNode node, float chunk,
188           float scale, int width, int offx, int offy)
189   {
190     if (node == null)
191     {
192       return;
193     }
194
195     if ((node.left() == null) && (node.right() == null))
196     {
197       // Drawing leaf node
198       float height = node.height;
199       float dist = node.dist;
200
201       int xstart = (int) ((height - dist) * scale) + offx;
202       int xend = (int) (height * scale) + offx;
203
204       int ypos = (int) (node.ycount * chunk) + offy;
205
206       if (node.element() instanceof SequenceI)
207       {
208         SequenceI seq = (SequenceI) ((SequenceNode) node).element();
209
210         if (av.getSequenceColour(seq) == Color.white)
211         {
212           g.setColor(Color.black);
213         }
214         else
215         {
216           g.setColor(av.getSequenceColour(seq).darker());
217         }
218       }
219       else
220       {
221         g.setColor(Color.black);
222       }
223
224       // Draw horizontal line
225       g.drawLine(xstart, ypos, xend, ypos);
226
227       String nodeLabel = "";
228
229       if (showDistances && (node.dist > 0))
230       {
231         nodeLabel = new Format("%-.2f").form(node.dist);
232       }
233
234       if (showBootstrap && node.bootstrap > -1)
235       {
236         if (showDistances)
237         {
238           nodeLabel = nodeLabel + " : ";
239         }
240
241         nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
242       }
243
244       if (!nodeLabel.equals(""))
245       {
246         g.drawString(nodeLabel, xstart + 2, ypos - 2);
247       }
248
249       String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
250               .getName())
251               : node.getName();
252
253       int charWidth = fm.stringWidth(name) + 3;
254       int charHeight = font.getSize();
255
256       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
257               charWidth, charHeight);
258
259       nameHash.put((SequenceI) node.element(), rect);
260
261       // Colour selected leaves differently
262       SequenceGroup selected = av.getSelectionGroup();
263
264       if ((selected != null)
265               && selected.getSequences(null).contains(
266                       (SequenceI) node.element()))
267       {
268         g.setColor(Color.gray);
269
270         g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
271         g.setColor(Color.white);
272       }
273
274       g.drawString(name, xend + 10, ypos + fm.getDescent());
275       g.setColor(Color.black);
276     }
277     else
278     {
279       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
280               offy);
281       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
282               offy);
283
284       float height = node.height;
285       float dist = node.dist;
286
287       int xstart = (int) ((height - dist) * scale) + offx;
288       int xend = (int) (height * scale) + offx;
289       int ypos = (int) (node.ycount * chunk) + offy;
290
291       g.setColor(((SequenceNode) node).color.darker());
292
293       // Draw horizontal line
294       g.drawLine(xstart, ypos, xend, ypos);
295       if (node == highlightNode)
296       {
297         g.fillRect(xend - 3, ypos - 3, 6, 6);
298       }
299       else
300       {
301         g.fillRect(xend - 2, ypos - 2, 4, 4);
302       }
303
304       int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
305               + offy;
306       int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
307               + offy;
308
309       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
310       nodeHash.put(node, pos);
311
312       g.drawLine((int) (height * scale) + offx, ystart,
313               (int) (height * scale) + offx, yend);
314
315       String nodeLabel = "";
316
317       if (showDistances && (node.dist > 0))
318       {
319         nodeLabel = new Format("%-.2f").form(node.dist);
320       }
321
322       if (showBootstrap && node.bootstrap > -1)
323       {
324         if (showDistances)
325         {
326           nodeLabel = nodeLabel + " : ";
327         }
328
329         nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
330       }
331
332       if (!nodeLabel.equals(""))
333       {
334         g.drawString(nodeLabel, xstart + 2, ypos - 2);
335       }
336     }
337   }
338
339   /**
340    * DOCUMENT ME!
341    * 
342    * @param x
343    *          DOCUMENT ME!
344    * @param y
345    *          DOCUMENT ME!
346    * 
347    * @return DOCUMENT ME!
348    */
349   public Object findElement(int x, int y)
350   {
351     Enumeration keys = nameHash.keys();
352
353     while (keys.hasMoreElements())
354     {
355       Object ob = keys.nextElement();
356       Rectangle rect = (Rectangle) nameHash.get(ob);
357
358       if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
359               && (y <= (rect.y + rect.height)))
360       {
361         return ob;
362       }
363     }
364
365     keys = nodeHash.keys();
366
367     while (keys.hasMoreElements())
368     {
369       Object ob = keys.nextElement();
370       Rectangle rect = (Rectangle) nodeHash.get(ob);
371
372       if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
373               && (y <= (rect.y + rect.height)))
374       {
375         return ob;
376       }
377     }
378
379     return null;
380   }
381
382   /**
383    * DOCUMENT ME!
384    * 
385    * @param pickBox
386    *          DOCUMENT ME!
387    */
388   public void pickNodes(Rectangle pickBox)
389   {
390     int width = getWidth();
391     int height = getHeight();
392
393     SequenceNode top = tree.getTopNode();
394
395     float wscale = (float) ((width * .8) - (offx * 2))
396             / tree.getMaxHeight();
397
398     if (top.count == 0)
399     {
400       top.count = ((SequenceNode) top.left()).count
401               + ((SequenceNode) top.right()).count;
402     }
403
404     float chunk = (float) (height - (offy)) / top.count;
405
406     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
407   }
408
409   /**
410    * DOCUMENT ME!
411    * 
412    * @param pickBox
413    *          DOCUMENT ME!
414    * @param node
415    *          DOCUMENT ME!
416    * @param chunk
417    *          DOCUMENT ME!
418    * @param scale
419    *          DOCUMENT ME!
420    * @param width
421    *          DOCUMENT ME!
422    * @param offx
423    *          DOCUMENT ME!
424    * @param offy
425    *          DOCUMENT ME!
426    */
427   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
428           float scale, int width, int offx, int offy)
429   {
430     if (node == null)
431     {
432       return;
433     }
434
435     if ((node.left() == null) && (node.right() == null))
436     {
437       float height = node.height;
438       float dist = node.dist;
439
440       int xstart = (int) ((height - dist) * scale) + offx;
441       int xend = (int) (height * scale) + offx;
442
443       int ypos = (int) (node.ycount * chunk) + offy;
444
445       if (pickBox.contains(new Point(xend, ypos)))
446       {
447         if (node.element() instanceof SequenceI)
448         {
449           SequenceI seq = (SequenceI) node.element();
450           SequenceGroup sg = av.getSelectionGroup();
451
452           if (sg != null)
453           {
454             sg.addOrRemove(seq, true);
455           }
456         }
457       }
458     }
459     else
460     {
461       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
462               offx, offy);
463       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
464               offx, offy);
465     }
466   }
467
468   /**
469    * DOCUMENT ME!
470    * 
471    * @param node
472    *          DOCUMENT ME!
473    * @param c
474    *          DOCUMENT ME!
475    */
476   public void setColor(SequenceNode node, Color c)
477   {
478     if (node == null)
479     {
480       return;
481     }
482
483     if ((node.left() == null) && (node.right() == null)) // TODO: internal node
484     {
485       node.color = c;
486
487       if (node.element() instanceof SequenceI)
488       {
489         AlignmentPanel[] aps = getAssociatedPanels();
490         for (int a = 0; a < aps.length; a++)
491         {
492           aps[a].av.setSequenceColour((SequenceI) node.element(), c);
493         }
494       }
495     }
496     else
497     {
498       node.color = c;
499       setColor((SequenceNode) node.left(), c);
500       setColor((SequenceNode) node.right(), c);
501     }
502   }
503
504   /**
505    * DOCUMENT ME!
506    */
507   void startPrinting()
508   {
509     Thread thread = new Thread(this);
510     thread.start();
511   }
512
513   // put printing in a thread to avoid painting problems
514   public void run()
515   {
516     PrinterJob printJob = PrinterJob.getPrinterJob();
517     PageFormat pf = printJob.pageDialog(printJob.defaultPage());
518
519     printJob.setPrintable(this, pf);
520
521     if (printJob.printDialog())
522     {
523       try
524       {
525         printJob.print();
526       } catch (Exception PrintException)
527       {
528         PrintException.printStackTrace();
529       }
530     }
531   }
532
533   /**
534    * DOCUMENT ME!
535    * 
536    * @param pg
537    *          DOCUMENT ME!
538    * @param pf
539    *          DOCUMENT ME!
540    * @param pi
541    *          DOCUMENT ME!
542    * 
543    * @return DOCUMENT ME!
544    * 
545    * @throws PrinterException
546    *           DOCUMENT ME!
547    */
548   public int print(Graphics pg, PageFormat pf, int pi)
549           throws PrinterException
550   {
551     pg.setFont(font);
552     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
553
554     int pwidth = (int) pf.getImageableWidth();
555     int pheight = (int) pf.getImageableHeight();
556
557     int noPages = getHeight() / pheight;
558
559     if (pi > noPages)
560     {
561       return Printable.NO_SUCH_PAGE;
562     }
563
564     if (pwidth > getWidth())
565     {
566       pwidth = getWidth();
567     }
568
569     if (fitToWindow)
570     {
571       if (pheight > getHeight())
572       {
573         pheight = getHeight();
574       }
575
576       noPages = 0;
577     }
578     else
579     {
580       FontMetrics fm = pg.getFontMetrics(font);
581       int height = fm.getHeight() * nameHash.size();
582       pg.translate(0, -pi * pheight);
583       pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
584
585       // translate number of pages,
586       // height is screen size as this is the
587       // non overlapping text size
588       pheight = height;
589     }
590
591     draw(pg, pwidth, pheight);
592
593     return Printable.PAGE_EXISTS;
594   }
595
596   /**
597    * DOCUMENT ME!
598    * 
599    * @param g
600    *          DOCUMENT ME!
601    */
602   public void paintComponent(Graphics g)
603   {
604     super.paintComponent(g);
605     g.setFont(font);
606
607     if (tree == null)
608     {
609       g.drawString("Calculating tree....", 20, getHeight() / 2);
610     }
611     else
612     {
613       fm = g.getFontMetrics(font);
614
615       if (nameHash.size() == 0)
616       {
617         repaint();
618       }
619
620       if (fitToWindow
621               || (!fitToWindow && (scrollPane.getHeight() > ((fm
622                       .getHeight() * nameHash.size()) + offy))))
623       {
624         draw(g, scrollPane.getWidth(), scrollPane.getHeight());
625         setPreferredSize(null);
626       }
627       else
628       {
629         setPreferredSize(new Dimension(scrollPane.getWidth(), fm
630                 .getHeight()
631                 * nameHash.size()));
632         draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
633       }
634
635       scrollPane.revalidate();
636     }
637   }
638
639   /**
640    * DOCUMENT ME!
641    * 
642    * @param fontSize
643    *          DOCUMENT ME!
644    */
645   public void setFont(Font font)
646   {
647     this.font = font;
648     repaint();
649   }
650
651   /**
652    * DOCUMENT ME!
653    * 
654    * @param g1
655    *          DOCUMENT ME!
656    * @param width
657    *          DOCUMENT ME!
658    * @param height
659    *          DOCUMENT ME!
660    */
661   public void draw(Graphics g1, int width, int height)
662   {
663     Graphics2D g2 = (Graphics2D) g1;
664     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
665             RenderingHints.VALUE_ANTIALIAS_ON);
666     g2.setColor(Color.white);
667     g2.fillRect(0, 0, width, height);
668
669     g2.setFont(font);
670
671     offy = font.getSize() + 10;
672
673     fm = g2.getFontMetrics(font);
674
675     labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
676
677     float wscale = (float) (width - labelLength - (offx * 2))
678             / tree.getMaxHeight();
679
680     SequenceNode top = tree.getTopNode();
681
682     if (top.count == 0)
683     {
684       top.count = ((SequenceNode) top.left()).count
685               + ((SequenceNode) top.right()).count;
686     }
687
688     float chunk = (float) (height - (offy)) / top.count;
689
690     drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
691
692     if (threshold != 0)
693     {
694       if (av.getCurrentTree() == tree)
695       {
696         g2.setColor(Color.red);
697       }
698       else
699       {
700         g2.setColor(Color.gray);
701       }
702
703       int x = (int) ((threshold * (float) (getWidth() - labelLength - (2 * offx))) + offx);
704
705       g2.drawLine(x, 0, x, getHeight());
706     }
707   }
708
709   /**
710    * DOCUMENT ME!
711    * 
712    * @param e
713    *          DOCUMENT ME!
714    */
715   public void mouseReleased(MouseEvent e)
716   {
717   }
718
719   /**
720    * DOCUMENT ME!
721    * 
722    * @param e
723    *          DOCUMENT ME!
724    */
725   public void mouseEntered(MouseEvent e)
726   {
727   }
728
729   /**
730    * DOCUMENT ME!
731    * 
732    * @param e
733    *          DOCUMENT ME!
734    */
735   public void mouseExited(MouseEvent e)
736   {
737   }
738
739   /**
740    * DOCUMENT ME!
741    * 
742    * @param e
743    *          DOCUMENT ME!
744    */
745   public void mouseClicked(MouseEvent evt)
746   {
747     if (highlightNode != null)
748     {
749       if (SwingUtilities.isRightMouseButton(evt))
750       {
751         Color col = JColorChooser.showDialog(this,
752                 "Select Sub-Tree Colour", highlightNode.color);
753         if (col != null)
754         {
755           setColor(highlightNode, col);
756         }
757       }
758       else if (evt.getClickCount() > 1)
759       {
760         tree.swapNodes(highlightNode);
761         tree.reCount(tree.getTopNode());
762         tree.findHeight(tree.getTopNode());
763       }
764       else
765       {
766         Vector leaves = new Vector();
767         tree.findLeaves(highlightNode, leaves);
768
769         for (int i = 0; i < leaves.size(); i++)
770         {
771           SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
772                   .element();
773           treeSelectionChanged(seq);
774         }
775         av.sendSelection();
776       }
777
778       PaintRefresher.Refresh(tp, av.getSequenceSetId());
779       repaint();
780     }
781   }
782
783   public void mouseMoved(MouseEvent evt)
784   {
785     av.setCurrentTree(tree);
786
787     Object ob = findElement(evt.getX(), evt.getY());
788
789     if (ob instanceof SequenceNode)
790     {
791       highlightNode = (SequenceNode) ob;
792       this.setToolTipText("<html>Left click to select leaves"
793               + "<br>Double-click to invert leaves"
794               + "<br>Right click to change colour");
795       repaint();
796
797     }
798     else
799     {
800       if (highlightNode != null)
801       {
802         highlightNode = null;
803         setToolTipText(null);
804         repaint();
805       }
806     }
807   }
808
809   public void mouseDragged(MouseEvent ect)
810   {
811   }
812
813   /**
814    * DOCUMENT ME!
815    * 
816    * @param e
817    *          DOCUMENT ME!
818    */
819   public void mousePressed(MouseEvent e)
820   {
821     av.setCurrentTree(tree);
822
823     int x = e.getX();
824     int y = e.getY();
825
826     Object ob = findElement(x, y);
827
828     if (ob instanceof SequenceI)
829     {
830       treeSelectionChanged((Sequence) ob);
831       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
832       repaint();
833       av.sendSelection();
834       return;
835     }
836     else if (!(ob instanceof SequenceNode))
837     {
838       // Find threshold
839       if (tree.getMaxHeight() != 0)
840       {
841         threshold = (float) (x - offx)
842                 / (float) (getWidth() - labelLength - (2 * offx));
843
844         tree.getGroups().removeAllElements();
845         tree.groupNodes(tree.getTopNode(), threshold);
846         setColor(tree.getTopNode(), Color.black);
847
848         AlignmentPanel[] aps = getAssociatedPanels();
849
850         for (int a = 0; a < aps.length; a++)
851         {
852           aps[a].av.setSelectionGroup(null);
853           aps[a].av.alignment.deleteAllGroups();
854           aps[a].av.sequenceColours = null;
855         }
856         colourGroups();
857       }
858
859       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
860       repaint();
861     }
862
863   }
864
865   void colourGroups()
866   {
867     AlignmentPanel[] aps = getAssociatedPanels();
868     for (int i = 0; i < tree.getGroups().size(); i++)
869     {
870       Color col = new Color((int) (Math.random() * 255), (int) (Math
871               .random() * 255), (int) (Math.random() * 255));
872       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
873
874       Vector l = tree.findLeaves((SequenceNode) tree.getGroups().elementAt(
875               i), new Vector());
876
877       Vector sequences = new Vector();
878
879       for (int j = 0; j < l.size(); j++)
880       {
881         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
882                 .element();
883
884         if (!sequences.contains(s1))
885         {
886           sequences.addElement(s1);
887         }
888       }
889
890       ColourSchemeI cs = null;
891
892       if (av.getGlobalColourScheme() != null)
893       {
894         if (av.getGlobalColourScheme() instanceof UserColourScheme)
895         {
896           cs = new UserColourScheme(((UserColourScheme) av
897                   .getGlobalColourScheme()).getColours());
898
899         }
900         else
901         {
902           cs = ColourSchemeProperty.getColour(sequences, av.alignment
903                   .getWidth(), ColourSchemeProperty.getColourName(av
904                   .getGlobalColourScheme()));
905         }
906
907         cs.setThreshold(av.getGlobalColourScheme().getThreshold(), av
908                 .getIgnoreGapsConsensus());
909       }
910
911       SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
912               false, 0, av.alignment.getWidth() - 1);
913
914       sg.setName("JTreeGroup:" + sg.hashCode());
915       sg.setIdColour(col);
916       for (int a = 0; a < aps.length; a++)
917       {
918         if (aps[a].av.getGlobalColourScheme() != null
919                 && aps[a].av.getGlobalColourScheme().conservationApplied())
920         {
921           Conservation c = new Conservation("Group",
922                   ResidueProperties.propHash, 3, sg.getSequences(null), sg
923                           .getStartRes(), sg.getEndRes());
924
925           c.calculate();
926           c.verdict(false, aps[a].av.ConsPercGaps);
927           sg.cs.setConservation(c);
928         }
929
930         aps[a].av.alignment.addGroup(sg);
931       }
932     }
933     // notify the panel to redo any group specific stuff.
934     for (int a=0;a<aps.length;a++)
935     {
936       aps[a].updateAnnotation();
937     }
938
939   }
940
941   /**
942    * DOCUMENT ME!
943    * 
944    * @param state
945    *          DOCUMENT ME!
946    */
947   public void setShowDistances(boolean state)
948   {
949     this.showDistances = state;
950     repaint();
951   }
952
953   /**
954    * DOCUMENT ME!
955    * 
956    * @param state
957    *          DOCUMENT ME!
958    */
959   public void setShowBootstrap(boolean state)
960   {
961     this.showBootstrap = state;
962     repaint();
963   }
964
965   /**
966    * DOCUMENT ME!
967    * 
968    * @param state
969    *          DOCUMENT ME!
970    */
971   public void setMarkPlaceholders(boolean state)
972   {
973     this.markPlaceholders = state;
974     repaint();
975   }
976
977   AlignmentPanel[] getAssociatedPanels()
978   {
979     if (applyToAllViews)
980     {
981       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
982     }
983     else
984     {
985       return new AlignmentPanel[]
986       { ap };
987     }
988   }
989 }