JAL-2874 added filtering when a subtree is selected
[jalview.git] / src / jalview / ext / archaeopteryx / JalviewBinding.java
1 package jalview.ext.archaeopteryx;
2
3 import jalview.analysis.AlignmentSorter;
4 import jalview.analysis.Conservation;
5 import jalview.api.AlignViewportI;
6 import jalview.commands.CommandI;
7 import jalview.commands.OrderCommand;
8 import jalview.datamodel.ColumnSelection;
9 import jalview.datamodel.HiddenColumns;
10 import jalview.datamodel.SequenceGroup;
11 import jalview.datamodel.SequenceI;
12 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
13 import jalview.gui.AlignViewport;
14 import jalview.gui.AlignmentPanel;
15 import jalview.gui.Desktop;
16 import jalview.gui.JvOptionPane;
17 import jalview.gui.PaintRefresher;
18 import jalview.schemes.ColourSchemeI;
19 import jalview.schemes.ColourSchemeProperty;
20 import jalview.schemes.UserColourScheme;
21 import jalview.structure.SelectionSource;
22 import jalview.structure.StructureSelectionManager;
23 import jalview.util.MappingUtils;
24 import jalview.util.MessageManager;
25 import jalview.viewmodel.AlignmentViewport;
26
27 import java.awt.Color;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.InputEvent;
30 import java.awt.event.MouseEvent;
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 import javax.swing.JTabbedPane;
38 import javax.swing.SwingUtilities;
39 import javax.swing.event.ChangeEvent;
40 import javax.swing.event.ChangeListener;
41 import javax.swing.event.InternalFrameAdapter;
42 import javax.swing.event.InternalFrameEvent;
43
44 import org.forester.archaeopteryx.MainFrame;
45 import org.forester.phylogeny.Phylogeny;
46 import org.forester.phylogeny.PhylogenyMethods;
47 import org.forester.phylogeny.PhylogenyNode;
48 import org.forester.phylogeny.data.BranchColor;
49
50 /**
51  * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
52  * it originates from, meaning that selecting sequences in the tree viewer also
53  * selects them in the alignment view and vice versa.
54  * 
55  * @author kjvanderheide
56  *
57  */
58 public final class JalviewBinding
59         implements ExternalTreeViewerBindingI<PhylogenyNode>
60 {
61   private final MainFrame aptxFrame;
62
63   private org.forester.archaeopteryx.TreePanel treeView;
64
65   private AlignmentViewport parentAvport;
66
67   private final JTabbedPane treeTabs;
68
69   private final StructureSelectionManager ssm;
70
71   private AlignmentPanel[] associatedPanels;
72
73   private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
74
75   private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
76
77   private float rootX;
78
79   private float furthestNodeX;
80
81   private int nrTreeGroups = 0;
82
83   private boolean applyToAllViews = false;
84
85   /**
86    * 
87    * @param archaeopteryx
88    * 
89    * @param jalviewAlignmentViewport
90    *          alignment viewport from which the tree was calculated.
91    * 
92    * @param alignMappedToNodes
93    *          map with sequences used to calculate the tree and matching tree
94    *          nodes as key, value pair respectively.
95    * 
96    * @param nodesMappedToAlign
97    *          map with tree nodes and matching sequences used to calculate the
98    *          tree as key, value pair respectively.
99    */
100   public JalviewBinding(final MainFrame archaeopteryx,
101           final AlignmentViewport jalviewAlignmentViewport,
102           final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
103           final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
104   {
105
106     if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
107     {
108       JvOptionPane.showMessageDialog(Desktop.desktop,
109               MessageManager.getString("label.tabs_detected_archaeopteryx"),
110               MessageManager.getString("label.problem_reading_tree_file"),
111               JvOptionPane.WARNING_MESSAGE);
112
113     }
114
115     // deal with/prohibit null values here as that will cause problems
116     aptxFrame = archaeopteryx;
117     parentAvport = jalviewAlignmentViewport;
118     sequencesBoundToNodes = alignMappedToNodes;
119     nodesBoundToSequences = nodesMappedToAlign;
120
121     treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
122     treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
123     ssm = parentAvport.getStructureSelectionManager();
124     
125     ssm.addSelectionListener(this);
126     treeView.addMouseListener(this);
127
128     PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
129     associatedPanels = PaintRefresher
130             .getAssociatedPanels(parentAvport.getSequenceSetId());
131
132     aptxFrame.addInternalFrameListener(new InternalFrameAdapter()
133     {
134
135       @Override
136       public void internalFrameClosed(InternalFrameEvent e)
137       {
138         AptxInit.getAllAptxFrames().remove(aptxFrame);
139         ssm.removeSelectionListener(JalviewBinding.this);
140       }
141
142     });
143
144     treeTabs.addChangeListener(new ChangeListener()
145     {
146
147       @Override
148       public void stateChanged(ChangeEvent e)
149       {
150
151         SwingUtilities.invokeLater(new Runnable()
152         {
153
154           @Override
155           /**
156            * Resend the selection to the tree view when tabs get switched, this
157            * has to be buried in invokeLater as Forester first resets the tree
158            * view on switching tabs, without invokeLater this would get called
159            * before Forester resets which would nullify the selection.
160            */
161           public void run()
162           {
163             treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
164             parentAvport.sendSelection();
165             // PaintRefresher.Refresh(treeView,
166             // parentAvport.getSequenceSetId());
167
168           }
169         });
170
171       }
172
173     });
174
175   }
176
177   @Override
178   public void actionPerformed(ActionEvent e)
179   {
180     if (treeView.isCurrentTreeIsSubtree())
181     {
182     LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
183             parentAvport.getAlignment().getSequencesArray(),
184             treeView.getPhylogeny());
185     bindAptxNodes.associateLeavesToSequences();
186     sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
187     nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
188     AptxInit.bindNodesToJalviewSequences(aptxFrame, parentAvport,
189             sequencesBoundToNodes, nodesBoundToSequences);
190
191
192     for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
193     {
194       if (!sequencesBoundToNodes.containsKey(seq))
195       {
196         parentAvport.hideSequence(new SequenceI[] { seq });
197       }
198       }
199     }
200
201
202
203   }
204
205   @Override
206   public void mouseClicked(MouseEvent e)
207   {
208     SwingUtilities.invokeLater(new Runnable() {
209
210       @Override
211       /**
212        * invokeLater so that this always runs after Forester's mouseClicked
213        */
214       public void run()
215       {
216
217
218         final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
219         if (node != null)
220         {
221           if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
222           // selection if shift
223           // IS NOT pressed
224           {
225             parentAvport.setSelectionGroup(null);
226
227           }
228           showNodeSelectionOnAlign(node);
229
230         }
231         else
232         {
233           partitionTree(e.getX());
234
235       }
236         PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
237         treeView.repaint();
238
239
240
241       }
242     });
243
244
245   }
246
247   @Override
248   public void mousePressed(final MouseEvent e)
249   {
250
251   }
252   @Override
253   public void mouseReleased(MouseEvent e)
254   {
255   }
256
257   @Override
258   public void mouseEntered(MouseEvent e)
259   {
260   }
261
262   @Override
263   public void mouseExited(MouseEvent e)
264   {
265   }
266
267
268   @Override
269   public void selection(final SequenceGroup seqsel,
270           final ColumnSelection colsel, final HiddenColumns hidden,
271           final SelectionSource source)
272   {
273     if (source == parentAvport) // check if source is alignment from where the
274     // tree originates
275     {
276       treeView.setFoundNodes0(
277               new HashSet<Long>(seqsel.getSequences().size()));
278
279
280       for (SequenceI selectedSequence : seqsel.getSequences())
281       {
282         PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
283         if (matchingNode != null)
284         {
285           treeView.getFoundNodes0().add(matchingNode.getId());
286
287
288           if (!matchingNode.getBranchData().isHasBranchColor())
289           {
290             // Color foundNodesColour = treeView.getTreeColorSet()
291             // .getFoundColor0();
292             // matchingNode.getBranchData()
293             // .setBranchColor(new BranchColor(foundNodesColour));
294
295           }
296
297         }
298
299       }
300
301       treeView.repaint();
302     }
303
304
305   }
306
307   /**
308    * Partially refactored from TreeCanvas
309    */
310   public void partitionTree(final int x)
311   {
312     Phylogeny tree = treeView.getPhylogeny();
313
314     if (!tree.isEmpty())
315     {
316       // should be calculated on each partition as the tree can theoretically
317       // change in the meantime
318       PhylogenyNode furthestNode = PhylogenyMethods
319               .calculateNodeWithMaxDistanceToRoot(tree);
320       furthestNodeX = furthestNode.getXcoord();
321       rootX = tree.getRoot().getXcoord();
322
323
324       // don't bother if 0 distance tree or clicked x lies outside of tree
325       if (furthestNodeX != rootX && !(x > furthestNodeX))
326       {
327         float threshold = (x - rootX) / (furthestNodeX - rootX);
328         List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
329                 tree.getRoot());
330
331       }
332     }
333
334
335   }
336
337   public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
338           PhylogenyNode node)
339   {
340
341     List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
342
343     parentAvport.setSelectionGroup(null);
344     parentAvport.getAlignment().deleteAllGroups();
345     parentAvport.clearSequenceColours();
346     if (parentAvport.getCodingComplement() != null)
347     {
348       parentAvport.getCodingComplement().setSelectionGroup(null);
349       parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
350       parentAvport.getCodingComplement().clearSequenceColours();
351     }
352
353
354     colourNodesAboveThreshold(nodesAboveThreshold, threshold,
355             node);
356     return nodesAboveThreshold;
357
358   }
359
360   /**
361    * Partially refactored from TreeCanvas colourGroups (can be made nicer).
362    * 
363    * @param nodeList
364    * @param threshold
365    * @param treeLength
366    * @param node
367    * @return
368    */
369   private List<PhylogenyNode> colourNodesAboveThreshold(
370           List<PhylogenyNode> nodeList, double threshold,
371           PhylogenyNode node)
372   {
373
374     for (PhylogenyNode childNode : node.getDescendants())
375     {
376       childNode.getBranchData()
377               .setBranchColor(new BranchColor(Color.black));
378       float nodeCutoff = (childNode.getXcoord() - rootX)
379               / (furthestNodeX - rootX);
380
381       if (nodeCutoff > threshold)
382       {
383         nodeList.add(childNode);
384
385         Color randomColour = new Color((int) (Math.random() * 255),
386                 (int) (Math.random() * 255), (int) (Math.random() * 255));
387         childNode.getBranchData()
388                 .setBranchColor(new BranchColor(randomColour));
389
390         List<SequenceI> groupSeqs = new ArrayList<>();
391         SequenceI seq = nodesBoundToSequences.get(childNode);
392         if (seq != null)
393         {
394           groupSeqs.add(seq);
395           parentAvport.setSequenceColour(seq, randomColour);
396         }
397
398         List<PhylogenyNode> descendantNodes = PhylogenyMethods
399                 .getAllDescendants(childNode);
400         // .forEach instead?
401         for (PhylogenyNode descNode : descendantNodes)
402         {
403           seq = nodesBoundToSequences.get(descNode);
404           if (seq != null)
405           {
406             groupSeqs.add(seq);
407             parentAvport.setSequenceColour(seq, randomColour);
408           }
409
410           descNode.getBranchData()
411                   .setBranchColor(new BranchColor(randomColour));
412         }
413
414         if (groupSeqs != null)
415         {
416           nrTreeGroups++;
417           groupThresholdSequences(groupSeqs, randomColour);
418         }}
419
420       else
421       {
422         colourNodesAboveThreshold(nodeList, threshold, childNode);
423       }
424     }
425
426
427         for (AlignmentPanel associatedPanel : associatedPanels) {
428
429         associatedPanel.updateAnnotation();
430
431         final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
432                 .getCodingComplement();
433         if (codingComplement != null)
434         {
435           // GROSS
436           ((AlignViewport) codingComplement).getAlignPanel()
437                   .updateAnnotation();
438         }
439       }
440
441
442     return nodeList;
443   }
444
445   public void groupThresholdSequences(List<SequenceI> groupedSeqs,
446           Color groupColour)
447   {
448     SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
449             true, true, false, 0,
450             parentAvport.getAlignment().getWidth() - 1);
451
452     ColourSchemeI cs = null;
453     if (parentAvport.getGlobalColourScheme() != null)
454     {
455       if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
456       {
457         cs = new UserColourScheme(
458                 ((UserColourScheme) parentAvport.getGlobalColourScheme())
459                         .getColours());
460       }
461       else
462       {
463         cs = ColourSchemeProperty.getColourScheme(treeGroup,
464                 ColourSchemeProperty.getColourName(
465                         parentAvport.getGlobalColourScheme()));
466       }
467
468     }
469     treeGroup.setColourScheme(cs);
470     treeGroup.getGroupColourScheme().setThreshold(
471             parentAvport.getResidueShading().getThreshold(),
472             parentAvport.isIgnoreGapsConsensus());
473
474     treeGroup.setName("Tree Group " + nrTreeGroups);
475     treeGroup.setIdColour(groupColour);
476
477     for (AlignmentPanel associatedPanel : associatedPanels)
478     {
479       AlignViewportI altViewport = associatedPanel
480               .getAlignViewport();
481
482       if (altViewport.getGlobalColourScheme() != null
483               && altViewport.getResidueShading()
484                       .conservationApplied())
485       {
486         Conservation conserv = new Conservation(treeGroup.getName(),
487                 treeGroup.getSequences(null), treeGroup.getStartRes(),
488                 treeGroup.getEndRes());
489         conserv.calculate();
490         conserv.verdict(false, altViewport.getConsPercGaps());
491         treeGroup.getGroupColourScheme().setConservation(conserv);
492       }
493
494       altViewport.getAlignment().addGroup(treeGroup);
495       // TODO can we push all of the below into AlignViewportI?
496       final AlignViewportI codingComplement = altViewport
497               .getCodingComplement();
498       if (codingComplement != null)
499       {
500         SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
501                 parentAvport, codingComplement);
502         if (mappedGroup.getSequences().size() > 0)
503         {
504           codingComplement.getAlignment().addGroup(mappedGroup);
505           for (SequenceI seq : mappedGroup.getSequences())
506           {
507             codingComplement.setSequenceColour(seq, groupColour.brighter());
508           }
509         }
510       }
511
512     }
513
514   }
515
516   /**
517    * may or may not need an extra repaint on the alignment view (check what kira
518    * does)
519    */
520   @Override
521   public void showNodeSelectionOnAlign(final PhylogenyNode node)
522   {
523
524       if (node.isInternal())
525       {
526         showMatchingChildSequences(node);
527       }
528
529       else
530       {
531         showMatchingSequence(node);
532       }
533
534
535     }
536
537
538
539
540
541   @Override
542   public void showMatchingSequence(final PhylogenyNode nodeToMatch)
543   {
544     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
545     if (matchingSequence != null)
546     {
547       long nodeId = nodeToMatch.getId();
548       addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
549       treeSelectionChanged(matchingSequence);
550       parentAvport.sendSelection();
551
552     }
553   }
554
555   @Override
556   public void showMatchingChildSequences(final PhylogenyNode parentNode)
557   {
558     // redundancy here, Forester already iterates through tree to get all
559     // descendants
560     List<PhylogenyNode> childNodes = PhylogenyMethods
561             .getAllDescendants(parentNode);
562
563
564     for (PhylogenyNode childNode : childNodes)
565     {
566       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
567
568       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
569       if (matchingSequence != null)
570       {
571         long nodeId = childNode.getId();
572         addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
573
574         treeSelectionChanged(matchingSequence);
575
576       }
577
578     }
579     parentAvport.sendSelection();
580
581
582   }
583
584   /**
585    * Refactored from TreeCanvas.
586    * 
587    * @param sequence
588    *          of the node selected in the tree viewer.
589    */
590   @Override
591   public void treeSelectionChanged(final SequenceI sequence)
592   {
593     if (!parentAvport.isClosed()) // alignment view could be closed
594     {
595       SequenceGroup selected = parentAvport.getSelectionGroup();
596
597       if (selected == null)
598       {
599         selected = new SequenceGroup();
600         parentAvport.setSelectionGroup(selected);
601       }
602
603       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
604         selected.addOrRemove(sequence, true);
605     }
606
607   }
608
609   @Override
610   public void sortByTree_actionPerformed()
611   {
612     //
613     // if (treeCanvas.applyToAllViews)
614     // {
615     // final ArrayList<CommandI> commands = new ArrayList<>();
616     // for (AlignmentPanel ap : PaintRefresher
617     // .getAssociatedPanels(parentAvport.getSequenceSetId()))
618     // {
619     // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
620     // }
621     // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
622     // {
623     //
624     // @Override
625     // public void undoCommand(AlignmentI[] views)
626     // {
627     // for (CommandI tsort : commands)
628     // {
629     // tsort.undoCommand(views);
630     // }
631     // }
632     //
633     // @Override
634     // public int getSize()
635     // {
636     // return commands.size();
637     // }
638     //
639     // @Override
640     // public String getDescription()
641     // {
642     // return "Tree Sort (many views)";
643     // }
644     //
645     // @Override
646     // public void doCommand(AlignmentI[] views)
647     // {
648     //
649     // for (CommandI tsort : commands)
650     // {
651     // tsort.doCommand(views);
652     // }
653     // }
654     // });
655     // for (AlignmentPanel ap : PaintRefresher
656     // .getAssociatedPanels(av.getSequenceSetId()))
657     // {
658     // // ensure all the alignFrames refresh their GI after adding an undo item
659     // ap.alignFrame.updateEditMenuBar();
660     // }
661     // }
662     // else
663     // {
664     // treeCanvas.ap.alignFrame
665     // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
666     // }
667
668   }
669
670   public CommandI sortAlignmentIn(AlignmentPanel ap)
671   {
672     // TODO: move to alignment view controller
673
674     AlignmentViewport viewport = ap.av;
675     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
676     try
677     {
678     AlignmentSorter.sortByTree(viewport.getAlignment(),
679             nodesBoundToSequences,
680               treeView.getPhylogeny());
681       CommandI undo;
682       undo = new OrderCommand("Tree Sort", oldOrder,
683               viewport.getAlignment());
684
685       ap.paintAlignment(true, false);
686       return undo;
687
688     } catch (Exception e)
689     {
690       System.err.println(e.getMessage());
691     }
692     return null;
693
694   }
695   
696
697
698   /**
699    * TO BE MOVED
700    * 
701    * @param set
702    * @param objectToCheck
703    */
704   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
705   {
706     if (set.contains(objectToCheck))
707     {
708       set.remove(objectToCheck);
709     }
710     else
711     {
712       set.add(objectToCheck);
713     }
714
715   }
716
717   public AlignmentViewport getParentAvport()
718   {
719     return parentAvport;
720   }
721
722   public void setParentAvport(final AlignmentViewport parentAvport)
723   {
724     this.parentAvport = parentAvport;
725   }
726
727   public AlignmentPanel[] getAssociatedPanels()
728   {
729     return associatedPanels;
730   }
731
732   public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
733   {
734     this.associatedPanels = associatedPanels;
735   }
736
737 }
738
739
740