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