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