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