package jalview.ext.archaeopteryx; import jalview.analysis.AlignmentSorter; import jalview.analysis.Conservation; import jalview.api.AlignViewportI; import jalview.commands.CommandI; import jalview.commands.OrderCommand; 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.AlignmentPanel; 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.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 javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import org.forester.archaeopteryx.MainFrame; 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 final MainFrame aptxFrame; private org.forester.archaeopteryx.TreePanel treeView; private AlignmentViewport parentAvport; private final JTabbedPane treeTabs; private final StructureSelectionManager ssm; private AlignmentPanel[] associatedPanels; private Map sequencesBoundToNodes; private Map nodesBoundToSequences; private float rootX; private float furthestNodeX; private int nrTreeGroups = 0; private boolean applyToAllViews = false; /** * * @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 aptxFrame = archaeopteryx; 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()); associatedPanels = PaintRefresher .getAssociatedPanels(parentAvport.getSequenceSetId()); aptxFrame.addInternalFrameListener(new InternalFrameAdapter() { @Override public void internalFrameClosed(InternalFrameEvent e) { AptxInit.getAllAptxFrames().remove(aptxFrame); ssm.removeSelectionListener(JalviewBinding.this); } }); 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) { // aptxFrame.actionPerformed(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(); } }); } @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()); if (!matchingNode.getBranchData().isHasBranchColor()) { // Color foundNodesColour = treeView.getTreeColorSet() // .getFoundColor0(); // matchingNode.getBranchData() // .setBranchColor(new BranchColor(foundNodesColour)); } } } treeView.repaint(); } } /** * Partially refactored from TreeCanvas */ public void partitionTree(final int x) { Phylogeny tree = treeView.getPhylogeny(); if (!tree.isEmpty()) { // should be calculated on each partition as the tree can theoretically // change in the meantime PhylogenyNode furthestNode = PhylogenyMethods .calculateNodeWithMaxDistanceToRoot(tree); furthestNodeX = furthestNode.getXcoord(); rootX = tree.getRoot().getXcoord(); // don't bother if 0 distance tree or clicked x lies outside of tree if (furthestNodeX != rootX && !(x > furthestNodeX)) { float threshold = (x - rootX) / (furthestNodeX - rootX); List foundNodes = getNodesAboveThreshold(threshold, tree.getRoot()); } } } public List getNodesAboveThreshold(double threshold, 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, 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, PhylogenyNode node) { for (PhylogenyNode childNode : node.getDescendants()) { childNode.getBranchData() .setBranchColor(new BranchColor(Color.black)); float nodeCutoff = (childNode.getXcoord() - rootX) / (furthestNodeX - rootX); if (nodeCutoff > threshold) { nodeList.add(childNode); Color randomColour = new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)); childNode.getBranchData() .setBranchColor(new BranchColor(randomColour)); List groupSeqs = new ArrayList<>(); SequenceI seq = nodesBoundToSequences.get(childNode); if (seq != null) { groupSeqs.add(seq); parentAvport.setSequenceColour(seq, randomColour); } List descendantNodes = PhylogenyMethods .getAllDescendants(childNode); // .forEach instead? for (PhylogenyNode descNode : descendantNodes) { seq = nodesBoundToSequences.get(descNode); if (seq != null) { groupSeqs.add(seq); parentAvport.setSequenceColour(seq, randomColour); } descNode.getBranchData() .setBranchColor(new BranchColor(randomColour)); } if (groupSeqs != null) { nrTreeGroups++; groupThresholdSequences(groupSeqs, randomColour); }} else { colourNodesAboveThreshold(nodeList, threshold, childNode); } } for (AlignmentPanel associatedPanel : associatedPanels) { associatedPanel.updateAnnotation(); final AlignViewportI codingComplement = associatedPanel.getAlignViewport() .getCodingComplement(); if (codingComplement != null) { // GROSS ((AlignViewport) codingComplement).getAlignPanel() .updateAnnotation(); } } return nodeList; } public void groupThresholdSequences(List groupedSeqs, Color groupColour) { SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, 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(treeGroup, ColourSchemeProperty.getColourName( parentAvport.getGlobalColourScheme())); } } treeGroup.setColourScheme(cs); treeGroup.getGroupColourScheme().setThreshold( parentAvport.getResidueShading().getThreshold(), parentAvport.isIgnoreGapsConsensus()); treeGroup.setName("Tree Group " + nrTreeGroups); treeGroup.setIdColour(groupColour); for (AlignmentPanel associatedPanel : associatedPanels) { AlignViewportI altViewport = associatedPanel .getAlignViewport(); if (altViewport.getGlobalColourScheme() != null && altViewport.getResidueShading() .conservationApplied()) { Conservation conserv = new Conservation(treeGroup.getName(), treeGroup.getSequences(null), treeGroup.getStartRes(), treeGroup.getEndRes()); conserv.calculate(); conserv.verdict(false, altViewport.getConsPercGaps()); treeGroup.getGroupColourScheme().setConservation(conserv); } altViewport.getAlignment().addGroup(treeGroup); // TODO can we push all of the below into AlignViewportI? final AlignViewportI codingComplement = altViewport .getCodingComplement(); if (codingComplement != null) { SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup, parentAvport, codingComplement); if (mappedGroup.getSequences().size() > 0) { codingComplement.getAlignment().addGroup(mappedGroup); for (SequenceI seq : mappedGroup.getSequences()) { codingComplement.setSequenceColour(seq, groupColour.brighter()); } } } } } /** * 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) { // redundancy here, Forester already iterates through tree to get all // descendants 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); } } @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.av.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(av.getSequenceSetId())) // { // // ensure all the alignFrames refresh their GI after adding an undo item // ap.alignFrame.updateEditMenuBar(); // } // } // else // { // treeCanvas.ap.alignFrame // .addHistoryItem(sortAlignmentIn(treeCanvas.ap)); // } } public CommandI sortAlignmentIn(AlignmentPanel ap) { // TODO: move to alignment view controller AlignmentViewport viewport = ap.av; SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray(); try { AlignmentSorter.sortByTree(viewport.getAlignment(), nodesBoundToSequences, treeView.getPhylogeny()); CommandI undo; undo = new OrderCommand("Tree Sort", oldOrder, viewport.getAlignment()); ap.paintAlignment(true, false); return undo; } catch (Exception e) { System.err.println(e.getMessage()); } return null; } /** * 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; } public AlignmentPanel[] getAssociatedPanels() { return associatedPanels; } public void setAssociatedPanels(AlignmentPanel[] associatedPanels) { this.associatedPanels = associatedPanels; } }