JAL-1953 more progress on interfaces, jalview binding still problematic
[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     treeView.registerWithPaintRefresher(
120             parentAvport.getSequenceSetId());
121     associatedPanels = PaintRefresher
122             .getAssociatedPanels(parentAvport.getSequenceSetId());
123
124     aptxFrame.addFrameListener(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         treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
206                 false, false);
207         treeView.repaint();
208
209
210
211       }
212     });
213
214
215   }
216
217   @Override
218   public void mousePressed(final MouseEvent e)
219   {
220
221   }
222   @Override
223   public void mouseReleased(MouseEvent e)
224   {
225   }
226
227   @Override
228   public void mouseEntered(MouseEvent e)
229   {
230   }
231
232   @Override
233   public void mouseExited(MouseEvent e)
234   {
235   }
236
237
238   @Override
239   public void selection(final SequenceGroup seqsel,
240           final ColumnSelection colsel, final HiddenColumns hidden,
241           final SelectionSource source)
242   {
243     if (source == parentAvport) // check if source is alignment from where the
244     // tree originates
245     {
246       treeView.setMatchingNodes(
247               new HashSet<Long>(seqsel.getSequences().size()));
248
249
250       for (SequenceI selectedSequence : seqsel.getSequences())
251       {
252         ExternalTreeNodeI matchingNode = sequencesBoundToNodes
253                 .get(selectedSequence);
254         if (matchingNode != null)
255         {
256           treeView.getMatchingNodes().add(matchingNode.getId());
257
258
259           // if (!matchingNode.getBranchData().isHasBranchColor())
260           // {
261           // // Color foundNodesColour = treeView.getTreeColorSet()
262           // // .getFoundColor0();
263           // // matchingNode.getBranchData()
264           // // .setBranchColor(new BranchColor(foundNodesColour));
265           //
266           // }
267
268         }
269
270       }
271
272       treeView.repaint();
273     }
274
275
276   }
277
278   /**
279    * Partially refactored from TreeCanvas
280    */
281   public void partitionTree(final int x)
282   {
283     ExternalTreeI tree = treeView.getTree();
284
285     if (!tree.isEmpty())
286     {
287       // should be calculated on each partition as the tree can theoretically
288       // change in the meantime
289       ExternalTreeNodeI furthestNode = tree.getFurthestNode();
290       furthestNodeX = furthestNode.getXcoord();
291       rootX = tree.getRoot().getXcoord();
292
293       // don't bother if 0 distance tree or clicked x lies outside of tree
294       if (furthestNodeX != rootX && !(x > furthestNodeX))
295       {
296         float threshold = (x - rootX) / (furthestNodeX - rootX);
297         List<ExternalTreeNodeI> foundNodes = getNodesAboveThreshold(
298                 threshold,
299                 tree.getRoot());
300
301       }
302     }
303
304
305   }
306
307   public List<ExternalTreeNodeI> getNodesAboveThreshold(double threshold,
308           ExternalTreeNodeI node)
309   {
310
311     List<ExternalTreeNodeI> nodesAboveThreshold = new ArrayList<>();
312
313     parentAvport.setSelectionGroup(null);
314     parentAvport.getAlignment().deleteAllGroups();
315     parentAvport.clearSequenceColours();
316     if (parentAvport.getCodingComplement() != null)
317     {
318       parentAvport.getCodingComplement().setSelectionGroup(null);
319       parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
320       parentAvport.getCodingComplement().clearSequenceColours();
321     }
322
323
324     colourNodesAboveThreshold(nodesAboveThreshold, threshold,
325             node);
326     return nodesAboveThreshold;
327
328   }
329
330   /**
331    * Partially refactored from TreeCanvas colourGroups (can be made nicer).
332    * 
333    * @param nodeList
334    * @param threshold
335    * @param treeLength
336    * @param node
337    * @return
338    */
339   private List<ExternalTreeNodeI> colourNodesAboveThreshold(
340           List<ExternalTreeNodeI> nodeList, double threshold,
341           ExternalTreeNodeI node)
342   {
343
344     for (ExternalTreeNodeI childNode : node.getDirectChildren())
345     {
346       childNode.setBranchColor(Color.black);
347       float nodeCutoff = (childNode.getXcoord() - rootX)
348               / (furthestNodeX - rootX);
349
350       if (nodeCutoff > threshold)
351       {
352         nodeList.add(childNode);
353
354         Color randomColour = new Color((int) (Math.random() * 255),
355                 (int) (Math.random() * 255), (int) (Math.random() * 255));
356         childNode.setBranchColor(randomColour);
357
358         List<SequenceI> groupSeqs = new ArrayList<>();
359         SequenceI seq = nodesBoundToSequences.get(childNode);
360         if (seq != null)
361         {
362           groupSeqs.add(seq);
363           parentAvport.setSequenceColour(seq, randomColour);
364         }
365
366         List<ExternalTreeNodeI> descendantNodes = childNode
367                 .getAllDescendants();
368         // .forEach instead?
369         for (ExternalTreeNodeI descNode : descendantNodes)
370         {
371           seq = nodesBoundToSequences.get(descNode);
372           if (seq != null)
373           {
374             groupSeqs.add(seq);
375             parentAvport.setSequenceColour(seq, randomColour);
376           }
377
378           descNode.setBranchColor(randomColour);
379         }
380
381         if (groupSeqs != null)
382         {
383           nrTreeGroups++;
384           groupThresholdSequences(groupSeqs, randomColour);
385         }}
386
387       else
388       {
389         colourNodesAboveThreshold(nodeList, threshold, childNode);
390       }
391     }
392
393
394         for (AlignmentPanel associatedPanel : associatedPanels) {
395
396         associatedPanel.updateAnnotation();
397
398         final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
399                 .getCodingComplement();
400         if (codingComplement != null)
401         {
402           // GROSS
403           ((AlignViewport) codingComplement).getAlignPanel()
404                   .updateAnnotation();
405         }
406       }
407
408
409     return nodeList;
410   }
411
412   public void groupThresholdSequences(List<SequenceI> groupedSeqs,
413           Color groupColour)
414   {
415     SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
416             true, true, false, 0,
417             parentAvport.getAlignment().getWidth() - 1);
418
419     ColourSchemeI cs = null;
420     if (parentAvport.getGlobalColourScheme() != null)
421     {
422       if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
423       {
424         cs = new UserColourScheme(
425                 ((UserColourScheme) parentAvport.getGlobalColourScheme())
426                         .getColours());
427       }
428       else
429       {
430         cs = ColourSchemeProperty.getColourScheme(treeGroup,
431                 ColourSchemeProperty.getColourName(
432                         parentAvport.getGlobalColourScheme()));
433       }
434
435     }
436     treeGroup.setColourScheme(cs);
437     treeGroup.getGroupColourScheme().setThreshold(
438             parentAvport.getResidueShading().getThreshold(),
439             parentAvport.isIgnoreGapsConsensus());
440
441     treeGroup.setName("Tree Group " + nrTreeGroups);
442     treeGroup.setIdColour(groupColour);
443
444     for (AlignmentPanel associatedPanel : associatedPanels)
445     {
446       AlignViewportI altViewport = associatedPanel
447               .getAlignViewport();
448
449       if (altViewport.getGlobalColourScheme() != null
450               && altViewport.getResidueShading()
451                       .conservationApplied())
452       {
453         Conservation conserv = new Conservation(treeGroup.getName(),
454                 treeGroup.getSequences(null), treeGroup.getStartRes(),
455                 treeGroup.getEndRes());
456         conserv.calculate();
457         conserv.verdict(false, altViewport.getConsPercGaps());
458         treeGroup.getGroupColourScheme().setConservation(conserv);
459       }
460
461       altViewport.getAlignment().addGroup(treeGroup);
462       // TODO can we push all of the below into AlignViewportI?
463       final AlignViewportI codingComplement = altViewport
464               .getCodingComplement();
465       if (codingComplement != null)
466       {
467         SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
468                 parentAvport, codingComplement);
469         if (mappedGroup.getSequences().size() > 0)
470         {
471           codingComplement.getAlignment().addGroup(mappedGroup);
472           for (SequenceI seq : mappedGroup.getSequences())
473           {
474             codingComplement.setSequenceColour(seq, groupColour.brighter());
475           }
476         }
477       }
478
479     }
480
481   }
482
483   /**
484    * may or may not need an extra repaint on the alignment view (check what kira
485    * does)
486    */
487   @Override
488   public void showNodeSelectionOnAlign(final ExternalTreeNodeI node)
489   {
490
491       if (node.isInternal())
492       {
493         showMatchingChildSequences(node);
494       }
495
496       else
497       {
498         showMatchingSequence(node);
499       }
500
501
502     }
503
504
505
506
507
508   @Override
509   public void showMatchingSequence(final ExternalTreeNodeI nodeToMatch)
510   {
511     SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
512     if (matchingSequence != null)
513     {
514       long nodeId = nodeToMatch.getId();
515       addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
516       treeSelectionChanged(matchingSequence);
517       parentAvport.sendSelection();
518
519     }
520   }
521
522   @Override
523   public void showMatchingChildSequences(final ExternalTreeNodeI parentNode)
524   {
525     // redundancy here, Forester already iterates through tree to get all
526     // descendants
527     List<ExternalTreeNodeI> childNodes = parentNode.getAllDescendants();
528
529
530     for (ExternalTreeNodeI childNode : childNodes)
531     {
532       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
533
534       SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
535       if (matchingSequence != null)
536       {
537         long nodeId = childNode.getId();
538         addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
539
540         treeSelectionChanged(matchingSequence);
541
542       }
543
544     }
545     parentAvport.sendSelection();
546
547
548   }
549
550   /**
551    * Refactored from TreeCanvas.
552    * 
553    * @param sequence
554    *          of the node selected in the tree viewer.
555    */
556   @Override
557   public void treeSelectionChanged(final SequenceI sequence)
558   {
559     if (!parentAvport.isClosed()) // alignment view could be closed
560     {
561       SequenceGroup selected = parentAvport.getSelectionGroup();
562
563       if (selected == null)
564       {
565         selected = new SequenceGroup();
566         parentAvport.setSelectionGroup(selected);
567       }
568
569       selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
570         selected.addOrRemove(sequence, true);
571     }
572
573   }
574
575   @Override
576   public void sortByTree_actionPerformed()// modify for Aptx
577   {
578
579     // if (treeCanvas.applyToAllViews)
580     // {
581     // final ArrayList<CommandI> commands = new ArrayList<>();
582     // for (AlignmentPanel ap : PaintRefresher
583     // .getAssociatedPanels(parentAvport.getSequenceSetId()))
584     // {
585     // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
586     // }
587     // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
588     // {
589     //
590     // @Override
591     // public void undoCommand(AlignmentI[] views)
592     // {
593     // for (CommandI tsort : commands)
594     // {
595     // tsort.undoCommand(views);
596     // }
597     // }
598     //
599     // @Override
600     // public int getSize()
601     // {
602     // return commands.size();
603     // }
604     //
605     // @Override
606     // public String getDescription()
607     // {
608     // return "Tree Sort (many views)";
609     // }
610     //
611     // @Override
612     // public void doCommand(AlignmentI[] views)
613     // {
614     //
615     // for (CommandI tsort : commands)
616     // {
617     // tsort.doCommand(views);
618     // }
619     // }
620     // });
621     // for (AlignmentPanel ap : PaintRefresher
622     // .getAssociatedPanels(av.getSequenceSetId()))
623     // {
624     // // ensure all the alignFrames refresh their GI after adding an undo item
625     // ap.alignFrame.updateEditMenuBar();
626     // }
627     // }
628     // else
629     // {
630     // treeCanvas.ap.alignFrame
631     // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
632     // }
633
634   }
635
636   @Override
637   public CommandI sortAlignmentIn(AlignmentPanel ap)
638   {
639     // TODO: move to alignment view controller
640
641     AlignmentViewport viewport = ap.av;
642     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
643     try
644     {
645     AlignmentSorter.sortByTree(viewport.getAlignment(),
646             nodesBoundToSequences,
647               treeView.getTree());
648       CommandI undo;
649       undo = new OrderCommand("Tree Sort", oldOrder,
650               viewport.getAlignment());
651
652       ap.paintAlignment(true, false);
653       return undo;
654
655     } catch (Exception e)
656     {
657       System.err.println(e.getMessage());
658     }
659     return null;
660
661   }
662   
663
664
665   /**
666    * TO BE MOVED
667    * 
668    * @param set
669    * @param objectToCheck
670    */
671   public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
672   {
673     if (set.contains(objectToCheck))
674     {
675       set.remove(objectToCheck);
676     }
677     else
678     {
679       set.add(objectToCheck);
680     }
681
682   }
683
684   public AlignmentViewport getParentAvport()
685   {
686     return parentAvport;
687   }
688
689   public void setParentAvport(final AlignmentViewport parentAvport)
690   {
691     this.parentAvport = parentAvport;
692   }
693
694   public AlignmentPanel[] getAssociatedPanels()
695   {
696     return associatedPanels;
697   }
698
699   public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
700   {
701     this.associatedPanels = associatedPanels;
702   }
703
704 }
705
706
707