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