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