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