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