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