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