new ID constructor and selection sending
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
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.alignment.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())
252               : node.getName();
253
254       int charWidth = fm.stringWidth(name) + 3;
255       int charHeight = font.getSize();
256
257       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
258               charWidth, charHeight);
259
260       nameHash.put((SequenceI) node.element(), rect);
261
262       // Colour selected leaves differently
263       SequenceGroup selected = av.getSelectionGroup();
264
265       if ((selected != null)
266               && selected.getSequences(null).contains(
267                       (SequenceI) node.element()))
268       {
269         g.setColor(Color.gray);
270
271         g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
272         g.setColor(Color.white);
273       }
274
275       g.drawString(name, xend + 10, ypos + fm.getDescent());
276       g.setColor(Color.black);
277     }
278     else
279     {
280       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
281               offy);
282       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
283               offy);
284
285       float height = node.height;
286       float dist = node.dist;
287
288       int xstart = (int) ((height - dist) * scale) + offx;
289       int xend = (int) (height * scale) + offx;
290       int ypos = (int) (node.ycount * chunk) + offy;
291
292       g.setColor(((SequenceNode) node).color.darker());
293
294       // Draw horizontal line
295       g.drawLine(xstart, ypos, xend, ypos);
296       if (node == highlightNode)
297       {
298         g.fillRect(xend - 3, ypos - 3, 6, 6);
299       }
300       else
301       {
302         g.fillRect(xend - 2, ypos - 2, 4, 4);
303       }
304
305       int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
306               + offy;
307       int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
308               + offy;
309
310       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
311       nodeHash.put(node, pos);
312
313       g.drawLine((int) (height * scale) + offx, ystart,
314               (int) (height * scale) + offx, yend);
315
316       String nodeLabel = "";
317
318       if (showDistances && (node.dist > 0))
319       {
320         nodeLabel = new Format("%-.2f").form(node.dist);
321       }
322
323       if (showBootstrap && node.bootstrap > -1)
324       {
325         if (showDistances)
326         {
327           nodeLabel = nodeLabel + " : ";
328         }
329
330         nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
331       }
332
333       if (!nodeLabel.equals(""))
334       {
335         g.drawString(nodeLabel, xstart + 2, ypos - 2);
336       }
337     }
338   }
339
340   /**
341    * DOCUMENT ME!
342    * 
343    * @param x
344    *                DOCUMENT ME!
345    * @param y
346    *                DOCUMENT ME!
347    * 
348    * @return DOCUMENT ME!
349    */
350   public Object findElement(int x, int y)
351   {
352     Enumeration keys = nameHash.keys();
353
354     while (keys.hasMoreElements())
355     {
356       Object ob = keys.nextElement();
357       Rectangle rect = (Rectangle) nameHash.get(ob);
358
359       if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
360               && (y <= (rect.y + rect.height)))
361       {
362         return ob;
363       }
364     }
365
366     keys = nodeHash.keys();
367
368     while (keys.hasMoreElements())
369     {
370       Object ob = keys.nextElement();
371       Rectangle rect = (Rectangle) nodeHash.get(ob);
372
373       if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
374               && (y <= (rect.y + rect.height)))
375       {
376         return ob;
377       }
378     }
379
380     return null;
381   }
382
383   /**
384    * DOCUMENT ME!
385    * 
386    * @param pickBox
387    *                DOCUMENT ME!
388    */
389   public void pickNodes(Rectangle pickBox)
390   {
391     int width = getWidth();
392     int height = getHeight();
393
394     SequenceNode top = tree.getTopNode();
395
396     float wscale = (float) ((width * .8) - (offx * 2))
397             / tree.getMaxHeight();
398
399     if (top.count == 0)
400     {
401       top.count = ((SequenceNode) top.left()).count
402               + ((SequenceNode) top.right()).count;
403     }
404
405     float chunk = (float) (height - (offy)) / top.count;
406
407     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
408   }
409
410   /**
411    * DOCUMENT ME!
412    * 
413    * @param pickBox
414    *                DOCUMENT ME!
415    * @param node
416    *                DOCUMENT ME!
417    * @param chunk
418    *                DOCUMENT ME!
419    * @param scale
420    *                DOCUMENT ME!
421    * @param width
422    *                DOCUMENT ME!
423    * @param offx
424    *                DOCUMENT ME!
425    * @param offy
426    *                DOCUMENT ME!
427    */
428   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
429           float scale, int width, int offx, int offy)
430   {
431     if (node == null)
432     {
433       return;
434     }
435
436     if ((node.left() == null) && (node.right() == null))
437     {
438       float height = node.height;
439       float dist = node.dist;
440
441       int xstart = (int) ((height - dist) * scale) + offx;
442       int xend = (int) (height * scale) + offx;
443
444       int ypos = (int) (node.ycount * chunk) + offy;
445
446       if (pickBox.contains(new Point(xend, ypos)))
447       {
448         if (node.element() instanceof SequenceI)
449         {
450           SequenceI seq = (SequenceI) node.element();
451           SequenceGroup sg = av.getSelectionGroup();
452
453           if (sg != null)
454           {
455             sg.addOrRemove(seq, true);
456           }
457         }
458       }
459     }
460     else
461     {
462       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
463               offx, offy);
464       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
465               offx, offy);
466     }
467   }
468
469   /**
470    * DOCUMENT ME!
471    * 
472    * @param node
473    *                DOCUMENT ME!
474    * @param c
475    *                DOCUMENT ME!
476    */
477   public void setColor(SequenceNode node, Color c)
478   {
479     if (node == null)
480     {
481       return;
482     }
483
484     if ((node.left() == null) && (node.right() == null)) // TODO: internal node
485     {
486       node.color = c;
487
488       if (node.element() instanceof SequenceI)
489       {
490         AlignmentPanel[] aps = getAssociatedPanels();
491         for (int a = 0; a < aps.length; a++)
492         {
493           aps[a].av.setSequenceColour((SequenceI) node.element(), c);
494         }
495       }
496     }
497     else
498     {
499       node.color = c;
500       setColor((SequenceNode) node.left(), c);
501       setColor((SequenceNode) node.right(), c);
502     }
503   }
504
505   /**
506    * DOCUMENT ME!
507    */
508   void startPrinting()
509   {
510     Thread thread = new Thread(this);
511     thread.start();
512   }
513
514   // put printing in a thread to avoid painting problems
515   public void run()
516   {
517     PrinterJob printJob = PrinterJob.getPrinterJob();
518     PageFormat pf = printJob.pageDialog(printJob.defaultPage());
519
520     printJob.setPrintable(this, pf);
521
522     if (printJob.printDialog())
523     {
524       try
525       {
526         printJob.print();
527       } catch (Exception PrintException)
528       {
529         PrintException.printStackTrace();
530       }
531     }
532   }
533
534   /**
535    * DOCUMENT ME!
536    * 
537    * @param pg
538    *                DOCUMENT ME!
539    * @param pf
540    *                DOCUMENT ME!
541    * @param pi
542    *                DOCUMENT ME!
543    * 
544    * @return DOCUMENT ME!
545    * 
546    * @throws PrinterException
547    *                 DOCUMENT ME!
548    */
549   public int print(Graphics pg, PageFormat pf, int pi)
550           throws PrinterException
551   {
552     pg.setFont(font);
553     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
554
555     int pwidth = (int) pf.getImageableWidth();
556     int pheight = (int) pf.getImageableHeight();
557
558     int noPages = getHeight() / pheight;
559
560     if (pi > noPages)
561     {
562       return Printable.NO_SUCH_PAGE;
563     }
564
565     if (pwidth > getWidth())
566     {
567       pwidth = getWidth();
568     }
569
570     if (fitToWindow)
571     {
572       if (pheight > getHeight())
573       {
574         pheight = getHeight();
575       }
576
577       noPages = 0;
578     }
579     else
580     {
581       FontMetrics fm = pg.getFontMetrics(font);
582       int height = fm.getHeight() * nameHash.size();
583       pg.translate(0, -pi * pheight);
584       pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
585
586       // translate number of pages,
587       // height is screen size as this is the
588       // non overlapping text size
589       pheight = height;
590     }
591
592     draw(pg, pwidth, pheight);
593
594     return Printable.PAGE_EXISTS;
595   }
596
597   /**
598    * DOCUMENT ME!
599    * 
600    * @param g
601    *                DOCUMENT ME!
602    */
603   public void paintComponent(Graphics g)
604   {
605     super.paintComponent(g);
606     g.setFont(font);
607
608     if (tree == null)
609     {
610       g.drawString("Calculating tree....", 20, getHeight() / 2);
611     }
612     else
613     {
614       fm = g.getFontMetrics(font);
615
616       if (nameHash.size() == 0)
617       {
618         repaint();
619       }
620
621       if (fitToWindow
622               || (!fitToWindow && (scrollPane.getHeight() > ((fm
623                       .getHeight() * nameHash.size()) + offy))))
624       {
625         draw(g, scrollPane.getWidth(), scrollPane.getHeight());
626         setPreferredSize(null);
627       }
628       else
629       {
630         setPreferredSize(new Dimension(scrollPane.getWidth(), fm
631                 .getHeight()
632                 * 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     for (int i = 0; i < tree.getGroups().size(); i++)
869     {
870       Color col = new Color((int) (Math.random() * 255), (int) (Math
871               .random() * 255), (int) (Math.random() * 255));
872       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
873
874       Vector l = tree.findLeaves((SequenceNode) tree.getGroups().elementAt(
875               i), new Vector());
876
877       Vector sequences = new Vector();
878
879       for (int j = 0; j < l.size(); j++)
880       {
881         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
882                 .element();
883
884         if (!sequences.contains(s1))
885         {
886           sequences.addElement(s1);
887         }
888       }
889
890       ColourSchemeI cs = null;
891
892       if (av.getGlobalColourScheme() != null)
893       {
894         if (av.getGlobalColourScheme() instanceof UserColourScheme)
895         {
896           cs = new UserColourScheme(((UserColourScheme) av
897                   .getGlobalColourScheme()).getColours());
898
899         }
900         else
901         {
902           cs = ColourSchemeProperty.getColour(sequences, av.alignment
903                   .getWidth(), ColourSchemeProperty.getColourName(av
904                   .getGlobalColourScheme()));
905         }
906
907         cs.setThreshold(av.getGlobalColourScheme().getThreshold(), av
908                 .getIgnoreGapsConsensus());
909       }
910
911       SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
912               false, 0, av.alignment.getWidth() - 1);
913
914       sg.setName("JTreeGroup:" + sg.hashCode());
915       sg.setIdColour(col);
916       AlignmentPanel[] aps = getAssociatedPanels();
917       for (int a = 0; a < aps.length; a++)
918       {
919         if (aps[a].av.getGlobalColourScheme() != null
920                 && aps[a].av.getGlobalColourScheme().conservationApplied())
921         {
922           Conservation c = new Conservation("Group",
923                   ResidueProperties.propHash, 3, sg.getSequences(null), sg
924                           .getStartRes(), sg.getEndRes());
925
926           c.calculate();
927           c.verdict(false, aps[a].av.ConsPercGaps);
928           sg.cs.setConservation(c);
929         }
930
931         aps[a].av.alignment.addGroup(sg);
932       }
933     }
934
935   }
936
937   /**
938    * DOCUMENT ME!
939    * 
940    * @param state
941    *                DOCUMENT ME!
942    */
943   public void setShowDistances(boolean state)
944   {
945     this.showDistances = state;
946     repaint();
947   }
948
949   /**
950    * DOCUMENT ME!
951    * 
952    * @param state
953    *                DOCUMENT ME!
954    */
955   public void setShowBootstrap(boolean state)
956   {
957     this.showBootstrap = state;
958     repaint();
959   }
960
961   /**
962    * DOCUMENT ME!
963    * 
964    * @param state
965    *                DOCUMENT ME!
966    */
967   public void setMarkPlaceholders(boolean state)
968   {
969     this.markPlaceholders = state;
970     repaint();
971   }
972
973   AlignmentPanel[] getAssociatedPanels()
974   {
975     if (applyToAllViews)
976     {
977       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
978     }
979     else
980     {
981       return new AlignmentPanel[]
982       { ap };
983     }
984   }
985 }