JAL-2844 check added for clicks left of root node
[jalview.git] / src / jalview / ext / archaeopteryx / JalviewBinding.java
1 package jalview.ext.archaeopteryx;
2
3 import jalview.analysis.Conservation;
4 import jalview.api.AlignViewportI;
5 import jalview.datamodel.ColumnSelection;
6 import jalview.datamodel.HiddenColumns;
7 import jalview.datamodel.SequenceGroup;
8 import jalview.datamodel.SequenceI;
9 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
10 import jalview.gui.AlignViewport;
11 import jalview.gui.Desktop;
12 import jalview.gui.JvOptionPane;
13 import jalview.gui.PaintRefresher;
14 import jalview.schemes.ColourSchemeI;
15 import jalview.schemes.ColourSchemeProperty;
16 import jalview.schemes.UserColourScheme;
17 import jalview.structure.SelectionSource;
18 import jalview.structure.StructureSelectionManager;
19 import jalview.util.MappingUtils;
20 import jalview.util.MessageManager;
21 import jalview.viewmodel.AlignmentViewport;
22
23 import java.awt.Color;
24 import java.awt.Graphics;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.InputEvent;
27 import java.awt.event.MouseEvent;
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33
34 import javax.swing.JTabbedPane;
35 import javax.swing.SwingUtilities;
36 import javax.swing.event.ChangeEvent;
37 import javax.swing.event.ChangeListener;
38
39 import org.forester.archaeopteryx.MainFrame;
40 import org.forester.archaeopteryx.TreePanelUtil;
41 import org.forester.phylogeny.Phylogeny;
42 import org.forester.phylogeny.PhylogenyMethods;
43 import org.forester.phylogeny.PhylogenyNode;
44 import org.forester.phylogeny.data.BranchColor;
45
46 /**
47  * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
48  * it originates from, meaning that selecting sequences in the tree viewer also
49  * selects them in the alignment view and vice versa.
50  * 
51  * @author kjvanderheide
52  *
53  */
54 public final class JalviewBinding
55         implements ExternalTreeViewerBindingI<PhylogenyNode>
56 {
57   private org.forester.archaeopteryx.TreePanel treeView;
58
59   private AlignmentViewport parentAvport;
60
61   private final JTabbedPane treeTabs;
62
63   private final StructureSelectionManager ssm;
64
65   private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
66
67   private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
68
69   private float rootX;
70
71   private float furthestNodeX;
72
73   /**
74    * 
75    * @param archaeopteryx
76    * 
77    * @param jalviewAlignmentViewport
78    *          alignment viewport from which the tree was calculated.
79    * 
80    * @param alignMappedToNodes
81    *          map with sequences used to calculate the tree and matching tree
82    *          nodes as key, value pair respectively.
83    * 
84    * @param nodesMappedToAlign
85    *          map with tree nodes and matching sequences used to calculate the
86    *          tree as key, value pair respectively.
87    */
88   public JalviewBinding(final MainFrame archaeopteryx,
89           final AlignmentViewport jalviewAlignmentViewport,
90           final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
91           final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
92   {
93
94     if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
95     {
96       JvOptionPane.showMessageDialog(Desktop.desktop,
97               MessageManager.getString("label.tabs_detected_archaeopteryx"),
98               MessageManager.getString("label.problem_reading_tree_file"),
99               JvOptionPane.WARNING_MESSAGE);
100       ;
101     }
102
103     // deal with/prohibit null values here as that will cause problems
104     parentAvport = jalviewAlignmentViewport;
105     sequencesBoundToNodes = alignMappedToNodes;
106     nodesBoundToSequences = nodesMappedToAlign;
107
108     treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
109     treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
110     ssm = parentAvport.getStructureSelectionManager();
111     
112     ssm.addSelectionListener(this);
113     treeView.addMouseListener(this);
114     PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
115
116
117     treeTabs.addChangeListener(new ChangeListener()
118     {
119
120       @Override
121       public void stateChanged(ChangeEvent e)
122       {
123
124         SwingUtilities.invokeLater(new Runnable()
125         {
126
127           @Override
128           /**
129            * Resend the selection to the tree view when tabs get switched, this
130            * has to be buried in invokeLater as Forester first resets the tree
131            * view on switching tabs, without invokeLater this would get called
132            * before Forester resets which would nullify the selection.
133            */
134           public void run()
135           {
136             treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
137             parentAvport.sendSelection();
138             // PaintRefresher.Refresh(treeView,
139             // parentAvport.getSequenceSetId());
140
141           }
142         });
143
144       }
145
146     });
147
148   }
149
150   @Override
151   public void actionPerformed(ActionEvent e)
152   {
153   }
154
155   @Override
156   public void mouseClicked(MouseEvent e)
157   {
158     SwingUtilities.invokeLater(new Runnable() {
159
160       @Override
161       /**
162        * invokeLater so that this always runs after Forester's mouseClicked
163        */
164       public void run()
165       {
166         final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
167         if (node != null)
168         {
169           if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
170           // selection if shift
171           // IS NOT pressed
172           {
173             parentAvport.setSelectionGroup(null);
174
175           }
176           showNodeSelectionOnAlign(node);
177         }
178         else
179         {
180
181           partitionTree(e.getX());
182       }
183         PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
184
185
186
187       }
188     });
189
190
191   }
192
193   @Override
194   public void mousePressed(final MouseEvent e)
195   {
196
197   }
198   @Override
199   public void mouseReleased(MouseEvent e)
200   {
201   }
202
203   @Override
204   public void mouseEntered(MouseEvent e)
205   {
206   }
207
208   @Override
209   public void mouseExited(MouseEvent e)
210   {
211   }
212
213
214   @Override
215   public void selection(final SequenceGroup seqsel,
216           final ColumnSelection colsel, final HiddenColumns hidden,
217           final SelectionSource source)
218   {
219     if (source == parentAvport) // check if source is alignment from where the
220     // tree originates
221     {
222       treeView.setFoundNodes0(
223               new HashSet<Long>(seqsel.getSequences().size()));
224
225       for (SequenceI selectedSequence : seqsel.getSequences())
226       {
227         PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
228         if (matchingNode != null)
229         {
230           treeView.getFoundNodes0().add(matchingNode.getId());
231
232
233           if (!matchingNode.getBranchData().isHasBranchColor())
234           {
235             Color foundNodesColour = treeView.getTreeColorSet()
236                     .getFoundColor0();
237           matchingNode.getBranchData()
238                     .setBranchColor(new BranchColor(foundNodesColour));
239
240           }
241
242         }
243
244       }
245
246       treeView.repaint();
247     }
248
249
250   }
251
252   /**
253    * Partially refactored from TreeCanvas
254    */
255   public void partitionTree(final int x)
256   {
257     Phylogeny tree = treeView.getPhylogeny();
258
259     if (!tree.isEmpty())
260     {
261       // should be calculated on each partition as the tree can theoretically
262       // change in the meantime
263       PhylogenyNode furthestNode = PhylogenyMethods
264               .calculateNodeWithMaxDistanceToRoot(tree);
265       furthestNodeX = furthestNode.getXcoord();
266       rootX = tree.getRoot().getXcoord();
267
268       if (furthestNodeX != rootX && !(x < rootX || x > furthestNodeX)) // don't
269                                                                        // bother
270                                                                        // if 0
271                                                        // distance tree or
272                                                        // clicked x lies outside
273                                                        // of tree
274       {
275         Graphics g = treeView.getGraphics();
276         int panelHeight = treeView.getHeight();
277         g.drawLine(x, 0, x, panelHeight);
278
279         float threshold = (x - rootX) / (furthestNodeX - rootX);
280         List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
281                 tree.getRoot());
282
283       }
284     }
285
286
287   }
288
289   public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
290           PhylogenyNode node)
291   {
292
293     List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
294
295     parentAvport.setSelectionGroup(null);
296     parentAvport.getAlignment().deleteAllGroups();
297     parentAvport.clearSequenceColours();
298     if (parentAvport.getCodingComplement() != null)
299     {
300       parentAvport.getCodingComplement().setSelectionGroup(null);
301       parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
302       parentAvport.getCodingComplement().clearSequenceColours();
303     }
304
305
306     colourNodesAboveThreshold(nodesAboveThreshold, threshold,
307             node);
308     return nodesAboveThreshold;
309
310   }
311
312   /**
313    * Partially refactored from TreeCanvas colourGroups (can be made nicer).
314    * 
315    * @param nodeList
316    * @param threshold
317    * @param treeLength
318    * @param node
319    * @return
320    */
321   private List<PhylogenyNode> colourNodesAboveThreshold(
322           List<PhylogenyNode> nodeList, double threshold,
323           PhylogenyNode node)
324   {
325     // could also use PhylogenyMethods.getAllDescendants
326     for (PhylogenyNode childNode : node.getDescendants())
327     {
328       childNode.getBranchData()
329               .setBranchColor(new BranchColor(Color.black));
330       float nodeCutoff = (childNode.getXcoord() - rootX)
331               / (furthestNodeX - rootX);
332
333       if (nodeCutoff > threshold)
334       {
335         nodeList.add(childNode);
336
337         Color randomColor = new Color((int) (Math.random() * 255),
338                 (int) (Math.random() * 255), (int) (Math.random() * 255));
339         TreePanelUtil.colorizeSubtree(childNode,
340                 new BranchColor(randomColor));
341         List<PhylogenyNode> descendantNodes = childNode
342                 .getAllExternalDescendants();
343         List<SequenceI> descendantSeqs = new ArrayList<>(); // .forEach instead?
344         for (PhylogenyNode descNode : descendantNodes)
345         {
346           descendantSeqs.add(nodesBoundToSequences.get(descNode));
347         }
348
349         SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
350                 true, true, false, 0,
351                 parentAvport.getAlignment().getWidth() - 1);
352
353         ColourSchemeI cs = null;
354         if (parentAvport.getGlobalColourScheme() != null)
355         {
356           if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
357           {
358             cs = new UserColourScheme(
359                     ((UserColourScheme) parentAvport.getGlobalColourScheme())
360                             .getColours());
361           }
362           else
363           {
364             cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
365                     .getColourName(parentAvport.getGlobalColourScheme()));
366           }
367         }
368         sg.setColourScheme(cs);
369         sg.getGroupColourScheme().setThreshold(
370                 parentAvport.getResidueShading().getThreshold(),
371                 parentAvport.isIgnoreGapsConsensus());
372         // sg.recalcConservation();
373         sg.setName("Tree Group:" + sg.hashCode());
374         sg.setIdColour(randomColor);
375
376         if (parentAvport.getGlobalColourScheme() != null
377                 && parentAvport.getResidueShading().conservationApplied())
378         {
379           Conservation c = new Conservation("Group", sg.getSequences(null),
380                   sg.getStartRes(), sg.getEndRes());
381           c.calculate();
382           c.verdict(false, parentAvport.getConsPercGaps());
383           sg.cs.setConservation(c);
384         }
385
386         parentAvport.getAlignment().addGroup(new SequenceGroup(sg));
387         // TODO can we push all of the below into AlignViewportI?
388         final AlignViewportI codingComplement = parentAvport
389                 .getCodingComplement();
390         if (codingComplement != null)
391         {
392           SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
393                   parentAvport,
394                   codingComplement);
395           if (mappedGroup.getSequences().size() > 0)
396           {
397             codingComplement.getAlignment().addGroup(mappedGroup);
398             for (SequenceI seq : mappedGroup.getSequences())
399             {
400               codingComplement.setSequenceColour(seq,
401                       randomColor.brighter());
402             }
403           }
404         }
405
406
407       }
408
409       else
410       {
411         colourNodesAboveThreshold(nodeList, threshold,
412                 childNode);
413       }
414     }
415
416     // GROSS
417     ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
418
419     final AlignViewportI codingComplement = parentAvport
420             .getCodingComplement();
421     if (codingComplement != null)
422     {
423       ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
424     }
425
426     return nodeList;
427   }
428
429
430
431
432   // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
433   // double treeHeight)
434   // {
435   // List<PhylogenyNode> groups = new ArrayList<>();
436   // _groupNodes(groups, root, threshold, treeHeight);
437   // System.out.println(groups);
438   // return groups;
439   // }
440   //
441   // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
442   // float threshold, double treeHeight)
443   // {
444   // if (nd == null)
445   // {
446   // return;
447   // }
448   //
449   // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
450   // {
451   // groups.add(nd);
452   // }
453   // else
454   // {
455   // for (PhylogenyNode childNode : nd.getDescendants())
456   // {
457   // _groupNodes(groups, childNode, threshold, treeHeight);
458   // }
459   // }
460   // }
461   //
462
463
464   /**
465    * may or may not need an extra repaint on the alignment view (check what kira
466    * does)
467    */
468   @Override
469   public void showNodeSelectionOnAlign(final PhylogenyNode node)
470   {
471
472       if (node.isInternal())
473       {
474         showMatchingChildSequences(node);
475       }
476
477       else
478       {
479         showMatchingSequence(node);
480       }
481
482
483     }
484
485
486
487
488
489   @Override
490   public void showMatchingSequence(final PhylogenyNode nodeToMatch)
491   {
492     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
493     if (matchingSequence != null)
494     {
495       long nodeId = nodeToMatch.getId();
496       addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
497       treeSelectionChanged(matchingSequence);
498       parentAvport.sendSelection();
499
500     }
501   }
502
503   @Override
504   public void showMatchingChildSequences(final PhylogenyNode parentNode)
505   {
506     List<PhylogenyNode> childNodes = PhylogenyMethods
507             .getAllDescendants(parentNode);
508
509
510     for (PhylogenyNode childNode : childNodes)
511     {
512       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
513
514       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
515       if (matchingSequence != null)
516       {
517         long nodeId = childNode.getId();
518         addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
519
520         treeSelectionChanged(matchingSequence);
521
522       }
523
524     }
525     parentAvport.sendSelection();
526
527
528   }
529
530   /**
531    * Refactored from TreeCanvas.
532    * 
533    * @param sequence
534    *          of the node selected in the tree viewer.
535    */
536   @Override
537   public void treeSelectionChanged(final SequenceI sequence)
538   {
539     if (!parentAvport.isClosed()) // alignment view could be closed
540     {
541       SequenceGroup selected = parentAvport.getSelectionGroup();
542
543       if (selected == null)
544       {
545         selected = new SequenceGroup();
546         parentAvport.setSelectionGroup(selected);
547       }
548
549       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
550         selected.addOrRemove(sequence, true);
551     }
552
553   }
554   public void sortByTree_actionPerformed() {
555     // parentAvport.mirrorCommand(command, undo, ssm, source);
556
557     // alignFrame
558     // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
559     
560   }
561   
562
563   /**
564    * sort the associated alignment view by the current tree.
565    * 
566    * @param e
567    */
568   // @Override
569   // public void sortByTree_actionPerformed()// modify for Aptx
570   // {
571   //
572   // // if (treeCanvas.applyToAllViews)
573   //
574   // final ArrayList<CommandI> commands = new ArrayList<>();
575   // for (AlignmentPanel ap : PaintRefresher
576   // .getAssociatedPanels(parentAvport.getSequenceSetId()))
577   // {
578   // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel()));
579   // }
580   // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
581   // {
582   //
583   // @Override
584   // public void undoCommand(AlignmentI[] views)
585   // {
586   // for (CommandI tsort : commands)
587   // {
588   // tsort.undoCommand(views);
589   // }
590   // }
591   //
592   // @Override
593   // public int getSize()
594   // {
595   // return commands.size();
596   // }
597   //
598   // @Override
599   // public String getDescription()
600   // {
601   // return "Tree Sort (many views)";
602   // }
603   //
604   // @Override
605   // public void doCommand(AlignmentI[] views)
606   // {
607   //
608   // for (CommandI tsort : commands)
609   // {
610   // tsort.doCommand(views);
611   // }
612   // }
613   // });
614   // for (AlignmentPanel ap : PaintRefresher
615   // .getAssociatedPanels(parentAvport.getSequenceSetId()))
616   // {
617   // // ensure all the alignFrames refresh their GI after adding an undo item
618   // ap.alignFrame.updateEditMenuBar();
619   // }
620   // }
621   // else
622   // {
623   // treeCanvas.ap.alignFrame
624   // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
625   // }
626
627
628
629   /**
630    * TO BE MOVED
631    * 
632    * @param set
633    * @param objectToCheck
634    */
635   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
636   {
637     if (set.contains(objectToCheck))
638     {
639       set.remove(objectToCheck);
640     }
641     else
642     {
643       set.add(objectToCheck);
644     }
645
646   }
647
648   public AlignmentViewport getParentAvport()
649   {
650     return parentAvport;
651   }
652
653   public void setParentAvport(final AlignmentViewport parentAvport)
654   {
655     this.parentAvport = parentAvport;
656   }
657 }
658
659
660