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