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