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