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