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