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