JAL-1497 check to see if tree structure is ready for display, and show a 'tree loadin...
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 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 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  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import java.awt.print.*;
26 import javax.swing.*;
27
28 import jalview.analysis.*;
29 import jalview.datamodel.*;
30 import jalview.schemes.*;
31 import jalview.util.*;
32
33 /**
34  * DOCUMENT ME!
35  * 
36  * @author $author$
37  * @version $Revision$
38  */
39 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
40         Printable, MouseMotionListener
41 {
42   /** DOCUMENT ME!! */
43   public static final String PLACEHOLDER = " * ";
44
45   NJTree tree;
46
47   JScrollPane scrollPane;
48
49   TreePanel tp;
50
51   AlignViewport av;
52
53   AlignmentPanel ap;
54
55   Font font;
56
57   FontMetrics fm;
58
59   boolean fitToWindow = true;
60
61   boolean showDistances = false;
62
63   boolean showBootstrap = false;
64
65   boolean markPlaceholders = false;
66
67   int offx = 20;
68
69   int offy;
70
71   float threshold;
72
73   String longestName;
74
75   int labelLength = -1;
76
77   Hashtable nameHash = new Hashtable();
78
79   Hashtable nodeHash = new Hashtable();
80
81   SequenceNode highlightNode;
82
83   boolean applyToAllViews = false;
84
85   /**
86    * Creates a new TreeCanvas object.
87    * 
88    * @param av
89    *          DOCUMENT ME!
90    * @param tree
91    *          DOCUMENT ME!
92    * @param scroller
93    *          DOCUMENT ME!
94    * @param label
95    *          DOCUMENT ME!
96    */
97   public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
98   {
99     this.tp = tp;
100     this.av = ap.av;
101     this.ap = ap;
102     font = av.getFont();
103     scrollPane = scroller;
104     addMouseListener(this);
105     addMouseMotionListener(this);
106     ToolTipManager.sharedInstance().registerComponent(this);
107   }
108
109   /**
110    * DOCUMENT ME!
111    * 
112    * @param sequence
113    *          DOCUMENT ME!
114    */
115   public void treeSelectionChanged(SequenceI sequence)
116   {
117     AlignmentPanel[] aps = getAssociatedPanels();
118
119     for (int a = 0; a < aps.length; a++)
120     {
121       SequenceGroup selected = aps[a].av.getSelectionGroup();
122
123       if (selected == null)
124       {
125         selected = new SequenceGroup();
126         aps[a].av.setSelectionGroup(selected);
127       }
128
129       selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
130       selected.addOrRemove(sequence, true);
131     }
132   }
133
134   /**
135    * DOCUMENT ME!
136    * 
137    * @param tree
138    *          DOCUMENT ME!
139    */
140   public void setTree(NJTree tree)
141   {
142     this.tree = tree;
143     tree.findHeight(tree.getTopNode());
144
145     // Now have to calculate longest name based on the leaves
146     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
147     boolean has_placeholders = false;
148     longestName = "";
149
150     for (int i = 0; i < leaves.size(); i++)
151     {
152       SequenceNode lf = (SequenceNode) leaves.elementAt(i);
153
154       if (lf.isPlaceholder())
155       {
156         has_placeholders = true;
157       }
158
159       if (longestName.length() < ((Sequence) lf.element()).getName()
160               .length())
161       {
162         longestName = TreeCanvas.PLACEHOLDER
163                 + ((Sequence) lf.element()).getName();
164       }
165     }
166
167     setMarkPlaceholders(has_placeholders);
168   }
169
170   /**
171    * DOCUMENT ME!
172    * 
173    * @param g
174    *          DOCUMENT ME!
175    * @param node
176    *          DOCUMENT ME!
177    * @param chunk
178    *          DOCUMENT ME!
179    * @param scale
180    *          DOCUMENT ME!
181    * @param width
182    *          DOCUMENT ME!
183    * @param offx
184    *          DOCUMENT ME!
185    * @param offy
186    *          DOCUMENT ME!
187    */
188   public void drawNode(Graphics g, SequenceNode node, float chunk,
189           float scale, int width, int offx, int offy)
190   {
191     if (node == null)
192     {
193       return;
194     }
195
196     if ((node.left() == null) && (node.right() == null))
197     {
198       // Drawing leaf node
199       float height = node.height;
200       float dist = node.dist;
201
202       int xstart = (int) ((height - dist) * scale) + offx;
203       int xend = (int) (height * scale) + offx;
204
205       int ypos = (int) (node.ycount * chunk) + offy;
206
207       if (node.element() instanceof SequenceI)
208       {
209         SequenceI seq = (SequenceI) ((SequenceNode) node).element();
210
211         if (av.getSequenceColour(seq) == Color.white)
212         {
213           g.setColor(Color.black);
214         }
215         else
216         {
217           g.setColor(av.getSequenceColour(seq).darker());
218         }
219       }
220       else
221       {
222         g.setColor(Color.black);
223       }
224
225       // Draw horizontal line
226       g.drawLine(xstart, ypos, xend, ypos);
227
228       String nodeLabel = "";
229
230       if (showDistances && (node.dist > 0))
231       {
232         nodeLabel = new Format("%-.2f").form(node.dist);
233       }
234
235       if (showBootstrap && node.bootstrap > -1)
236       {
237         if (showDistances)
238         {
239           nodeLabel = nodeLabel + " : ";
240         }
241
242         nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
243       }
244
245       if (!nodeLabel.equals(""))
246       {
247         g.drawString(nodeLabel, xstart + 2, ypos - 2);
248       }
249
250       String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
251               .getName()) : 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         if (aps != null)
491         {
492           for (int a = 0; a < aps.length; a++)
493           {
494             aps[a].av.setSequenceColour((SequenceI) node.element(), c);
495           }
496         }
497       }
498     }
499     else
500     {
501       node.color = c;
502       setColor((SequenceNode) node.left(), c);
503       setColor((SequenceNode) node.right(), c);
504     }
505   }
506
507   /**
508    * DOCUMENT ME!
509    */
510   void startPrinting()
511   {
512     Thread thread = new Thread(this);
513     thread.start();
514   }
515
516   // put printing in a thread to avoid painting problems
517   public void run()
518   {
519     PrinterJob printJob = PrinterJob.getPrinterJob();
520     PageFormat pf = printJob.pageDialog(printJob.defaultPage());
521
522     printJob.setPrintable(this, pf);
523
524     if (printJob.printDialog())
525     {
526       try
527       {
528         printJob.print();
529       } catch (Exception PrintException)
530       {
531         PrintException.printStackTrace();
532       }
533     }
534   }
535
536   /**
537    * DOCUMENT ME!
538    * 
539    * @param pg
540    *          DOCUMENT ME!
541    * @param pf
542    *          DOCUMENT ME!
543    * @param pi
544    *          DOCUMENT ME!
545    * 
546    * @return DOCUMENT ME!
547    * 
548    * @throws PrinterException
549    *           DOCUMENT ME!
550    */
551   public int print(Graphics pg, PageFormat pf, int pi)
552           throws PrinterException
553   {
554     pg.setFont(font);
555     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
556
557     int pwidth = (int) pf.getImageableWidth();
558     int pheight = (int) pf.getImageableHeight();
559
560     int noPages = getHeight() / pheight;
561
562     if (pi > noPages)
563     {
564       return Printable.NO_SUCH_PAGE;
565     }
566
567     if (pwidth > getWidth())
568     {
569       pwidth = getWidth();
570     }
571
572     if (fitToWindow)
573     {
574       if (pheight > getHeight())
575       {
576         pheight = getHeight();
577       }
578
579       noPages = 0;
580     }
581     else
582     {
583       FontMetrics fm = pg.getFontMetrics(font);
584       int height = fm.getHeight() * nameHash.size();
585       pg.translate(0, -pi * pheight);
586       pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
587
588       // translate number of pages,
589       // height is screen size as this is the
590       // non overlapping text size
591       pheight = height;
592     }
593
594     draw(pg, pwidth, pheight);
595
596     return Printable.PAGE_EXISTS;
597   }
598
599   /**
600    * DOCUMENT ME!
601    * 
602    * @param g
603    *          DOCUMENT ME!
604    */
605   public void paintComponent(Graphics g)
606   {
607     super.paintComponent(g);
608     g.setFont(font);
609
610     if (tree == null)
611     {
612       g.drawString(MessageManager.getString("label.calculating_tree") + "....", 20, getHeight() / 2);
613     }
614     else
615     {
616       fm = g.getFontMetrics(font);
617
618       if (nameHash.size() == 0)
619       {
620         repaint();
621       }
622
623       if (fitToWindow
624               || (!fitToWindow && (scrollPane.getHeight() > ((fm
625                       .getHeight() * nameHash.size()) + offy))))
626       {
627         draw(g, scrollPane.getWidth(), scrollPane.getHeight());
628         setPreferredSize(null);
629       }
630       else
631       {
632         setPreferredSize(new Dimension(scrollPane.getWidth(),
633                 fm.getHeight() * nameHash.size()));
634         draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
635       }
636
637       scrollPane.revalidate();
638     }
639   }
640
641   /**
642    * DOCUMENT ME!
643    * 
644    * @param fontSize
645    *          DOCUMENT ME!
646    */
647   public void setFont(Font font)
648   {
649     this.font = font;
650     repaint();
651   }
652
653   /**
654    * DOCUMENT ME!
655    * 
656    * @param g1
657    *          DOCUMENT ME!
658    * @param width
659    *          DOCUMENT ME!
660    * @param height
661    *          DOCUMENT ME!
662    */
663   public void draw(Graphics g1, int width, int height)
664   {
665     Graphics2D g2 = (Graphics2D) g1;
666     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
667             RenderingHints.VALUE_ANTIALIAS_ON);
668     g2.setColor(Color.white);
669     g2.fillRect(0, 0, width, height);
670     g2.setFont(font);
671
672     if (longestName==null || tree ==null)
673     {
674       g2.drawString("Calculating tree.",20,20);
675     }
676     offy = font.getSize() + 10;
677
678     fm = g2.getFontMetrics(font);
679
680     labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
681
682     float wscale = (float) (width - labelLength - (offx * 2))
683             / tree.getMaxHeight();
684
685     SequenceNode top = tree.getTopNode();
686
687     if (top.count == 0)
688     {
689       top.count = ((SequenceNode) top.left()).count
690               + ((SequenceNode) top.right()).count;
691     }
692
693     float chunk = (float) (height - (offy)) / top.count;
694
695     drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
696
697     if (threshold != 0)
698     {
699       if (av.getCurrentTree() == tree)
700       {
701         g2.setColor(Color.red);
702       }
703       else
704       {
705         g2.setColor(Color.gray);
706       }
707
708       int x = (int) ((threshold * (float) (getWidth() - labelLength - (2 * offx))) + offx);
709
710       g2.drawLine(x, 0, x, getHeight());
711     }
712   }
713
714   /**
715    * DOCUMENT ME!
716    * 
717    * @param e
718    *          DOCUMENT ME!
719    */
720   public void mouseReleased(MouseEvent e)
721   {
722   }
723
724   /**
725    * DOCUMENT ME!
726    * 
727    * @param e
728    *          DOCUMENT ME!
729    */
730   public void mouseEntered(MouseEvent e)
731   {
732   }
733
734   /**
735    * DOCUMENT ME!
736    * 
737    * @param e
738    *          DOCUMENT ME!
739    */
740   public void mouseExited(MouseEvent e)
741   {
742   }
743
744   /**
745    * DOCUMENT ME!
746    * 
747    * @param e
748    *          DOCUMENT ME!
749    */
750   public void mouseClicked(MouseEvent evt)
751   {
752     if (highlightNode != null)
753     {
754       if (SwingUtilities.isRightMouseButton(evt))
755       {
756         Color col = JColorChooser.showDialog(this,
757                 "Select Sub-Tree Colour", highlightNode.color);
758         if (col != null)
759         {
760           setColor(highlightNode, col);
761         }
762       }
763       else if (evt.getClickCount() > 1)
764       {
765         tree.swapNodes(highlightNode);
766         tree.reCount(tree.getTopNode());
767         tree.findHeight(tree.getTopNode());
768       }
769       else
770       {
771         Vector leaves = new Vector();
772         tree.findLeaves(highlightNode, leaves);
773
774         for (int i = 0; i < leaves.size(); i++)
775         {
776           SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
777                   .element();
778           treeSelectionChanged(seq);
779         }
780         av.sendSelection();
781       }
782
783       PaintRefresher.Refresh(tp, av.getSequenceSetId());
784       repaint();
785     }
786   }
787
788   public void mouseMoved(MouseEvent evt)
789   {
790     av.setCurrentTree(tree);
791
792     Object ob = findElement(evt.getX(), evt.getY());
793
794     if (ob instanceof SequenceNode)
795     {
796       highlightNode = (SequenceNode) ob;
797       this.setToolTipText("<html>" + MessageManager.getString("label.highlightnode"));
798       repaint();
799
800     }
801     else
802     {
803       if (highlightNode != null)
804       {
805         highlightNode = null;
806         setToolTipText(null);
807         repaint();
808       }
809     }
810   }
811
812   public void mouseDragged(MouseEvent ect)
813   {
814   }
815
816   /**
817    * DOCUMENT ME!
818    * 
819    * @param e
820    *          DOCUMENT ME!
821    */
822   public void mousePressed(MouseEvent e)
823   {
824     av.setCurrentTree(tree);
825
826     int x = e.getX();
827     int y = e.getY();
828
829     Object ob = findElement(x, y);
830
831     if (ob instanceof SequenceI)
832     {
833       treeSelectionChanged((Sequence) ob);
834       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
835       repaint();
836       av.sendSelection();
837       return;
838     }
839     else if (!(ob instanceof SequenceNode))
840     {
841       // Find threshold
842       if (tree.getMaxHeight() != 0)
843       {
844         threshold = (float) (x - offx)
845                 / (float) (getWidth() - labelLength - (2 * offx));
846
847         tree.getGroups().removeAllElements();
848         tree.groupNodes(tree.getTopNode(), threshold);
849         setColor(tree.getTopNode(), Color.black);
850
851         AlignmentPanel[] aps = getAssociatedPanels();
852
853         for (int a = 0; a < aps.length; a++)
854         {
855           aps[a].av.setSelectionGroup(null);
856           aps[a].av.getAlignment().deleteAllGroups();
857           aps[a].av.clearSequenceColours();
858         }
859         colourGroups();
860       }
861
862       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
863       repaint();
864     }
865
866   }
867
868   void colourGroups()
869   {
870     AlignmentPanel[] aps = getAssociatedPanels();
871     for (int i = 0; i < tree.getGroups().size(); i++)
872     {
873       Color col = new Color((int) (Math.random() * 255),
874               (int) (Math.random() * 255), (int) (Math.random() * 255));
875       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
876
877       Vector l = tree.findLeaves(
878               (SequenceNode) tree.getGroups().elementAt(i), new Vector());
879
880       Vector sequences = new Vector();
881
882       for (int j = 0; j < l.size(); j++)
883       {
884         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
885                 .element();
886
887         if (!sequences.contains(s1))
888         {
889           sequences.addElement(s1);
890         }
891       }
892
893       ColourSchemeI cs = null;
894       SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
895               false, 0, av.getAlignment().getWidth() - 1);
896
897       if (av.getGlobalColourScheme() != null)
898       {
899         if (av.getGlobalColourScheme() instanceof UserColourScheme)
900         {
901           cs = new UserColourScheme(
902                   ((UserColourScheme) av.getGlobalColourScheme())
903                           .getColours());
904
905         }
906         else
907         {
908           cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
909                   .getColourName(av.getGlobalColourScheme()));
910         }
911         // cs is null if shading is an annotationColourGradient
912         if (cs != null)
913         {
914           cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
915                   av.getIgnoreGapsConsensus());
916         }
917       }
918       sg.cs = cs;
919       // sg.recalcConservation();
920       sg.setName("JTreeGroup:" + sg.hashCode());
921       sg.setIdColour(col);
922       for (int a = 0; a < aps.length; a++)
923       {
924         if (aps[a].av.getGlobalColourScheme() != null
925                 && aps[a].av.getGlobalColourScheme().conservationApplied())
926         {
927           Conservation c = new Conservation("Group",
928                   ResidueProperties.propHash, 3, sg.getSequences(null),
929                   sg.getStartRes(), sg.getEndRes());
930
931           c.calculate();
932           c.verdict(false, aps[a].av.getConsPercGaps());
933           sg.cs.setConservation(c);
934         }
935
936         aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
937       }
938     }
939     // notify the panel to redo any group specific stuff.
940     for (int a = 0; a < aps.length; a++)
941     {
942       aps[a].updateAnnotation();
943       // TODO: JAL-868 - need to ensure view colour change message is broadcast
944       // to any Jmols listening in
945     }
946
947   }
948
949   /**
950    * DOCUMENT ME!
951    * 
952    * @param state
953    *          DOCUMENT ME!
954    */
955   public void setShowDistances(boolean state)
956   {
957     this.showDistances = state;
958     repaint();
959   }
960
961   /**
962    * DOCUMENT ME!
963    * 
964    * @param state
965    *          DOCUMENT ME!
966    */
967   public void setShowBootstrap(boolean state)
968   {
969     this.showBootstrap = state;
970     repaint();
971   }
972
973   /**
974    * DOCUMENT ME!
975    * 
976    * @param state
977    *          DOCUMENT ME!
978    */
979   public void setMarkPlaceholders(boolean state)
980   {
981     this.markPlaceholders = state;
982     repaint();
983   }
984
985   AlignmentPanel[] getAssociatedPanels()
986   {
987     if (applyToAllViews)
988     {
989       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
990     }
991     else
992     {
993       return new AlignmentPanel[]
994       { ap };
995     }
996   }
997 }