c5650f5a973013c65c436c11991d76b7bd60a390
[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")
615               + "....", 20, getHeight() / 2);
616     }
617     else
618     {
619       fm = g.getFontMetrics(font);
620
621       if (nameHash.size() == 0)
622       {
623         repaint();
624       }
625
626       if (fitToWindow
627               || (!fitToWindow && (scrollPane.getHeight() > ((fm
628                       .getHeight() * nameHash.size()) + offy))))
629       {
630         draw(g, scrollPane.getWidth(), scrollPane.getHeight());
631         setPreferredSize(null);
632       }
633       else
634       {
635         setPreferredSize(new Dimension(scrollPane.getWidth(),
636                 fm.getHeight() * nameHash.size()));
637         draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
638       }
639
640       scrollPane.revalidate();
641     }
642   }
643
644   /**
645    * DOCUMENT ME!
646    * 
647    * @param fontSize
648    *          DOCUMENT ME!
649    */
650   public void setFont(Font font)
651   {
652     this.font = font;
653     repaint();
654   }
655
656   /**
657    * DOCUMENT ME!
658    * 
659    * @param g1
660    *          DOCUMENT ME!
661    * @param width
662    *          DOCUMENT ME!
663    * @param height
664    *          DOCUMENT ME!
665    */
666   public void draw(Graphics g1, int width, int height)
667   {
668     Graphics2D g2 = (Graphics2D) g1;
669     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
670             RenderingHints.VALUE_ANTIALIAS_ON);
671     g2.setColor(Color.white);
672     g2.fillRect(0, 0, width, height);
673     g2.setFont(font);
674
675     if (longestName == null || tree == null)
676     {
677       g2.drawString("Calculating tree.", 20, 20);
678     }
679     offy = font.getSize() + 10;
680
681     fm = g2.getFontMetrics(font);
682
683     labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
684
685     float wscale = (float) (width - labelLength - (offx * 2))
686             / tree.getMaxHeight();
687
688     SequenceNode top = tree.getTopNode();
689
690     if (top.count == 0)
691     {
692       top.count = ((SequenceNode) top.left()).count
693               + ((SequenceNode) top.right()).count;
694     }
695
696     float chunk = (float) (height - (offy)) / top.count;
697
698     drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
699
700     if (threshold != 0)
701     {
702       if (av.getCurrentTree() == tree)
703       {
704         g2.setColor(Color.red);
705       }
706       else
707       {
708         g2.setColor(Color.gray);
709       }
710
711       int x = (int) ((threshold * (float) (getWidth() - labelLength - (2 * offx))) + offx);
712
713       g2.drawLine(x, 0, x, getHeight());
714     }
715   }
716
717   /**
718    * DOCUMENT ME!
719    * 
720    * @param e
721    *          DOCUMENT ME!
722    */
723   public void mouseReleased(MouseEvent e)
724   {
725   }
726
727   /**
728    * DOCUMENT ME!
729    * 
730    * @param e
731    *          DOCUMENT ME!
732    */
733   public void mouseEntered(MouseEvent e)
734   {
735   }
736
737   /**
738    * DOCUMENT ME!
739    * 
740    * @param e
741    *          DOCUMENT ME!
742    */
743   public void mouseExited(MouseEvent e)
744   {
745   }
746
747   /**
748    * DOCUMENT ME!
749    * 
750    * @param e
751    *          DOCUMENT ME!
752    */
753   public void mouseClicked(MouseEvent evt)
754   {
755     if (highlightNode != null)
756     {
757       if (SwingUtilities.isRightMouseButton(evt))
758       {
759         Color col = JColorChooser.showDialog(this,
760                 MessageManager.getString("label.select_subtree_colour"), highlightNode.color);
761         if (col != null)
762         {
763           setColor(highlightNode, col);
764         }
765       }
766       else if (evt.getClickCount() > 1)
767       {
768         tree.swapNodes(highlightNode);
769         tree.reCount(tree.getTopNode());
770         tree.findHeight(tree.getTopNode());
771       }
772       else
773       {
774         Vector leaves = new Vector();
775         tree.findLeaves(highlightNode, leaves);
776
777         for (int i = 0; i < leaves.size(); i++)
778         {
779           SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
780                   .element();
781           treeSelectionChanged(seq);
782         }
783         av.sendSelection();
784       }
785
786       PaintRefresher.Refresh(tp, av.getSequenceSetId());
787       repaint();
788     }
789   }
790
791   public void mouseMoved(MouseEvent evt)
792   {
793     av.setCurrentTree(tree);
794
795     Object ob = findElement(evt.getX(), evt.getY());
796
797     if (ob instanceof SequenceNode)
798     {
799       highlightNode = (SequenceNode) ob;
800       this.setToolTipText("<html>"
801               + MessageManager.getString("label.highlightnode"));
802       repaint();
803
804     }
805     else
806     {
807       if (highlightNode != null)
808       {
809         highlightNode = null;
810         setToolTipText(null);
811         repaint();
812       }
813     }
814   }
815
816   public void mouseDragged(MouseEvent ect)
817   {
818   }
819
820   /**
821    * DOCUMENT ME!
822    * 
823    * @param e
824    *          DOCUMENT ME!
825    */
826   public void mousePressed(MouseEvent e)
827   {
828     av.setCurrentTree(tree);
829
830     int x = e.getX();
831     int y = e.getY();
832
833     Object ob = findElement(x, y);
834
835     if (ob instanceof SequenceI)
836     {
837       treeSelectionChanged((Sequence) ob);
838       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
839       repaint();
840       av.sendSelection();
841       return;
842     }
843     else if (!(ob instanceof SequenceNode))
844     {
845       // Find threshold
846       if (tree.getMaxHeight() != 0)
847       {
848         threshold = (float) (x - offx)
849                 / (float) (getWidth() - labelLength - (2 * offx));
850
851         tree.getGroups().removeAllElements();
852         tree.groupNodes(tree.getTopNode(), threshold);
853         setColor(tree.getTopNode(), Color.black);
854
855         AlignmentPanel[] aps = getAssociatedPanels();
856
857         for (int a = 0; a < aps.length; a++)
858         {
859           aps[a].av.setSelectionGroup(null);
860           aps[a].av.getAlignment().deleteAllGroups();
861           aps[a].av.clearSequenceColours();
862         }
863         colourGroups();
864       }
865
866       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
867       repaint();
868     }
869
870   }
871
872   void colourGroups()
873   {
874     AlignmentPanel[] aps = getAssociatedPanels();
875     for (int i = 0; i < tree.getGroups().size(); i++)
876     {
877       Color col = new Color((int) (Math.random() * 255),
878               (int) (Math.random() * 255), (int) (Math.random() * 255));
879       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
880
881       Vector l = tree.findLeaves(
882               (SequenceNode) tree.getGroups().elementAt(i), new Vector());
883
884       Vector sequences = new Vector();
885
886       for (int j = 0; j < l.size(); j++)
887       {
888         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
889                 .element();
890
891         if (!sequences.contains(s1))
892         {
893           sequences.addElement(s1);
894         }
895       }
896
897       ColourSchemeI cs = null;
898       SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
899               false, 0, av.getAlignment().getWidth() - 1);
900
901       if (av.getGlobalColourScheme() != null)
902       {
903         if (av.getGlobalColourScheme() instanceof UserColourScheme)
904         {
905           cs = new UserColourScheme(
906                   ((UserColourScheme) av.getGlobalColourScheme())
907                           .getColours());
908
909         }
910         else
911         {
912           cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
913                   .getColourName(av.getGlobalColourScheme()));
914         }
915         // cs is null if shading is an annotationColourGradient
916         if (cs != null)
917         {
918           cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
919                   av.getIgnoreGapsConsensus());
920         }
921       }
922       sg.cs = cs;
923       // sg.recalcConservation();
924       sg.setName("JTreeGroup:" + sg.hashCode());
925       sg.setIdColour(col);
926       for (int a = 0; a < aps.length; a++)
927       {
928         if (aps[a].av.getGlobalColourScheme() != null
929                 && aps[a].av.getGlobalColourScheme().conservationApplied())
930         {
931           Conservation c = new Conservation("Group",
932                   ResidueProperties.propHash, 3, sg.getSequences(null),
933                   sg.getStartRes(), sg.getEndRes());
934
935           c.calculate();
936           c.verdict(false, aps[a].av.getConsPercGaps());
937           sg.cs.setConservation(c);
938         }
939
940         aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
941       }
942     }
943     // notify the panel to redo any group specific stuff.
944     for (int a = 0; a < aps.length; a++)
945     {
946       aps[a].updateAnnotation();
947       // TODO: JAL-868 - need to ensure view colour change message is broadcast
948       // to any Jmols listening in
949     }
950
951   }
952
953   /**
954    * DOCUMENT ME!
955    * 
956    * @param state
957    *          DOCUMENT ME!
958    */
959   public void setShowDistances(boolean state)
960   {
961     this.showDistances = state;
962     repaint();
963   }
964
965   /**
966    * DOCUMENT ME!
967    * 
968    * @param state
969    *          DOCUMENT ME!
970    */
971   public void setShowBootstrap(boolean state)
972   {
973     this.showBootstrap = state;
974     repaint();
975   }
976
977   /**
978    * DOCUMENT ME!
979    * 
980    * @param state
981    *          DOCUMENT ME!
982    */
983   public void setMarkPlaceholders(boolean state)
984   {
985     this.markPlaceholders = state;
986     repaint();
987   }
988
989   AlignmentPanel[] getAssociatedPanels()
990   {
991     if (applyToAllViews)
992     {
993       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
994     }
995     else
996     {
997       return new AlignmentPanel[]
998       { ap };
999     }
1000   }
1001 }