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