JAL-2844 entirely X coord based partitioning
[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 < furthestNodeX) // don't bother if 0
269                                                        // distance tree or
270                                                        // clicked x lies outside
271                                                        // of tree
272       {
273         Graphics g = treeView.getGraphics();
274         int panelHeight = treeView.getHeight();
275         g.drawLine(x, 0, x, panelHeight);
276
277         float threshold = (x - rootX) / (furthestNodeX - rootX);
278         List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
279                 tree.getRoot());
280
281       }
282     }
283
284
285   }
286
287   public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
288           PhylogenyNode node)
289   {
290
291     List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
292
293     parentAvport.setSelectionGroup(null);
294     parentAvport.getAlignment().deleteAllGroups();
295     parentAvport.clearSequenceColours();
296     if (parentAvport.getCodingComplement() != null)
297     {
298       parentAvport.getCodingComplement().setSelectionGroup(null);
299       parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
300       parentAvport.getCodingComplement().clearSequenceColours();
301     }
302
303
304     colourNodesAboveThreshold(nodesAboveThreshold, threshold,
305             node);
306     return nodesAboveThreshold;
307
308   }
309
310   /**
311    * Partially refactored from TreeCanvas colourGroups (can be made nicer).
312    * 
313    * @param nodeList
314    * @param threshold
315    * @param treeLength
316    * @param node
317    * @return
318    */
319   private List<PhylogenyNode> colourNodesAboveThreshold(
320           List<PhylogenyNode> nodeList, double threshold,
321           PhylogenyNode node)
322   {
323     // could also use PhylogenyMethods.getAllDescendants
324     for (PhylogenyNode childNode : node.getDescendants())
325     {
326       childNode.getBranchData()
327               .setBranchColor(new BranchColor(Color.black));
328       float nodeCutoff = (childNode.getXcoord() - rootX)
329               / (furthestNodeX - rootX);
330
331       if (nodeCutoff > threshold)
332       {
333         nodeList.add(childNode);
334
335         Color randomColor = new Color((int) (Math.random() * 255),
336                 (int) (Math.random() * 255), (int) (Math.random() * 255));
337         TreePanelUtil.colorizeSubtree(childNode,
338                 new BranchColor(randomColor));
339         List<PhylogenyNode> descendantNodes = childNode
340                 .getAllExternalDescendants();
341         List<SequenceI> descendantSeqs = new ArrayList<>(); // .forEach instead?
342         for (PhylogenyNode descNode : descendantNodes)
343         {
344           descendantSeqs.add(nodesBoundToSequences.get(descNode));
345         }
346
347         SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
348                 true, true, false, 0,
349                 parentAvport.getAlignment().getWidth() - 1);
350
351         ColourSchemeI cs = null;
352         if (parentAvport.getGlobalColourScheme() != null)
353         {
354           if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
355           {
356             cs = new UserColourScheme(
357                     ((UserColourScheme) parentAvport.getGlobalColourScheme())
358                             .getColours());
359           }
360           else
361           {
362             cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
363                     .getColourName(parentAvport.getGlobalColourScheme()));
364           }
365         }
366         sg.setColourScheme(cs);
367         sg.getGroupColourScheme().setThreshold(
368                 parentAvport.getResidueShading().getThreshold(),
369                 parentAvport.isIgnoreGapsConsensus());
370         // sg.recalcConservation();
371         sg.setName("Tree Group:" + sg.hashCode());
372         sg.setIdColour(randomColor);
373
374         if (parentAvport.getGlobalColourScheme() != null
375                 && parentAvport.getResidueShading().conservationApplied())
376         {
377           Conservation c = new Conservation("Group", sg.getSequences(null),
378                   sg.getStartRes(), sg.getEndRes());
379           c.calculate();
380           c.verdict(false, parentAvport.getConsPercGaps());
381           sg.cs.setConservation(c);
382         }
383
384         parentAvport.getAlignment().addGroup(new SequenceGroup(sg));
385         // TODO can we push all of the below into AlignViewportI?
386         final AlignViewportI codingComplement = parentAvport
387                 .getCodingComplement();
388         if (codingComplement != null)
389         {
390           SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
391                   parentAvport,
392                   codingComplement);
393           if (mappedGroup.getSequences().size() > 0)
394           {
395             codingComplement.getAlignment().addGroup(mappedGroup);
396             for (SequenceI seq : mappedGroup.getSequences())
397             {
398               codingComplement.setSequenceColour(seq,
399                       randomColor.brighter());
400             }
401           }
402         }
403
404
405       }
406
407       else
408       {
409         colourNodesAboveThreshold(nodeList, threshold,
410                 childNode);
411       }
412     }
413
414     // GROSS
415     ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
416
417     final AlignViewportI codingComplement = parentAvport
418             .getCodingComplement();
419     if (codingComplement != null)
420     {
421       ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
422     }
423
424     return nodeList;
425   }
426
427
428
429
430   // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
431   // double treeHeight)
432   // {
433   // List<PhylogenyNode> groups = new ArrayList<>();
434   // _groupNodes(groups, root, threshold, treeHeight);
435   // System.out.println(groups);
436   // return groups;
437   // }
438   //
439   // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
440   // float threshold, double treeHeight)
441   // {
442   // if (nd == null)
443   // {
444   // return;
445   // }
446   //
447   // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
448   // {
449   // groups.add(nd);
450   // }
451   // else
452   // {
453   // for (PhylogenyNode childNode : nd.getDescendants())
454   // {
455   // _groupNodes(groups, childNode, threshold, treeHeight);
456   // }
457   // }
458   // }
459   //
460
461
462   /**
463    * may or may not need an extra repaint on the alignment view (check what kira
464    * does)
465    */
466   @Override
467   public void showNodeSelectionOnAlign(final PhylogenyNode node)
468   {
469
470       if (node.isInternal())
471       {
472         showMatchingChildSequences(node);
473       }
474
475       else
476       {
477         showMatchingSequence(node);
478       }
479
480
481     }
482
483
484
485
486
487   @Override
488   public void showMatchingSequence(final PhylogenyNode nodeToMatch)
489   {
490     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
491     if (matchingSequence != null)
492     {
493       long nodeId = nodeToMatch.getId();
494       addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
495       treeSelectionChanged(matchingSequence);
496       parentAvport.sendSelection();
497
498     }
499   }
500
501   @Override
502   public void showMatchingChildSequences(final PhylogenyNode parentNode)
503   {
504     List<PhylogenyNode> childNodes = PhylogenyMethods
505             .getAllDescendants(parentNode);
506
507
508     for (PhylogenyNode childNode : childNodes)
509     {
510       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
511
512       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
513       if (matchingSequence != null)
514       {
515         long nodeId = childNode.getId();
516         addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
517
518         treeSelectionChanged(matchingSequence);
519
520       }
521
522     }
523     parentAvport.sendSelection();
524
525
526   }
527
528   /**
529    * Refactored from TreeCanvas.
530    * 
531    * @param sequence
532    *          of the node selected in the tree viewer.
533    */
534   @Override
535   public void treeSelectionChanged(final SequenceI sequence)
536   {
537     if (!parentAvport.isClosed()) // alignment view could be closed
538     {
539       SequenceGroup selected = parentAvport.getSelectionGroup();
540
541       if (selected == null)
542       {
543         selected = new SequenceGroup();
544         parentAvport.setSelectionGroup(selected);
545       }
546
547       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
548         selected.addOrRemove(sequence, true);
549     }
550
551   }
552   public void sortByTree_actionPerformed() {
553     // parentAvport.mirrorCommand(command, undo, ssm, source);
554
555     // alignFrame
556     // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
557     
558   }
559   
560
561   /**
562    * sort the associated alignment view by the current tree.
563    * 
564    * @param e
565    */
566   // @Override
567   // public void sortByTree_actionPerformed()// modify for Aptx
568   // {
569   //
570   // // if (treeCanvas.applyToAllViews)
571   //
572   // final ArrayList<CommandI> commands = new ArrayList<>();
573   // for (AlignmentPanel ap : PaintRefresher
574   // .getAssociatedPanels(parentAvport.getSequenceSetId()))
575   // {
576   // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel()));
577   // }
578   // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
579   // {
580   //
581   // @Override
582   // public void undoCommand(AlignmentI[] views)
583   // {
584   // for (CommandI tsort : commands)
585   // {
586   // tsort.undoCommand(views);
587   // }
588   // }
589   //
590   // @Override
591   // public int getSize()
592   // {
593   // return commands.size();
594   // }
595   //
596   // @Override
597   // public String getDescription()
598   // {
599   // return "Tree Sort (many views)";
600   // }
601   //
602   // @Override
603   // public void doCommand(AlignmentI[] views)
604   // {
605   //
606   // for (CommandI tsort : commands)
607   // {
608   // tsort.doCommand(views);
609   // }
610   // }
611   // });
612   // for (AlignmentPanel ap : PaintRefresher
613   // .getAssociatedPanels(parentAvport.getSequenceSetId()))
614   // {
615   // // ensure all the alignFrames refresh their GI after adding an undo item
616   // ap.alignFrame.updateEditMenuBar();
617   // }
618   // }
619   // else
620   // {
621   // treeCanvas.ap.alignFrame
622   // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
623   // }
624
625
626
627   /**
628    * TO BE MOVED
629    * 
630    * @param set
631    * @param objectToCheck
632    */
633   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
634   {
635     if (set.contains(objectToCheck))
636     {
637       set.remove(objectToCheck);
638     }
639     else
640     {
641       set.add(objectToCheck);
642     }
643
644   }
645
646   public AlignmentViewport getParentAvport()
647   {
648     return parentAvport;
649   }
650
651   public void setParentAvport(final AlignmentViewport parentAvport)
652   {
653     this.parentAvport = parentAvport;
654   }
655 }
656
657
658