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