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