Merge branch 'develop' into features/JAL-518_justify_seqs_in_region
[jalview.git] / src / jalview / ext / rbvi / chimera / ChimeraXCommands.java
diff --git a/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java
new file mode 100644 (file)
index 0000000..66ecaea
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.ext.rbvi.chimera;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommand;
+import jalview.structure.StructureCommandI;
+import jalview.structure.StructureCommandsI.AtomSpecType;
+
+/**
+ * Routines for generating ChimeraX commands for Jalview/ChimeraX binding
+ */
+public class ChimeraXCommands extends ChimeraCommands
+{
+  // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#resattr
+  private static final StructureCommand LIST_RESIDUE_ATTRIBUTES = new StructureCommand(
+          "info resattr");
+
+  // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/exit.html
+  private static final StructureCommand CLOSE_CHIMERAX = new StructureCommand(
+          "exit");
+
+  // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#notify
+  private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand(
+          "info notify stop selection jalview");
+
+  private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand(
+          "info notify stop models jalview");
+
+  // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#selection
+  private static final StructureCommand GET_SELECTION = new StructureCommand(
+          "info selection level residue");
+
+  private static final StructureCommand SHOW_BACKBONE = new StructureCommand(
+          "~display all;~ribbon;show @CA|P atoms");
+
+  // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/view.html
+  private static final StructureCommand FOCUS_VIEW = new StructureCommand(
+          "view");
+
+  private static final StructureCommandI COLOUR_BY_CHARGE = new StructureCommand(
+          "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow");
+
+  @Override
+  public List<StructureCommandI> colourByCharge()
+  {
+    return Arrays.asList(COLOUR_BY_CHARGE);
+  }
+
+  @Override
+  public String getResidueSpec(String residue)
+  {
+    return ":" + residue;
+  }
+
+  @Override
+  public StructureCommandI colourResidues(String atomSpec, Color colour)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/color.html
+    String colourCode = getColourString(colour);
+
+    return new StructureCommand("color " + atomSpec + " " + colourCode);
+  }
+
+  @Override
+  public StructureCommandI focusView()
+  {
+    return FOCUS_VIEW;
+  }
+
+  /**
+   * {@inheritDoc}
+   * 
+   * @return
+   */
+  @Override
+  public int getModelStartNo()
+  {
+    return 1;
+  }
+
+  /**
+   * Returns a viewer command to set the given residue attribute value on
+   * residues specified by the AtomSpecModel, for example
+   * 
+   * <pre>
+   * setattr #0/A:3-9,14-20,39-43 res jv_strand 'strand' create true
+   * </pre>
+   * 
+   * @param attributeName
+   * @param attributeValue
+   * @param atomSpecModel
+   * @return
+   */
+  @Override
+  protected StructureCommandI setAttribute(String attributeName,
+          String attributeValue, AtomSpecModel atomSpecModel)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    sb.append("setattr ")
+            .append(getAtomSpec(atomSpecModel, AtomSpecType.RESIDUE_ONLY));
+    sb.append(" res ").append(attributeName).append(" '")
+            .append(attributeValue).append("'");
+    sb.append(" create true");
+    return new StructureCommand(sb.toString());
+  }
+
+  @Override
+  public StructureCommandI openCommandFile(String path)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html
+    return new StructureCommand("open " + path);
+  }
+
+  @Override
+  public StructureCommandI saveSession(String filepath)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html
+    // note ChimeraX will append ".cxs" to the filepath!
+    return new StructureCommand("save " + filepath + " format session");
+  }
+
+  /**
+   * Returns the range(s) formatted as a ChimeraX atomspec, for example
+   * <p>
+   * #1/A:2-20,30-40/B:10-20|#2/A:12-30
+   * <p>
+   * Note there is no need to explicitly exclude ALTLOC atoms when
+   * {@code alphaOnly == true}, as this is the default behaviour of ChimeraX (a
+   * change from Chimera)
+   * 
+   * @return
+   */
+  @Override
+  public String getAtomSpec(AtomSpecModel atomSpec, AtomSpecType specType)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModel = true;
+    for (String model : atomSpec.getModels())
+    {
+      if (!firstModel)
+      {
+        sb.append("|");
+      }
+      firstModel = false;
+      appendModel(sb, model, atomSpec);
+      if (specType == AtomSpecType.ALPHA)
+      {
+        sb.append("@CA");
+      }
+      if (specType == AtomSpecType.PHOSPHATE)
+      {
+        sb.append("@P");
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * A helper method to append an atomSpec string for atoms in the given model
+   * 
+   * @param sb
+   * @param model
+   * @param atomSpec
+   */
+  protected void appendModel(StringBuilder sb, String model,
+          AtomSpecModel atomSpec)
+  {
+    sb.append("#").append(model);
+
+    for (String chain : atomSpec.getChains(model))
+    {
+      boolean firstPositionForChain = true;
+      sb.append("/").append(chain.trim()).append(":");
+      List<int[]> rangeList = atomSpec.getRanges(model, chain);
+      boolean first = true;
+      for (int[] range : rangeList)
+      {
+        if (!first)
+        {
+          sb.append(",");
+        }
+        first = false;
+        appendRange(sb, range[0], range[1], chain, firstPositionForChain,
+                true);
+      }
+    }
+  }
+
+  @Override
+  public List<StructureCommandI> showBackbone()
+  {
+    return Arrays.asList(SHOW_BACKBONE);
+  }
+
+  @Override
+  public List<StructureCommandI> superposeStructures(AtomSpecModel ref,
+          AtomSpecModel spec, AtomSpecType backbone)
+  {
+    /*
+     * Form ChimeraX match command to match spec to ref
+     * 
+     * match #1/A:2-94 toAtoms #2/A:1-93
+     * 
+     * @see https://www.cgl.ucsf.edu/chimerax/docs/user/commands/align.html
+     */
+    StringBuilder cmd = new StringBuilder();
+    String atomSpec = getAtomSpec(spec, backbone);
+    String refSpec = getAtomSpec(ref, backbone);
+    cmd.append("align ").append(atomSpec).append(" toAtoms ")
+            .append(refSpec);
+
+    /*
+     * show superposed residues as ribbon, others as chain
+     */
+    cmd.append("; ribbon ");
+    cmd.append(getAtomSpec(spec, AtomSpecType.RESIDUE_ONLY)).append("|");
+    cmd.append(getAtomSpec(ref, AtomSpecType.RESIDUE_ONLY))
+            .append("; view");
+
+    return Arrays.asList(new StructureCommand(cmd.toString()));
+  }
+
+  @Override
+  public StructureCommandI openSession(String filepath)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html#composite
+    // this version of the command has no dependency on file extension
+    return new StructureCommand("open " + filepath + " format session");
+  }
+
+  @Override
+  public StructureCommandI closeViewer()
+  {
+    return CLOSE_CHIMERAX;
+  }
+
+  @Override
+  public List<StructureCommandI> startNotifications(String uri)
+  {
+    List<StructureCommandI> cmds = new ArrayList<>();
+    cmds.add(new StructureCommand(
+            "info notify start models jalview prefix ModelChanged url "
+                    + uri));
+    cmds.add(new StructureCommand(
+            "info notify start selection jalview prefix SelectionChanged url "
+                    + uri));
+    return cmds;
+  }
+
+  @Override
+  public List<StructureCommandI> stopNotifications()
+  {
+    List<StructureCommandI> cmds = new ArrayList<>();
+    cmds.add(STOP_NOTIFY_MODELS);
+    cmds.add(STOP_NOTIFY_SELECTION);
+    return cmds;
+  }
+
+  @Override
+  public StructureCommandI getSelectedResidues()
+  {
+    return GET_SELECTION;
+  }
+
+  @Override
+  public StructureCommandI listResidueAttributes()
+  {
+    return LIST_RESIDUE_ATTRIBUTES;
+  }
+}