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