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