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