package jalview.ext.archaeopteryx; import jalview.analysis.Conservation; import jalview.api.AlignViewportI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.ext.treeviewer.ExternalTreeViewerBindingI; import jalview.gui.AlignViewport; import jalview.gui.Desktop; import jalview.gui.JvOptionPane; import jalview.gui.PaintRefresher; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemeProperty; import jalview.schemes.UserColourScheme; import jalview.structure.SelectionSource; import jalview.structure.StructureSelectionManager; import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.forester.archaeopteryx.MainFrame; import org.forester.archaeopteryx.TreePanelUtil; import org.forester.phylogeny.Phylogeny; import org.forester.phylogeny.PhylogenyMethods; import org.forester.phylogeny.PhylogenyNode; import org.forester.phylogeny.data.BranchColor; /** * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that * it originates from, meaning that selecting sequences in the tree viewer also * selects them in the alignment view and vice versa. * * @author kjvanderheide * */ public final class JalviewBinding implements ExternalTreeViewerBindingI { private org.forester.archaeopteryx.TreePanel treeView; private AlignmentViewport parentAvport; private final JTabbedPane treeTabs; private final StructureSelectionManager ssm; private Map sequencesBoundToNodes; private Map nodesBoundToSequences; /** * * @param archaeopteryx * * @param jalviewAlignmentViewport * alignment viewport from which the tree was calculated. * * @param alignMappedToNodes * map with sequences used to calculate the tree and matching tree * nodes as key, value pair respectively. * * @param nodesMappedToAlign * map with tree nodes and matching sequences used to calculate the * tree as key, value pair respectively. */ public JalviewBinding(final MainFrame archaeopteryx, final AlignmentViewport jalviewAlignmentViewport, final Map alignMappedToNodes, final Map nodesMappedToAlign) { if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1) { JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager.getString("label.tabs_detected_archaeopteryx"), MessageManager.getString("label.problem_reading_tree_file"), JvOptionPane.WARNING_MESSAGE); ; } // deal with/prohibit null values here as that will cause problems parentAvport = jalviewAlignmentViewport; sequencesBoundToNodes = alignMappedToNodes; nodesBoundToSequences = nodesMappedToAlign; treeView = archaeopteryx.getMainPanel().getCurrentTreePanel(); treeTabs = archaeopteryx.getMainPanel().getTabbedPane(); ssm = parentAvport.getStructureSelectionManager(); ssm.addSelectionListener(this); treeView.addMouseListener(this); PaintRefresher.Register(treeView, parentAvport.getSequenceSetId()); treeTabs.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { SwingUtilities.invokeLater(new Runnable() { @Override /** * Resend the selection to the tree view when tabs get switched, this * has to be buried in invokeLater as Forester first resets the tree * view on switching tabs, without invokeLater this would get called * before Forester resets which would nullify the selection. */ public void run() { treeView = archaeopteryx.getMainPanel().getCurrentTreePanel(); parentAvport.sendSelection(); // PaintRefresher.Refresh(treeView, // parentAvport.getSequenceSetId()); } }); } }); } @Override public void actionPerformed(ActionEvent e) { } @Override public void mouseClicked(MouseEvent e) { SwingUtilities.invokeLater(new Runnable() { @Override /** * invokeLater so that this always runs after Forester's mouseClicked */ public void run() { final PhylogenyNode node = treeView.findNode(e.getX(), e.getY()); if (node != null) { if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous // selection if shift // IS NOT pressed { parentAvport.setSelectionGroup(null); } showNodeSelectionOnAlign(node); } else { partitionTree(e.getX()); } PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId()); treeView.repaint(); // MOVE Graphics g = treeView.getGraphics(); int panelHeight = treeView.getHeight(); g.drawLine(e.getX(), 0, e.getX(), panelHeight); } }); } @Override public void mousePressed(final MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void selection(final SequenceGroup seqsel, final ColumnSelection colsel, final HiddenColumns hidden, final SelectionSource source) { if (source == parentAvport) // check if source is alignment from where the // tree originates { treeView.setFoundNodes0( new HashSet(seqsel.getSequences().size())); for (SequenceI selectedSequence : seqsel.getSequences()) { PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence); if (matchingNode != null) { treeView.getFoundNodes0().add(matchingNode.getId()); } } } } /** * Partially refactored from TreeCanvas */ public void partitionTree(final int x) { Phylogeny tree = treeView.getPhylogeny(); if (!tree.isEmpty()) { double longestBranch = tree.calculateHeight(true); if (longestBranch != 0) { // double relativeTreeWidth = longestBranch / viewWidth; float rootX = tree.getRoot().getXcoord(); double threshold = ((double) x - rootX) / longestBranch; List foundNodes = getNodesAboveThreshold(threshold, longestBranch, tree.getRoot()); } } } public List getNodesAboveThreshold(double threshold, double treeLength, PhylogenyNode node) { List nodesAboveThreshold = new ArrayList<>(); parentAvport.setSelectionGroup(null); parentAvport.getAlignment().deleteAllGroups(); parentAvport.clearSequenceColours(); if (parentAvport.getCodingComplement() != null) { parentAvport.getCodingComplement().setSelectionGroup(null); parentAvport.getCodingComplement().getAlignment().deleteAllGroups(); parentAvport.getCodingComplement().clearSequenceColours(); } colourNodesAboveThreshold(nodesAboveThreshold, threshold, treeLength, node); return nodesAboveThreshold; } /** * Partially refactored from TreeCanvas colourGroups (can be made nicer). * * @param nodeList * @param threshold * @param treeLength * @param node * @return */ private List colourNodesAboveThreshold( List nodeList, double threshold, double treeLength, PhylogenyNode node) { // could also use PhylogenyMethods.getAllDescendants for (PhylogenyNode childNode : node.getDescendants()) { childNode.getBranchData() .setBranchColor(new BranchColor(Color.black)); double nodeCutoff = childNode.calculateDistanceToRoot() / treeLength; if (nodeCutoff > threshold) { nodeList.add(childNode); Color randomColor = new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)); TreePanelUtil.colorizeSubtree(childNode, new BranchColor(randomColor)); List descendantNodes = childNode .getAllExternalDescendants(); List descendantSeqs = new ArrayList<>(); for (PhylogenyNode descNode : descendantNodes) { descendantSeqs.add(nodesBoundToSequences.get(descNode)); } SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null, true, true, false, 0, parentAvport.getAlignment().getWidth() - 1); ColourSchemeI cs = null; if (parentAvport.getGlobalColourScheme() != null) { if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme) { cs = new UserColourScheme( ((UserColourScheme) parentAvport.getGlobalColourScheme()) .getColours()); } else { cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty .getColourName(parentAvport.getGlobalColourScheme())); } } sg.setColourScheme(cs); sg.getGroupColourScheme().setThreshold( parentAvport.getResidueShading().getThreshold(), parentAvport.isIgnoreGapsConsensus()); // sg.recalcConservation(); sg.setName("Tree Group:" + sg.hashCode()); sg.setIdColour(randomColor); if (parentAvport.getGlobalColourScheme() != null && parentAvport.getResidueShading().conservationApplied()) { Conservation c = new Conservation("Group", sg.getSequences(null), sg.getStartRes(), sg.getEndRes()); c.calculate(); c.verdict(false, parentAvport.getConsPercGaps()); sg.cs.setConservation(c); } parentAvport.getAlignment().addGroup(new SequenceGroup(sg)); // TODO can we push all of the below into AlignViewportI? final AlignViewportI codingComplement = parentAvport .getCodingComplement(); if (codingComplement != null) { SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, parentAvport, codingComplement); if (mappedGroup.getSequences().size() > 0) { codingComplement.getAlignment().addGroup(mappedGroup); for (SequenceI seq : mappedGroup.getSequences()) { codingComplement.setSequenceColour(seq, randomColor.brighter()); } } } } else { colourNodesAboveThreshold(nodeList, threshold, treeLength, childNode); } } // GROSS ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation(); final AlignViewportI codingComplement = parentAvport .getCodingComplement(); if (codingComplement != null) { ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation(); } return nodeList; } // public List groupNodes(float threshold, PhylogenyNode root, // double treeHeight) // { // List groups = new ArrayList<>(); // _groupNodes(groups, root, threshold, treeHeight); // System.out.println(groups); // return groups; // } // // protected void _groupNodes(List groups, PhylogenyNode nd, // float threshold, double treeHeight) // { // if (nd == null) // { // return; // } // // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold) // { // groups.add(nd); // } // else // { // for (PhylogenyNode childNode : nd.getDescendants()) // { // _groupNodes(groups, childNode, threshold, treeHeight); // } // } // } // /** * may or may not need an extra repaint on the alignment view (check what kira * does) */ @Override public void showNodeSelectionOnAlign(final PhylogenyNode node) { if (node.isInternal()) { showMatchingChildSequences(node); } else { showMatchingSequence(node); } } @Override public void showMatchingSequence(final PhylogenyNode nodeToMatch) { SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch); if (matchingSequence != null) { long nodeId = nodeToMatch.getId(); addOrRemoveInSet(treeView.getFoundNodes0(), nodeId); treeSelectionChanged(matchingSequence); parentAvport.sendSelection(); } } @Override public void showMatchingChildSequences(final PhylogenyNode parentNode) { List childNodes = PhylogenyMethods .getAllDescendants(parentNode); for (PhylogenyNode childNode : childNodes) { // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE)); SequenceI matchingSequence = nodesBoundToSequences.get(childNode); if (matchingSequence != null) { long nodeId = childNode.getId(); addOrRemoveInSet(treeView.getFoundNodes0(), nodeId); treeSelectionChanged(matchingSequence); } } parentAvport.sendSelection(); } /** * Refactored from TreeCanvas. * * @param sequence * of the node selected in the tree viewer. */ @Override public void treeSelectionChanged(final SequenceI sequence) { if (!parentAvport.isClosed()) // alignment view could be closed { SequenceGroup selected = parentAvport.getSelectionGroup(); if (selected == null) { selected = new SequenceGroup(); parentAvport.setSelectionGroup(selected); } selected.setEndRes(parentAvport.getAlignment().getWidth() - 1); selected.addOrRemove(sequence, true); } } public void sortByTree_actionPerformed() { // parentAvport.mirrorCommand(command, undo, ssm, source); // alignFrame // .addHistoryItem(sortAlignmentIn(treeCanvas.ap)); } /** * sort the associated alignment view by the current tree. * * @param e */ // @Override // public void sortByTree_actionPerformed()// modify for Aptx // { // // // if (treeCanvas.applyToAllViews) // // final ArrayList commands = new ArrayList<>(); // for (AlignmentPanel ap : PaintRefresher // .getAssociatedPanels(parentAvport.getSequenceSetId())) // { // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel())); // } // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI() // { // // @Override // public void undoCommand(AlignmentI[] views) // { // for (CommandI tsort : commands) // { // tsort.undoCommand(views); // } // } // // @Override // public int getSize() // { // return commands.size(); // } // // @Override // public String getDescription() // { // return "Tree Sort (many views)"; // } // // @Override // public void doCommand(AlignmentI[] views) // { // // for (CommandI tsort : commands) // { // tsort.doCommand(views); // } // } // }); // for (AlignmentPanel ap : PaintRefresher // .getAssociatedPanels(parentAvport.getSequenceSetId())) // { // // ensure all the alignFrames refresh their GI after adding an undo item // ap.alignFrame.updateEditMenuBar(); // } // } // else // { // treeCanvas.ap.alignFrame // .addHistoryItem(sortAlignmentIn(treeCanvas.ap)); // } /** * TO BE MOVED * * @param set * @param objectToCheck */ public static void addOrRemoveInSet(Set set, E objectToCheck) { if (set.contains(objectToCheck)) { set.remove(objectToCheck); } else { set.add(objectToCheck); } } public AlignmentViewport getParentAvport() { return parentAvport; } public void setParentAvport(final AlignmentViewport parentAvport) { this.parentAvport = parentAvport; } }