--- /dev/null
+package jalview.ext.archaeopteryx;
+
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
+import jalview.gui.PaintRefresher;
+import jalview.structure.SelectionSource;
+import jalview.structure.StructureSelectionManager;
+import jalview.viewmodel.AlignmentViewport;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+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.phylogeny.Phylogeny;
+import org.forester.phylogeny.PhylogenyMethods;
+import org.forester.phylogeny.PhylogenyNode;
+
+/**
+ * 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<PhylogenyNode>
+{
+ private final MainFrame aptxFrame;
+
+ private org.forester.archaeopteryx.TreePanel currentTreeView;
+
+ private final AlignmentViewport parentAvport;
+
+ private JTabbedPane treeTabs;
+
+ private final StructureSelectionManager ssm;
+
+ private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
+
+ private Map<PhylogenyNode, SequenceI> 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<SequenceI, PhylogenyNode> alignMappedToNodes,
+ final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
+ {
+ // deal with/prohibit null values here as that will cause problems
+ aptxFrame = archaeopteryx;
+ parentAvport = jalviewAlignmentViewport;
+ sequencesBoundToNodes = alignMappedToNodes;
+ nodesBoundToSequences = nodesMappedToAlign;
+
+ treeTabs = aptxFrame.getMainPanel().getTabbedPane();
+ ssm = parentAvport.getStructureSelectionManager();
+
+ // archaeopteryx.getMainPanel().getControlPanel().setColorBranches(true);
+
+ ssm.addSelectionListener(this);
+
+ int tabCount = treeTabs.getTabCount();
+
+ for (int i = 0; i < tabCount; i++)
+ {
+ // roundabout way to select each tree because getComponentAt(i) requires
+ // casting to TreePanel which doesn't work
+ treeTabs.setSelectedIndex(i);
+ currentTreeView = aptxFrame.getMainPanel().getCurrentTreePanel();
+ currentTreeView.addMouseListener(this);
+ PaintRefresher.Register(currentTreeView,
+ 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()
+ {
+ currentTreeView = aptxFrame.getMainPanel()
+ .getCurrentTreePanel();
+ parentAvport.sendSelection();
+
+ }
+ });
+
+ }
+
+ });
+
+ }
+
+ @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 = currentTreeView.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);
+
+
+ }
+
+ }
+ });
+
+
+ }
+
+ @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
+ {
+ currentTreeView.setFoundNodes0(
+ new HashSet<Long>(seqsel.getSequences().size()));
+
+ for (SequenceI selectedSequence : seqsel.getSequences())
+ {
+ PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
+ if (matchingNode != null)
+ {
+ currentTreeView.getFoundNodes0().add(matchingNode.getId());
+ }
+
+ }
+ aptxFrame.repaint();
+
+ }
+
+
+ }
+
+ /**
+ * Partially refactored from TreeCanvas
+ */
+ public void partitionTree(final MouseEvent e)
+ {
+ int x = e.getX();
+ int lineLength = currentTreeView.getHeight();
+
+ Phylogeny tree = currentTreeView.getPhylogeny();
+ double treeHeight = tree.calculateHeight(true);
+
+
+
+ if (treeHeight != 0)
+ {
+ int viewWidth = currentTreeView.getWidth();
+
+ // treeView.validate();
+
+ // System.out.println("selection");
+ // System.out.println(x);
+ // System.out.println("-------------");
+ // System.out.println("width");
+ // System.out.println(viewWidth);
+
+ }
+
+
+ }
+
+
+
+ @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(currentTreeView.getFoundNodes0(), nodeId);
+ treeSelectionChanged(matchingSequence);
+ parentAvport.sendSelection();
+
+ }
+ }
+
+ @Override
+ public void showMatchingChildSequences(final PhylogenyNode parentNode)
+ {
+ List<PhylogenyNode> 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(currentTreeView.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<CommandI> commands = new ArrayList<>();
+ // for (AlignmentPanel ap : PaintRefresher
+ // .getAssociatedPanels(parentAvport.getSequenceSetId()))
+ // {
+ // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
+ // }
+ // av.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));
+ // }
+
+
+
+ /**
+ * 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);
+ }
+
+ }
+
+ public AlignmentViewport getParentAvport()
+ {
+ return parentAvport;
+ }
+
+ public org.forester.archaeopteryx.TreePanel getTreeView()
+ {
+ return currentTreeView;
+ }
+
+}
+
+
+
+