Merge remote-tracking branch 'origin/develop' into kjvdh/features/PhylogenyViewer
[jalview.git] / src / jalview / ext / archaeopteryx / JalviewBinding.java
1 package jalview.ext.archaeopteryx;
2
3 import jalview.datamodel.ColumnSelection;
4 import jalview.datamodel.HiddenColumns;
5 import jalview.datamodel.SequenceGroup;
6 import jalview.datamodel.SequenceI;
7 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
8 import jalview.gui.Desktop;
9 import jalview.gui.JvOptionPane;
10 import jalview.gui.PaintRefresher;
11 import jalview.structure.SelectionSource;
12 import jalview.structure.StructureSelectionManager;
13 import jalview.util.MessageManager;
14 import jalview.viewmodel.AlignmentViewport;
15
16 import java.awt.Graphics;
17 import java.awt.event.ActionEvent;
18 import java.awt.event.InputEvent;
19 import java.awt.event.MouseEvent;
20 import java.util.ArrayList;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25
26 import javax.swing.JTabbedPane;
27 import javax.swing.SwingUtilities;
28 import javax.swing.event.ChangeEvent;
29 import javax.swing.event.ChangeListener;
30
31 import org.forester.archaeopteryx.MainFrame;
32 import org.forester.phylogeny.Phylogeny;
33 import org.forester.phylogeny.PhylogenyMethods;
34 import org.forester.phylogeny.PhylogenyNode;
35
36 /**
37  * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
38  * it originates from, meaning that selecting sequences in the tree viewer also
39  * selects them in the alignment view and vice versa.
40  * 
41  * @author kjvanderheide
42  *
43  */
44 public final class JalviewBinding
45         implements ExternalTreeViewerBindingI<PhylogenyNode>
46 {
47   private org.forester.archaeopteryx.TreePanel treeView;
48
49   private AlignmentViewport parentAvport;
50
51   private final JTabbedPane treeTabs;
52
53   private final StructureSelectionManager ssm;
54
55   private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
56
57   private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
58
59   /**
60    * 
61    * @param archaeopteryx
62    * 
63    * @param jalviewAlignmentViewport
64    *          alignment viewport from which the tree was calculated.
65    * 
66    * @param alignMappedToNodes
67    *          map with sequences used to calculate the tree and matching tree
68    *          nodes as key, value pair respectively.
69    * 
70    * @param nodesMappedToAlign
71    *          map with tree nodes and matching sequences used to calculate the
72    *          tree as key, value pair respectively.
73    */
74   public JalviewBinding(final MainFrame archaeopteryx,
75           final AlignmentViewport jalviewAlignmentViewport,
76           final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
77           final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
78   {
79
80     if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
81     {
82       JvOptionPane.showMessageDialog(Desktop.desktop,
83               MessageManager.getString("label.tabs_detected_archaeopteryx"),
84               MessageManager.getString("label.problem_reading_tree_file"),
85               JvOptionPane.WARNING_MESSAGE);
86       ;
87     }
88
89     // deal with/prohibit null values here as that will cause problems
90     parentAvport = jalviewAlignmentViewport;
91     sequencesBoundToNodes = alignMappedToNodes;
92     nodesBoundToSequences = nodesMappedToAlign;
93
94     treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
95     treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
96     ssm = parentAvport.getStructureSelectionManager();
97     
98     ssm.addSelectionListener(this);
99     treeView.addMouseListener(this);
100     PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
101
102
103     treeTabs.addChangeListener(new ChangeListener()
104     {
105
106       @Override
107       public void stateChanged(ChangeEvent e)
108       {
109
110         SwingUtilities.invokeLater(new Runnable()
111         {
112
113           @Override
114           /**
115            * Resend the selection to the tree view when tabs get switched, this
116            * has to be buried in invokeLater as Forester first resets the tree
117            * view on switching tabs, without invokeLater this would get called
118            * before Forester resets which would nullify the selection.
119            */
120           public void run()
121           {
122             treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
123             parentAvport.sendSelection();
124             // PaintRefresher.Refresh(treeView,
125             // parentAvport.getSequenceSetId());
126
127           }
128         });
129
130       }
131
132     });
133
134   }
135
136   @Override
137   public void actionPerformed(ActionEvent e)
138   {
139   }
140
141   @Override
142   public void mouseClicked(MouseEvent e)
143   {
144     SwingUtilities.invokeLater(new Runnable() {
145
146       @Override
147       /**
148        * invokeLater so that this always runs after Forester's mouseClicked
149        */
150       public void run()
151       {
152         final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
153         if (node != null)
154         {
155           if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
156           // selection if shift
157           // IS NOT pressed
158           {
159             parentAvport.setSelectionGroup(null);
160
161           }
162           showNodeSelectionOnAlign(node);
163         }
164         else
165         {
166           partitionTree(e.getX());
167       }
168         PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
169       }
170     });
171
172
173   }
174
175   @Override
176   public void mousePressed(final MouseEvent e)
177   {
178
179   }
180   @Override
181   public void mouseReleased(MouseEvent e)
182   {
183   }
184
185   @Override
186   public void mouseEntered(MouseEvent e)
187   {
188   }
189
190   @Override
191   public void mouseExited(MouseEvent e)
192   {
193   }
194
195
196   @Override
197   public void selection(final SequenceGroup seqsel,
198           final ColumnSelection colsel, final HiddenColumns hidden,
199           final SelectionSource source)
200   {
201     if (source == parentAvport) // check if source is alignment from where the
202     // tree originates
203     {
204       treeView.setFoundNodes0(
205               new HashSet<Long>(seqsel.getSequences().size()));
206
207       for (SequenceI selectedSequence : seqsel.getSequences())
208       {
209         PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
210         if (matchingNode != null)
211         {
212           treeView.getFoundNodes0().add(matchingNode.getId());
213         }
214
215       }
216       treeView.repaint();
217
218     }
219
220
221   }
222
223   /**
224    * Partially refactored from TreeCanvas
225    */
226   public void partitionTree(final int x)
227   {
228     Phylogeny tree = treeView.getPhylogeny();
229
230     if (!tree.isEmpty())
231     {
232       double longestBranch = tree.calculateHeight(true);
233       if (longestBranch != 0)
234       {
235         Graphics g = treeView.getGraphics();
236         int panelHeight = treeView.getHeight();
237         int viewWidth = treeView.getWidth();
238         g.drawLine(x, 0, x, panelHeight);
239
240         // double relativeTreeWidth = treeDepth / viewWidth;
241         //
242         // System.out.println(relativeTreeWidth);
243
244         double threshold = x / longestBranch;
245         System.out.println(threshold);
246         List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
247                 longestBranch, tree.getRoot());
248         for (PhylogenyNode foundNode : foundNodes)
249         {
250           System.out.println(foundNode);
251         }
252
253         // groupNodes(threshold, tree.getRoot(), longestBranch);
254
255
256
257
258       }
259     }
260
261
262   }
263
264   public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
265           double treeLength, PhylogenyNode node)
266   {
267
268     List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
269
270     iterateNodesAboveThreshold(nodesAboveThreshold, threshold, treeLength,
271             node);
272     return nodesAboveThreshold;
273
274   }
275
276   private List<PhylogenyNode> iterateNodesAboveThreshold(
277           List<PhylogenyNode> nodeList, double threshold,
278           double treeLength, PhylogenyNode node)
279   {
280     for (PhylogenyNode childNode : node.getDescendants())
281     {
282       double nodeCutoff = childNode.calculateDistanceToRoot() / treeLength;
283
284       if (nodeCutoff > threshold)
285       {
286         nodeList.add(node);
287       }
288
289       else
290       {
291       iterateNodesAboveThreshold(nodeList, threshold, treeLength,
292                 childNode);
293       }
294
295     }
296     return nodeList;
297   }
298
299
300
301   // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
302   // double treeHeight)
303   // {
304   // List<PhylogenyNode> groups = new ArrayList<>();
305   // _groupNodes(groups, root, threshold, treeHeight);
306   // System.out.println(groups);
307   // return groups;
308   // }
309   //
310   // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
311   // float threshold, double treeHeight)
312   // {
313   // if (nd == null)
314   // {
315   // return;
316   // }
317   //
318   // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
319   // {
320   // groups.add(nd);
321   // }
322   // else
323   // {
324   // for (PhylogenyNode childNode : nd.getDescendants())
325   // {
326   // _groupNodes(groups, childNode, threshold, treeHeight);
327   // }
328   // }
329   // }
330   //
331
332
333   /**
334    * may or may not need an extra repaint on the alignment view (check what kira
335    * does)
336    */
337   @Override
338   public void showNodeSelectionOnAlign(final PhylogenyNode node)
339   {
340
341       if (node.isInternal())
342       {
343         showMatchingChildSequences(node);
344       }
345
346       else
347       {
348         showMatchingSequence(node);
349       }
350
351
352     }
353
354
355
356
357
358   @Override
359   public void showMatchingSequence(final PhylogenyNode nodeToMatch)
360   {
361     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
362     if (matchingSequence != null)
363     {
364       long nodeId = nodeToMatch.getId();
365       addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
366       treeSelectionChanged(matchingSequence);
367       parentAvport.sendSelection();
368
369     }
370   }
371
372   @Override
373   public void showMatchingChildSequences(final PhylogenyNode parentNode)
374   {
375     List<PhylogenyNode> childNodes = PhylogenyMethods
376             .getAllDescendants(parentNode);
377
378
379     for (PhylogenyNode childNode : childNodes)
380     {
381       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
382
383       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
384       if (matchingSequence != null)
385       {
386         long nodeId = childNode.getId();
387         addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
388
389         treeSelectionChanged(matchingSequence);
390
391       }
392
393     }
394     parentAvport.sendSelection();
395
396
397   }
398
399   /**
400    * Refactored from TreeCanvas.
401    * 
402    * @param sequence
403    *          of the node selected in the tree viewer.
404    */
405   @Override
406   public void treeSelectionChanged(final SequenceI sequence)
407   {
408     if (!parentAvport.isClosed()) // alignment view could be closed
409     {
410       SequenceGroup selected = parentAvport.getSelectionGroup();
411
412       if (selected == null)
413       {
414         selected = new SequenceGroup();
415         parentAvport.setSelectionGroup(selected);
416       }
417
418       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
419         selected.addOrRemove(sequence, true);
420     }
421
422   }
423   public void sortByTree_actionPerformed() {
424     // parentAvport.mirrorCommand(command, undo, ssm, source);
425
426     // alignFrame
427     // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
428     
429   }
430   
431
432   /**
433    * sort the associated alignment view by the current tree.
434    * 
435    * @param e
436    */
437   // @Override
438   // public void sortByTree_actionPerformed()// modify for Aptx
439   // {
440   //
441   // // if (treeCanvas.applyToAllViews)
442   //
443   // final ArrayList<CommandI> commands = new ArrayList<>();
444   // for (AlignmentPanel ap : PaintRefresher
445   // .getAssociatedPanels(parentAvport.getSequenceSetId()))
446   // {
447   // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
448   // }
449   // av.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
450   // {
451   //
452   // @Override
453   // public void undoCommand(AlignmentI[] views)
454   // {
455   // for (CommandI tsort : commands)
456   // {
457   // tsort.undoCommand(views);
458   // }
459   // }
460   //
461   // @Override
462   // public int getSize()
463   // {
464   // return commands.size();
465   // }
466   //
467   // @Override
468   // public String getDescription()
469   // {
470   // return "Tree Sort (many views)";
471   // }
472   //
473   // @Override
474   // public void doCommand(AlignmentI[] views)
475   // {
476   //
477   // for (CommandI tsort : commands)
478   // {
479   // tsort.doCommand(views);
480   // }
481   // }
482   // });
483   // for (AlignmentPanel ap : PaintRefresher
484   // .getAssociatedPanels(av.getSequenceSetId()))
485   // {
486   // // ensure all the alignFrames refresh their GI after adding an undo item
487   // ap.alignFrame.updateEditMenuBar();
488   // }
489   // }
490   // else
491   // {
492   // treeCanvas.ap.alignFrame
493   // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
494   // }
495
496
497
498   /**
499    * TO BE MOVED
500    * 
501    * @param set
502    * @param objectToCheck
503    */
504   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
505   {
506     if (set.contains(objectToCheck))
507     {
508       set.remove(objectToCheck);
509     }
510     else
511     {
512       set.add(objectToCheck);
513     }
514
515   }
516
517   public AlignmentViewport getParentAvport()
518   {
519     return parentAvport;
520   }
521
522   public void setParentAvport(final AlignmentViewport parentAvport)
523   {
524     this.parentAvport = parentAvport;
525   }
526 }
527
528
529