b38ac1c9303bf43fb2c70bbce25a860d6f422239
[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.addToMatchingNodes(matchingNode);
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   @Override
327   public void partitionTree(final int x)
328   {
329     TreeI tree = treeView.getTree();
330
331     if (!tree.isEmpty())
332     {
333       // should be calculated on each partition as the tree can theoretically
334       // change in the meantime
335       TreeNodeI furthestNode = tree.getFurthestNode();
336       furthestNodeX = furthestNode.getXcoord();
337       rootX = tree.getRoot().getXcoord();
338
339       // don't bother if 0 distance tree or clicked x lies outside of tree
340       // if (furthestNodeX != rootX && !(x > furthestNodeX))
341
342         float threshold = (x - rootX) / (furthestNodeX - rootX);
343         List<TreeNodeI> foundNodes = getNodesAboveThreshold(
344                 threshold,
345                 tree.getRoot());
346
347
348     }
349
350
351   }
352
353   public List<TreeNodeI> getNodesAboveThreshold(float threshold,
354           TreeNodeI node)
355   {
356
357     List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
358
359     parentAvport.setSelectionGroup(null);
360     parentAvport.getAlignment().deleteAllGroups();
361     parentAvport.clearSequenceColours();
362     if (parentAvport.getCodingComplement() != null)
363     {
364       parentAvport.getCodingComplement().setSelectionGroup(null);
365       parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
366       parentAvport.getCodingComplement().clearSequenceColours();
367     }
368
369
370     colourNodesAboveThreshold(nodesAboveThreshold, threshold,
371             node);
372     return nodesAboveThreshold;
373
374   }
375
376   /**
377    * Partially refactored from TreeCanvas colourGroups (can be made nicer).
378    * 
379    * @param nodeList
380    * @param threshold
381    * @param treeLength
382    * @param node
383    * @return
384    */
385   private List<TreeNodeI> colourNodesAboveThreshold(
386           List<TreeNodeI> nodeList, float threshold,
387           TreeNodeI node)
388   {
389
390     for (TreeNodeI childNode : node.getDirectChildren())
391     {
392       childNode.setBranchColor(Color.black);
393       float nodeCutoff = (childNode.getXcoord() - rootX)
394               / (furthestNodeX - rootX);
395
396       if (nodeCutoff > threshold)
397       {
398         nodeList.add(childNode);
399
400         Color randomColour = new Color((int) (Math.random() * 255),
401                 (int) (Math.random() * 255), (int) (Math.random() * 255));
402         childNode.setBranchColor(randomColour);
403
404         List<SequenceI> groupSeqs = new ArrayList<>();
405         SequenceI seq = nodesBoundToSequences.get(childNode);
406         if (seq != null)
407         {
408           groupSeqs.add(seq);
409           parentAvport.setSequenceColour(seq, randomColour);
410         }
411
412         List<TreeNodeI> descendantNodes = childNode
413                 .getAllDescendants();
414         // .forEach instead?
415         for (TreeNodeI descNode : descendantNodes)
416         {
417           seq = nodesBoundToSequences.get(descNode);
418           if (seq != null)
419           {
420             groupSeqs.add(seq);
421             parentAvport.setSequenceColour(seq, randomColour);
422           }
423
424           descNode.setBranchColor(randomColour);
425         }
426
427         if (groupSeqs != null)
428         {
429           nrTreeGroups++;
430           groupThresholdSequences(groupSeqs, randomColour);
431         }}
432
433       else
434       {
435         colourNodesAboveThreshold(nodeList, threshold, childNode);
436       }
437     }
438
439     for (AlignmentPanel associatedPanel : getAssociatedPanels())
440     {
441
442         associatedPanel.updateAnnotation();
443
444         final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
445                 .getCodingComplement();
446         if (codingComplement != null)
447         {
448           // GROSS
449           ((AlignViewport) codingComplement).getAlignPanel()
450                   .updateAnnotation();
451         }
452       }
453
454
455     return nodeList;
456   }
457
458   public void groupThresholdSequences(List<SequenceI> groupedSeqs,
459           Color groupColour)
460   {
461     SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
462             true, true, false, 0,
463             parentAvport.getAlignment().getWidth() - 1);
464
465     ColourSchemeI cs = null;
466     if (parentAvport.getGlobalColourScheme() != null)
467     {
468       if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
469       {
470         cs = new UserColourScheme(
471                 ((UserColourScheme) parentAvport.getGlobalColourScheme())
472                         .getColours());
473       }
474       else
475       {
476         cs = ColourSchemeProperty.getColourScheme(treeGroup,
477                 ColourSchemeProperty.getColourName(
478                         parentAvport.getGlobalColourScheme()));
479       }
480
481     }
482     treeGroup.setColourScheme(cs);
483     treeGroup.getGroupColourScheme().setThreshold(
484             parentAvport.getResidueShading().getThreshold(),
485             parentAvport.isIgnoreGapsConsensus());
486
487     treeGroup.setName("Tree Group " + nrTreeGroups);
488     treeGroup.setIdColour(groupColour);
489
490     for (AlignmentPanel associatedPanel : getAssociatedPanels())
491     {
492       AlignViewportI altViewport = associatedPanel
493               .getAlignViewport();
494
495       if (altViewport.getGlobalColourScheme() != null
496               && altViewport.getResidueShading()
497                       .conservationApplied())
498       {
499         Conservation conserv = new Conservation(treeGroup.getName(),
500                 treeGroup.getSequences(null), treeGroup.getStartRes(),
501                 treeGroup.getEndRes());
502         conserv.calculate();
503         conserv.verdict(false, altViewport.getConsPercGaps());
504         treeGroup.getGroupColourScheme().setConservation(conserv);
505       }
506
507       altViewport.getAlignment().addGroup(treeGroup);
508       // TODO can we push all of the below into AlignViewportI?
509       final AlignViewportI codingComplement = altViewport
510               .getCodingComplement();
511       if (codingComplement != null)
512       {
513         SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
514                 parentAvport, codingComplement);
515         if (mappedGroup.getSequences().size() > 0)
516         {
517           codingComplement.getAlignment().addGroup(mappedGroup);
518           for (SequenceI seq : mappedGroup.getSequences())
519           {
520             codingComplement.setSequenceColour(seq, groupColour.brighter());
521           }
522         }
523       }
524
525     }
526
527   }
528
529
530   @Override
531   public void showNodeSelectionOnAlign(final TreeNodeI node)
532   {
533
534       if (node.isInternal())
535       {
536         showMatchingChildSequences(node);
537       }
538
539       else
540       {
541         showMatchingSequence(node);
542       }
543
544
545     }
546
547
548
549
550
551   @Override
552   public void showMatchingSequence(final TreeNodeI nodeToMatch)
553   {
554     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
555     if (matchingSequence != null)
556     {
557       long nodeId = nodeToMatch.getId();
558       addOrRemoveInSet(treeView.getMatchingNodesIds(), nodeId);
559       treeSelectionChanged(matchingSequence);
560       parentAvport.sendSelection();
561
562     }
563   }
564
565   @Override
566   public void showMatchingChildSequences(final TreeNodeI parentNode)
567   {
568     // redundancy here, Forester already iterates through tree to get all
569     // descendants
570     List<TreeNodeI> childNodes = parentNode.getAllDescendants();
571
572
573     for (TreeNodeI childNode : childNodes)
574     {
575       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
576       if (matchingSequence != null)
577       {
578         long nodeId = childNode.getId();
579         addOrRemoveInSet(treeView.getMatchingNodesIds(), nodeId);
580
581         treeSelectionChanged(matchingSequence);
582
583       }
584
585     }
586     parentAvport.sendSelection();
587
588
589   }
590
591
592   @Override
593   public void treeSelectionChanged(final SequenceI sequence)
594   {
595     if (!parentAvport.isClosed()) // alignment view could be closed
596     {
597       SequenceGroup selected = parentAvport.getSelectionGroup();
598
599       if (selected == null)
600       {
601         selected = new SequenceGroup();
602         parentAvport.setSelectionGroup(selected);
603       }
604
605       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
606         selected.addOrRemove(sequence, true);
607     }
608
609   }
610
611   @Override
612   public void sortByTree_actionPerformed()
613   {
614
615     // if (applyToAllViews)
616
617       final ArrayList<CommandI> commands = new ArrayList<>();
618       for (AlignmentPanel ap : PaintRefresher
619               .getAssociatedPanels(parentAvport.getSequenceSetId()))
620       {
621         commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
622         ap.alignFrame.addHistoryItem(new CommandI()
623         {
624
625           @Override
626           public void undoCommand(AlignmentI[] views)
627           {
628             for (CommandI tsort : commands)
629             {
630               tsort.undoCommand(views);
631             }
632           }
633
634           @Override
635           public int getSize()
636           {
637             return commands.size();
638           }
639
640           @Override
641           public String getDescription()
642           {
643             return "Tree Sort (many views)";
644           }
645
646           @Override
647           public void doCommand(AlignmentI[] views)
648           {
649
650             for (CommandI tsort : commands)
651             {
652               tsort.doCommand(views);
653             }
654           }
655         });
656
657         ap.alignFrame.updateEditMenuBar();
658       }
659     }
660   // else
661   // {
662   // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
663   // }
664
665
666
667   @Override
668   public CommandI sortAlignmentIn(AlignmentPanel ap)
669   {
670     AlignmentViewport viewport = ap.av;
671     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
672     try
673     {
674     AlignmentSorter.sortByTree(viewport.getAlignment(),
675             nodesBoundToSequences,
676               treeView.getTree());
677       CommandI undo;
678       undo = new OrderCommand("Tree Sort", oldOrder,
679               viewport.getAlignment());
680
681       ap.paintAlignment(true, false);
682       return undo;
683
684     } catch (Exception e)
685     {
686       System.err.println(e.getMessage());
687     }
688     return null;
689
690   }
691   
692
693
694   /**
695    * TO BE MOVED
696    * 
697    * @param set
698    * @param objectToCheck
699    */
700   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
701   {
702     if (set.contains(objectToCheck))
703     {
704       set.remove(objectToCheck);
705     }
706     else
707     {
708       set.add(objectToCheck);
709     }
710
711   }
712
713   public AlignmentViewport getParentAvport()
714   {
715     return parentAvport;
716   }
717
718   public void setParentAvport(final AlignmentViewport parentAvport)
719   {
720     this.parentAvport = parentAvport;
721   }
722
723   public AlignmentPanel[] getAssociatedPanels()
724   {
725     return PaintRefresher
726             .getAssociatedPanels(parentAvport.getSequenceSetId());
727   }
728
729   @Override
730   public Map<SequenceI, TreeNodeI> getAlignmentWithNodes()
731   {
732     return sequencesBoundToNodes;
733   }
734
735   @Override
736   public Map<TreeNodeI, SequenceI> getNodesWithAlignment()
737   {
738     return nodesBoundToSequences;
739   }
740
741
742 }
743
744
745