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