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