JAL-2844 entirely X coord based partitioning
[jalview.git] / src / jalview / ext / archaeopteryx / JalviewBinding.java
index 53078cf..1649b62 100644 (file)
@@ -1,21 +1,35 @@
 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;
@@ -23,8 +37,11 @@ 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
@@ -41,7 +58,7 @@ public final class JalviewBinding
 
   private AlignmentViewport parentAvport;
 
-  private JTabbedPane treeTabs;
+  private final JTabbedPane treeTabs;
 
   private final StructureSelectionManager ssm;
 
@@ -49,6 +66,10 @@ public final class JalviewBinding
 
   private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
 
+  private float rootX;
+
+  private float furthestNodeX;
+
   /**
    * 
    * @param archaeopteryx
@@ -69,6 +90,16 @@ public final class JalviewBinding
           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;
@@ -77,8 +108,6 @@ public final class JalviewBinding
     treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
     treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
     ssm = parentAvport.getStructureSelectionManager();
-
-   // archaeopteryx.getMainPanel().getControlPanel().setColorBranches(true);
     
     ssm.addSelectionListener(this);
     treeView.addMouseListener(this);
@@ -104,6 +133,7 @@ public final class JalviewBinding
            */
           public void run()
           {
+            treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
             parentAvport.sendSelection();
             // PaintRefresher.Refresh(treeView,
             // parentAvport.getSequenceSetId());
@@ -112,7 +142,7 @@ public final class JalviewBinding
         });
 
       }
-      
+
     });
 
   }
@@ -125,27 +155,45 @@ public final class JalviewBinding
   @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)
   {
-    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();
-    }
   }
   @Override
   public void mouseReleased(MouseEvent e)
@@ -180,25 +228,245 @@ public final class JalviewBinding
         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();
+
+    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 void partitionTree()
+  public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
+          PhylogenyNode node)
   {
 
+    List<PhylogenyNode> 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<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
+      {
+        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())
       {
         showMatchingChildSequences(node);
@@ -209,6 +477,7 @@ public final class JalviewBinding
         showMatchingSequence(node);
       }
 
+
     }
 
 
@@ -221,9 +490,11 @@ public final class JalviewBinding
     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()); // redundant?
+
     }
   }
 
@@ -233,6 +504,7 @@ public final class JalviewBinding
     List<PhylogenyNode> childNodes = PhylogenyMethods
             .getAllDescendants(parentNode);
 
+
     for (PhylogenyNode childNode : childNodes)
     {
       // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
@@ -240,12 +512,17 @@ public final class JalviewBinding
       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()); // redundant?
+
+
   }
 
   /**
@@ -257,16 +534,112 @@ public final class JalviewBinding
   @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);
+    }
 
   }