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
private AlignmentViewport parentAvport;
+ private final JTabbedPane treeTabs;
+
private final StructureSelectionManager ssm;
private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
+ private float rootX;
+
+ private float furthestNodeX;
+
/**
*
* @param archaeopteryx
final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
final Map<PhylogenyNode, SequenceI> 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
@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());
+
+
+
+ }
+ });
+
+
}
@Override
public void mousePressed(final MouseEvent e)
{
- showNodeSelectionOnAlign(e);
- }
+ }
@Override
public void mouseReleased(MouseEvent e)
{
if (matchingNode != null)
{
treeView.getFoundNodes0().add(matchingNode.getId());
+
+
+ if (!matchingNode.getBranchData().isHasBranchColor())
+ {
+ Color foundNodesColour = treeView.getTreeColorSet()
+ .getFoundColor0();
+ matchingNode.getBranchData()
+ .setBranchColor(new BranchColor(foundNodesColour));
+
+ }
+
}
}
- PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
+ treeView.repaint();
}
}
+ /**
+ * Partially refactored from TreeCanvas
+ */
+ public void partitionTree(final int x)
+ {
+ Phylogeny tree = treeView.getPhylogeny();
- @Override
- public void showNodeSelectionOnAlign(final MouseEvent e)
+ 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();
+
+ if (furthestNodeX != rootX && x < furthestNodeX) // don't bother if 0
+ // distance tree or
+ // clicked x lies outside
+ // of tree
+ {
+ Graphics g = treeView.getGraphics();
+ int panelHeight = treeView.getHeight();
+ g.drawLine(x, 0, x, panelHeight);
+
+ float threshold = (x - rootX) / (furthestNodeX - rootX);
+ List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
+ tree.getRoot());
+
+ }
+ }
+
+
+ }
+
+ public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
+ PhylogenyNode node)
{
- final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
- if (node != null)
+
+ List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
+
+ parentAvport.setSelectionGroup(null);
+ parentAvport.getAlignment().deleteAllGroups();
+ parentAvport.clearSequenceColours();
+ if (parentAvport.getCodingComplement() != null)
{
- if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
- // selection if shift
- // IS NOT pressed
+ 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<PhylogenyNode> colourNodesAboveThreshold(
+ List<PhylogenyNode> nodeList, double threshold,
+ PhylogenyNode node)
+ {
+ // could also use PhylogenyMethods.getAllDescendants
+ 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 randomColor = new Color((int) (Math.random() * 255),
+ (int) (Math.random() * 255), (int) (Math.random() * 255));
+ TreePanelUtil.colorizeSubtree(childNode,
+ new BranchColor(randomColor));
+ List<PhylogenyNode> descendantNodes = childNode
+ .getAllExternalDescendants();
+ List<SequenceI> descendantSeqs = new ArrayList<>(); // .forEach instead?
+ 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
{
- parentAvport.setSelectionGroup(null);
+ colourNodesAboveThreshold(nodeList, threshold,
+ childNode);
}
+ }
+
+ // GROSS
+ ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
+
+ final AlignViewportI codingComplement = parentAvport
+ .getCodingComplement();
+ if (codingComplement != null)
+ {
+ ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
+ }
+
+ return nodeList;
+ }
+
+
+
+
+ // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
+ // double treeHeight)
+ // {
+ // List<PhylogenyNode> groups = new ArrayList<>();
+ // _groupNodes(groups, root, threshold, treeHeight);
+ // System.out.println(groups);
+ // return groups;
+ // }
+ //
+ // protected void _groupNodes(List<PhylogenyNode> 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())
{
showMatchingSequence(node);
}
+
}
- }
+
SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
if (matchingSequence != null)
{
+ long nodeId = nodeToMatch.getId();
+ addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
treeSelectionChanged(matchingSequence);
parentAvport.sendSelection();
- PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
+
}
}
@Override
public void showMatchingChildSequences(final PhylogenyNode parentNode)
{
- final List<PhylogenyNode> childNodes = PhylogenyMethods
+ List<PhylogenyNode> childNodes = PhylogenyMethods
.getAllDescendants(parentNode);
- // final BranchColor branchColor = new BranchColor();
+
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();
- PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
+
+
}
/**
@Override
public void treeSelectionChanged(final SequenceI sequence)
{
- SequenceGroup selected = parentAvport.getSelectionGroup();
-
- if (selected == null)
+ if (!parentAvport.isClosed()) // alignment view could be closed
{
- selected = new SequenceGroup();
- parentAvport.setSelectionGroup(selected);
+ SequenceGroup selected = parentAvport.getSelectionGroup();
+
+ if (selected == null)
+ {
+ selected = new SequenceGroup();
+ parentAvport.setSelectionGroup(selected);
+ }
+
+ selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
+ selected.addOrRemove(sequence, true);
}
- 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<CommandI> 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 <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
+ {
+ if (set.contains(objectToCheck))
+ {
+ set.remove(objectToCheck);
+ }
+ else
+ {
+ set.add(objectToCheck);
+ }
}
{
this.parentAvport = parentAvport;
}
-
}