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