JAL-2844 added offset to x coord to make it equal 0 at the root node
[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         float xRoot = tree.getRoot().getXcoord();
245         double threshold = ((double) x - xRoot) / longestBranch;
246         System.out.println(threshold);
247         List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
248                 longestBranch, tree.getRoot());
249         for (PhylogenyNode foundNode : foundNodes)
250         {
251           System.out.println(foundNode);
252         }
253
254         // groupNodes(threshold, tree.getRoot(), longestBranch);
255
256
257
258
259       }
260     }
261
262
263   }
264
265   public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
266           double treeLength, PhylogenyNode node)
267   {
268
269     List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
270
271     iterateNodesAboveThreshold(nodesAboveThreshold, threshold, treeLength,
272             node);
273     return nodesAboveThreshold;
274
275   }
276
277   private List<PhylogenyNode> iterateNodesAboveThreshold(
278           List<PhylogenyNode> nodeList, double threshold,
279           double treeLength, PhylogenyNode node)
280   {
281     for (PhylogenyNode childNode : node.getDescendants())
282     {
283       double nodeCutoff = childNode.calculateDistanceToRoot() / treeLength;
284
285       if (nodeCutoff > threshold)
286       {
287         nodeList.add(node);
288       }
289
290       else
291       {
292       iterateNodesAboveThreshold(nodeList, threshold, treeLength,
293                 childNode);
294       }
295
296     }
297     return nodeList;
298   }
299
300
301
302   // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
303   // double treeHeight)
304   // {
305   // List<PhylogenyNode> groups = new ArrayList<>();
306   // _groupNodes(groups, root, threshold, treeHeight);
307   // System.out.println(groups);
308   // return groups;
309   // }
310   //
311   // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
312   // float threshold, double treeHeight)
313   // {
314   // if (nd == null)
315   // {
316   // return;
317   // }
318   //
319   // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
320   // {
321   // groups.add(nd);
322   // }
323   // else
324   // {
325   // for (PhylogenyNode childNode : nd.getDescendants())
326   // {
327   // _groupNodes(groups, childNode, threshold, treeHeight);
328   // }
329   // }
330   // }
331   //
332
333
334   /**
335    * may or may not need an extra repaint on the alignment view (check what kira
336    * does)
337    */
338   @Override
339   public void showNodeSelectionOnAlign(final PhylogenyNode node)
340   {
341
342       if (node.isInternal())
343       {
344         showMatchingChildSequences(node);
345       }
346
347       else
348       {
349         showMatchingSequence(node);
350       }
351
352
353     }
354
355
356
357
358
359   @Override
360   public void showMatchingSequence(final PhylogenyNode nodeToMatch)
361   {
362     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
363     if (matchingSequence != null)
364     {
365       long nodeId = nodeToMatch.getId();
366       addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
367       treeSelectionChanged(matchingSequence);
368       parentAvport.sendSelection();
369
370     }
371   }
372
373   @Override
374   public void showMatchingChildSequences(final PhylogenyNode parentNode)
375   {
376     List<PhylogenyNode> childNodes = PhylogenyMethods
377             .getAllDescendants(parentNode);
378
379
380     for (PhylogenyNode childNode : childNodes)
381     {
382       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
383
384       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
385       if (matchingSequence != null)
386       {
387         long nodeId = childNode.getId();
388         addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
389
390         treeSelectionChanged(matchingSequence);
391
392       }
393
394     }
395     parentAvport.sendSelection();
396
397
398   }
399
400   /**
401    * Refactored from TreeCanvas.
402    * 
403    * @param sequence
404    *          of the node selected in the tree viewer.
405    */
406   @Override
407   public void treeSelectionChanged(final SequenceI sequence)
408   {
409     if (!parentAvport.isClosed()) // alignment view could be closed
410     {
411       SequenceGroup selected = parentAvport.getSelectionGroup();
412
413       if (selected == null)
414       {
415         selected = new SequenceGroup();
416         parentAvport.setSelectionGroup(selected);
417       }
418
419       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
420         selected.addOrRemove(sequence, true);
421     }
422
423   }
424   public void sortByTree_actionPerformed() {
425     // parentAvport.mirrorCommand(command, undo, ssm, source);
426
427     // alignFrame
428     // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
429     
430   }
431   
432
433   /**
434    * sort the associated alignment view by the current tree.
435    * 
436    * @param e
437    */
438   // @Override
439   // public void sortByTree_actionPerformed()// modify for Aptx
440   // {
441   //
442   // // if (treeCanvas.applyToAllViews)
443   //
444   // final ArrayList<CommandI> commands = new ArrayList<>();
445   // for (AlignmentPanel ap : PaintRefresher
446   // .getAssociatedPanels(parentAvport.getSequenceSetId()))
447   // {
448   // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
449   // }
450   // av.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
451   // {
452   //
453   // @Override
454   // public void undoCommand(AlignmentI[] views)
455   // {
456   // for (CommandI tsort : commands)
457   // {
458   // tsort.undoCommand(views);
459   // }
460   // }
461   //
462   // @Override
463   // public int getSize()
464   // {
465   // return commands.size();
466   // }
467   //
468   // @Override
469   // public String getDescription()
470   // {
471   // return "Tree Sort (many views)";
472   // }
473   //
474   // @Override
475   // public void doCommand(AlignmentI[] views)
476   // {
477   //
478   // for (CommandI tsort : commands)
479   // {
480   // tsort.doCommand(views);
481   // }
482   // }
483   // });
484   // for (AlignmentPanel ap : PaintRefresher
485   // .getAssociatedPanels(av.getSequenceSetId()))
486   // {
487   // // ensure all the alignFrames refresh their GI after adding an undo item
488   // ap.alignFrame.updateEditMenuBar();
489   // }
490   // }
491   // else
492   // {
493   // treeCanvas.ap.alignFrame
494   // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
495   // }
496
497
498
499   /**
500    * TO BE MOVED
501    * 
502    * @param set
503    * @param objectToCheck
504    */
505   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
506   {
507     if (set.contains(objectToCheck))
508     {
509       set.remove(objectToCheck);
510     }
511     else
512     {
513       set.add(objectToCheck);
514     }
515
516   }
517
518   public AlignmentViewport getParentAvport()
519   {
520     return parentAvport;
521   }
522
523   public void setParentAvport(final AlignmentViewport parentAvport)
524   {
525     this.parentAvport = parentAvport;
526   }
527 }
528
529
530