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