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