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