JAL-2422 pull-up refactoring of structure commands (continued)
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 20 Feb 2020 14:06:13 +0000 (14:06 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 20 Feb 2020 14:06:13 +0000 (14:06 +0000)
26 files changed:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/structures/JalviewStructureDisplayI.java
src/jalview/appletgui/AppletJmol.java
src/jalview/appletgui/UserDefinedColours.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/ChimeraXCommands.java [new file with mode: 0644]
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/AppJmol.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/ChimeraXViewFrame.java [new file with mode: 0644]
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/gui/JalviewChimeraXBindingModel.java [new file with mode: 0644]
src/jalview/gui/StructureViewerBase.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/structure/StructureCommandsBase.java [new file with mode: 0644]
src/jalview/structure/StructureCommandsFactory.java [new file with mode: 0644]
src/jalview/structure/StructureCommandsI.java [new file with mode: 0644]
src/jalview/structures/models/AAStructureBindingModel.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/structure/StructureSelectionManagerTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java

index c5031c2..7ec251d 100644 (file)
@@ -1123,7 +1123,7 @@ status.fetching_db_refs = Fetching db refs
 status.loading_cached_pdb_entries = Loading Cached PDB Entries
 status.searching_for_pdb_structures = Searching for PDB Structures
 status.opening_file_for = opening file for
-status.colouring_chimera = Colouring Chimera
+status.colouring_structures = Colouring structures
 label.font_doesnt_have_letters_defined = Font doesn't have letters defined\nso cannot be used\nwith alignment data
 label.font_too_small = Font size is too small
 label.error_loading_file_params = Error loading file {0}
index 369ac96..af1fc87 100644 (file)
@@ -1198,7 +1198,7 @@ label.confirm_close_chimera=Cerrar
 tooltip.rnalifold_calculations=Se calcularán predicciones de estructura secondaria de RNA para el alineaminento, y se actualizarán si se efectuan cambios
 tooltip.rnalifold_settings=Modificar la configuración de la predicción RNAAlifold. Úselo para ocultar o mostrar resultados del cálculo de RNA, o cambiar parámetros de el plegado de RNA.
 label.show_selected_annotations=Mostrar anotaciones seleccionadas
-status.colouring_chimera=Coloreando Chimera
+status.colouring_structures=Coloreando estructuras
 label.configure_displayed_columns=Configurar Columnas Mostradas
 label.aacon_calculations=cálculos AACon
 label.pdb_web-service_error=Error de servicio web PDB
index 506334c..d8c8371 100644 (file)
@@ -23,7 +23,6 @@ package jalview.api.structures;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.schemes.ColourSchemeI;
 import jalview.structures.models.AAStructureBindingModel;
 
 public interface JalviewStructureDisplayI
@@ -59,13 +58,6 @@ public interface JalviewStructureDisplayI
   void closeViewer(boolean closeExternalViewer);
 
   /**
-   * apply a colourscheme to the structures in the viewer
-   * 
-   * @param colourScheme
-   */
-  void setJalviewColourScheme(ColourSchemeI colourScheme);
-
-  /**
    * 
    * @return true if all background sequence/structure binding threads have
    *         completed for this viewer instance
index 2cd9ee5..89a912f 100644 (file)
@@ -406,7 +406,7 @@ public class AppletJmol extends EmbmenuFrame implements
         }
       }
     }
-    jmb.centerViewer(toshow);
+    jmb.showChains(toshow);
   }
 
   void closeViewer()
@@ -455,41 +455,41 @@ public class AppletJmol extends EmbmenuFrame implements
     else if (evt.getSource() == zappo)
     {
       setEnabled(zappo);
-      jmb.setJalviewColourScheme(new ZappoColourScheme());
+      jmb.colourByJalviewColourScheme(new ZappoColourScheme());
     }
     else if (evt.getSource() == taylor)
     {
       setEnabled(taylor);
-      jmb.setJalviewColourScheme(new TaylorColourScheme());
+      jmb.colourByJalviewColourScheme(new TaylorColourScheme());
     }
     else if (evt.getSource() == hydro)
     {
       setEnabled(hydro);
-      jmb.setJalviewColourScheme(new HydrophobicColourScheme());
+      jmb.colourByJalviewColourScheme(new HydrophobicColourScheme());
     }
     else if (evt.getSource() == helix)
     {
       setEnabled(helix);
-      jmb.setJalviewColourScheme(new HelixColourScheme());
+      jmb.colourByJalviewColourScheme(new HelixColourScheme());
     }
     else if (evt.getSource() == strand)
     {
       setEnabled(strand);
-      jmb.setJalviewColourScheme(new StrandColourScheme());
+      jmb.colourByJalviewColourScheme(new StrandColourScheme());
     }
     else if (evt.getSource() == turn)
     {
       setEnabled(turn);
-      jmb.setJalviewColourScheme(new TurnColourScheme());
+      jmb.colourByJalviewColourScheme(new TurnColourScheme());
     }
     else if (evt.getSource() == buried)
     {
       setEnabled(buried);
-      jmb.setJalviewColourScheme(new BuriedColourScheme());
+      jmb.colourByJalviewColourScheme(new BuriedColourScheme());
     }
     else if (evt.getSource() == purinepyrimidine)
     {
-      jmb.setJalviewColourScheme(new PurinePyrimidineColourScheme());
+      jmb.colourByJalviewColourScheme(new PurinePyrimidineColourScheme());
     }
     else if (evt.getSource() == user)
     {
@@ -693,9 +693,9 @@ public class AppletJmol extends EmbmenuFrame implements
    * 
    * }
    */
-  public void setJalviewColourScheme(UserColourScheme ucs)
+  public void colourByJalviewColourScheme(UserColourScheme ucs)
   {
-    jmb.setJalviewColourScheme(ucs);
+    jmb.colourByJalviewColourScheme(ucs);
   }
 
   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
index 6831a73..bfce880 100644 (file)
@@ -524,7 +524,7 @@ public class UserDefinedColours extends Panel
     }
     else if (jmol != null)
     {
-      jmol.setJalviewColourScheme(ucs);
+      jmol.colourByJalviewColourScheme(ucs);
     }
     else if (pdbcanvas != null)
     {
index e19bb29..9b8fb91 100644 (file)
@@ -20,9 +20,7 @@
  */
 package jalview.ext.jmol;
 
-import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
@@ -30,15 +28,11 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.IProgressIndicator;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
-import java.awt.Color;
 import java.awt.Container;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
@@ -46,7 +40,6 @@ import java.io.File;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.BitSet;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
@@ -58,7 +51,6 @@ import org.jmol.api.JmolSelectionListener;
 import org.jmol.api.JmolStatusListener;
 import org.jmol.api.JmolViewer;
 import org.jmol.c.CBK;
-import org.jmol.script.T;
 import org.jmol.viewer.Viewer;
 
 public abstract class JalviewJmolBinding extends AAStructureBindingModel
@@ -77,10 +69,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   Vector<String> atomsPicked = new Vector<>();
 
-  private List<String> chainNames;
-
-  Hashtable<String, String> chainFile;
-
   /*
    * the default or current model displayed if the model cannot be identified
    * from the selection message
@@ -102,6 +90,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           DataSourceType protocol)
   {
     super(ssm, pdbentry, sequenceIs, protocol);
+    setStructureCommands(new JmolCommands());
     /*
      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
      * "jalviewJmol", ap.av.applet .getDocumentBase(),
@@ -119,6 +108,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     jmolViewer = theViewer;
     jmolViewer.setJmolStatusListener(this);
     jmolViewer.addSelectionListener(this);
+    setStructureCommands(new JmolCommands());
   }
 
   /**
@@ -132,36 +122,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return getViewerTitle("Jmol", true);
   }
 
-  /**
-   * prepare the view for a given set of models/chains. chainList contains
-   * strings of the form 'pdbfilename:Chaincode'
-   * 
-   * @param chainList
-   *          list of chains to make visible
-   */
-  public void centerViewer(Vector<String> chainList)
-  {
-    StringBuilder cmd = new StringBuilder(128);
-    int mlength, p;
-    for (String lbl : chainList)
-    {
-      mlength = 0;
-      do
-      {
-        p = mlength;
-        mlength = lbl.indexOf(":", p);
-      } while (p < mlength && mlength < (lbl.length() - 2));
-      // TODO: lookup each pdb id and recover proper model number for it.
-      cmd.append(":" + lbl.substring(mlength + 1) + " /"
-              + (1 + getModelNum(chainFile.get(lbl))) + " or ");
-    }
-    if (cmd.length() > 0)
-    {
-      cmd.setLength(cmd.length() - 4);
-    }
-    evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
-  }
-
   public void closeViewer()
   {
     // remove listeners for all structures in viewer
@@ -172,24 +132,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     releaseUIResources();
   }
 
-  @Override
-  public void colourByChain()
-  {
-    colourBySequence = false;
-    // TODO: colour by chain should colour each chain distinctly across all
-    // visible models
-    // TODO: http://issues.jalview.org/browse/JAL-628
-    evalStateCommand("select *;color chain");
-  }
-
-  @Override
-  public void colourByCharge()
-  {
-    colourBySequence = false;
-    evalStateCommand("select *;color white;select ASP,GLU;color red;"
-            + "select LYS,ARG;color blue;select CYS;color yellow");
-  }
-
   /**
    * superpose the structures associated with sequences in the alignment
    * according to their corresponding positions.
@@ -442,13 +384,13 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       {
         // TODO is performing selectioncom redundant here? is done later on
         // System.out.println("Select regions:\n" + selectioncom.toString());
-        evalStateCommand("select *; cartoons off; backbone; select ("
-                + selectioncom.toString() + "); cartoons; ");
+        executeCommand("select *; cartoons off; backbone; select ("
+                + selectioncom.toString() + "); cartoons; ", false);
         // selcom.append("; ribbons; ");
         String cmdString = command.toString();
         // System.out.println("Superimpose command(s):\n" + cmdString);
 
-        evalStateCommand(cmdString);
+        executeCommand(cmdString, false);
       }
     }
     if (selectioncom.length() > 0)
@@ -458,8 +400,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         selectioncom.setLength(selectioncom.length() - 1);
       }
       // System.out.println("Select regions:\n" + selectioncom.toString());
-      evalStateCommand("select *; cartoons off; backbone; select ("
-              + selectioncom.toString() + "); cartoons; ");
+      executeCommand("select *; cartoons off; backbone; select ("
+              + selectioncom.toString() + "); cartoons; ", false);
       // evalStateCommand("select *; backbone; select "+selcom.toString()+";
       // cartoons; center "+selcom.toString());
     }
@@ -467,8 +409,13 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return null;
   }
 
-  public void evalStateCommand(String command)
+  @Override
+  public List<String> executeCommand(String command, boolean getReply)
   {
+    if (command == null)
+    {
+      return null;
+    }
     jmolHistory(false);
     if (lastCommand == null || !lastCommand.equals(command))
     {
@@ -476,60 +423,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     }
     jmolHistory(true);
     lastCommand = command;
-  }
-
-  Thread colourby = null;
-  /**
-   * Sends a set of colour commands to the structure viewer
-   * 
-   * @param colourBySequenceCommands
-   */
-  @Override
-  protected void colourBySequence(
-          final StructureMappingcommandSet[] colourBySequenceCommands)
-  {
-    if (colourby != null)
-    {
-      colourby.interrupt();
-      colourby = null;
-    }
-    colourby = new Thread(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
-        {
-          for (String cbyseq : cpdbbyseq.commands)
-          {
-            executeWhenReady(cbyseq);
-          }
-        }
-      }
-    });
-    colourby.start();
-  }
-
-  /**
-   * @param files
-   * @param sr
-   * @param viewPanel
-   * @return
-   */
-  @Override
-  protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
-  {
-    return JmolCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, viewPanel);
-  }
-
-  /**
-   * @param command
-   */
-  protected void executeWhenReady(String command)
-  {
-    evalStateCommand(command);
+    return null;
   }
 
   public void createImage(String file, String type, int quality)
@@ -570,19 +464,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return null;
   }
 
-  public Color getColour(int atomIndex, int pdbResNum, String chain,
-          String pdbfile)
-  {
-    if (getModelNum(pdbfile) < 0)
-    {
-      return null;
-    }
-    // TODO: verify atomIndex is selecting correct model.
-    // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4
-    int colour = jmolViewer.ms.at[atomIndex].atomPropertyInt(T.color);
-    return new Color(colour);
-  }
-
   /**
    * instruct the Jalview binding to update the pdbentries vector if necessary
    * prior to matching the jmol view's contents to the list of structure files
@@ -590,23 +471,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    */
   public abstract void refreshPdbEntries();
 
-  private int getModelNum(String modelFileName)
-  {
-    String[] mfn = getStructureFiles();
-    if (mfn == null)
-    {
-      return -1;
-    }
-    for (int i = 0; i < mfn.length; i++)
-    {
-      if (mfn[i].equalsIgnoreCase(modelFileName))
-      {
-        return i;
-      }
-    }
-    return -1;
-  }
-
   /**
    * map between index of model filename returned from getPdbFile and the first
    * index of models from this file in the viewer. Note - this is not trimmed -
@@ -854,7 +718,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
               .append(toks.nextToken());
       sb.append("|").append(label).append("\"");
-      evalStateCommand(sb.toString());
+      executeCommand(sb.toString(), false);
     }
   }
 
@@ -1036,8 +900,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     fileLoadingError = null;
     String[] oldmodels = modelFileNames;
     modelFileNames = null;
-    chainNames = new ArrayList<>();
-    chainFile = new Hashtable<>();
     boolean notifyLoaded = false;
     String[] modelfilenames = getStructureFiles();
     // first check if we've lost any structures
@@ -1146,10 +1008,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           // add an entry for every chain in the model
           for (int i = 0; i < pdb.getChains().size(); i++)
           {
-            String chid = new String(
-                    pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
-            chainFile.put(chid, fileName);
-            chainNames.add(chid);
+            String chid = pdb.getId() + ":"
+                    + pdb.getChains().elementAt(i).id;
+            addChainFile(chid, fileName);
+            getChainNames().add(chid);
           }
           notifyLoaded = true;
         }
@@ -1198,12 +1060,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     setLoadingFromArchive(false);
   }
 
-  @Override
-  public List<String> getChainNames()
-  {
-    return chainNames;
-  }
-
   protected IProgressIndicator getIProgressIndicator()
   {
     return null;
@@ -1248,35 +1104,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   }
 
-  @Override
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    colourBySequence = false;
-
-    if (cs == null)
-    {
-      return;
-    }
-
-    jmolHistory(false);
-    StringBuilder command = new StringBuilder(128);
-    command.append("select *;color white;");
-    List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
-            false);
-    for (String resName : residueSet)
-    {
-      char res = resName.length() == 3
-              ? ResidueProperties.getSingleCharacterCode(resName)
-              : resName.charAt(0);
-      Color col = cs.findColour(res, 0, null, null, 0f);
-      command.append("select " + resName + ";color[" + col.getRed() + ","
-              + col.getGreen() + "," + col.getBlue() + "];");
-    }
-
-    evalStateCommand(command.toString());
-    jmolHistory(true);
-  }
-
   public void showHelp()
   {
     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
@@ -1367,15 +1194,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   protected org.jmol.api.JmolAppConsoleInterface console = null;
 
   @Override
-  public void setBackgroundColour(java.awt.Color col)
-  {
-    jmolHistory(false);
-    jmolViewer.evalStringQuiet("background [" + col.getRed() + ","
-            + col.getGreen() + "," + col.getBlue() + "];");
-    jmolHistory(true);
-  }
-
-  @Override
   public int[] resizeInnerPanel(String data)
   {
     // Jalview doesn't honour resize panel requests
@@ -1435,4 +1253,22 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   {
     showConsole(false);
   }
+
+  @Override
+  protected int getModelNoForFile(String pdbFile)
+  {
+    if (modelFileNames == null)
+    {
+      return -1;
+    }
+    for (int i = 0; i < modelFileNames.length; i++)
+    {
+      if (modelFileNames[i].equalsIgnoreCase(pdbFile))
+      {
+        return i;
+      }
+    }
+    return -1;
+
+  }
 }
index 8fb0de6..c8a54cd 100644 (file)
@@ -28,49 +28,56 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.StructureCommandsBase;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 /**
- * Routines for generating Jmol commands for Jalview/Jmol binding another
- * cruisecontrol test.
+ * Routines for generating Jmol commands for Jalview/Jmol binding
  * 
  * @author JimP
  * 
  */
-public class JmolCommands
+public class JmolCommands extends StructureCommandsBase
 {
+  private static final String CMD_COLOUR_BY_CHARGE = "select *;color white;select ASP,GLU;color red;"
+          + "select LYS,ARG;color blue;select CYS;color yellow";
 
-  /**
-   * Jmol utility which constructs the commands to colour chains by the given
-   * alignment
-   * 
-   * @returns Object[] { Object[] { <model being coloured>,
-   * 
-   */
-  public static StructureMappingcommandSet[] getColourBySequenceCommand(
+  private static final String CMD_COLOUR_BY_CHAIN = "select *;color chain";
+
+  private static String formatRGB(Color c) {
+    return c == null ? null
+            : String.format("[%d,%d,%d]", c.getRed(), c.getGreen(),
+                    c.getBlue());
+  }
+
+  @Override
+  public String[] colourBySequence(
           StructureSelectionManager ssm, String[] files,
           SequenceI[][] sequence, SequenceRenderer sr,
           AlignmentViewPanel viewPanel)
   {
+    // TODO refactor to call buildColoursMap() first...
+
     FeatureRenderer fr = viewPanel.getFeatureRenderer();
     FeatureColourFinder finder = new FeatureColourFinder(fr);
     AlignViewportI viewport = viewPanel.getAlignViewport();
     HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
     AlignmentI al = viewport.getAlignment();
-    List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
+    List<String> cset = new ArrayList<>();
 
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-      StringBuffer command = new StringBuffer();
-      StructureMappingcommandSet smc;
-      ArrayList<String> str = new ArrayList<String>();
+      StringBuilder command = new StringBuilder(128);
+      List<String> str = new ArrayList<>();
 
       if (mapping == null || mapping.length < 1)
       {
@@ -89,7 +96,7 @@ public class JmolCommands
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
-              if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+              if (Comparison.isGap(asp.getCharAt(r)))
               {
                 continue;
               }
@@ -164,15 +171,14 @@ public class JmolCommands
         str.add(command.toString());
         command.setLength(0);
       }
-      // Finally, add the command set ready to be returned.
-      cset.add(new StructureMappingcommandSet(JmolCommands.class,
-              files[pdbfnum], str.toArray(new String[str.size()])));
+      cset.addAll(str);
 
     }
-    return cset.toArray(new StructureMappingcommandSet[cset.size()]);
+    return cset.toArray(new String[cset.size()]);
   }
 
-  public static StringBuffer condenseCommand(StringBuffer command, int pos)
+  public static StringBuilder condenseCommand(StringBuilder command,
+          int pos)
   {
 
     // work back to last 'select'
@@ -187,7 +193,7 @@ public class JmolCommands
       ;
     } while ((q = command.indexOf("select", p)) == -1 && p > 0);
 
-    StringBuffer sb = new StringBuffer(command.substring(0, q + 7));
+    StringBuilder sb = new StringBuilder(command.substring(0, q + 7));
 
     command = command.delete(0, q + 7);
 
@@ -207,4 +213,76 @@ public class JmolCommands
     return sb;
   }
 
+  @Override
+  public String colourByChain()
+  {
+    return CMD_COLOUR_BY_CHAIN;
+  }
+
+  @Override
+  public String colourByCharge()
+  {
+    return CMD_COLOUR_BY_CHARGE;
+  }
+
+  @Override
+  public String colourByResidues(Map<String, Color> colours)
+  {
+    StringBuilder cmd = new StringBuilder(128);
+    cmd.append("select *;color white;");
+
+    /*
+     * concatenate commands like
+     * select VAL;color[100,215,55]
+     */
+    for (Entry<String, Color> entry : colours.entrySet())
+    {
+      Color col = entry.getValue();
+      String resCode = entry.getKey();
+      cmd.append("select ").append(resCode).append(";");
+      cmd.append("color[");
+      cmd.append(formatRGB(col));
+      cmd.append("];");
+    }
+
+    return cmd.toString();
+  }
+
+  @Override
+  public String setBackgroundColour(Color col)
+  {
+    return "background " + formatRGB(col);
+  }
+
+  @Override
+  public String focusView()
+  {
+    return "zoom 0";
+  }
+
+  @Override
+  public String showChains(List<String> toShow)
+  {
+    StringBuilder atomSpec = new StringBuilder(128);
+    boolean first = true;
+    for (String chain : toShow)
+    {
+      String[] tokens = chain.split(":");
+      if (tokens.length == 2)
+      {
+        if (!first)
+        {
+          atomSpec.append(" or ");
+        }
+        first = false;
+        atomSpec.append(":").append(tokens[1]).append(" /").append(tokens[0]);
+      }
+    }
+
+    String spec = atomSpec.toString();
+    String command = "select *;restrict " + spec + ";cartoon;center "
+            + spec;
+    return command;
+  }
+
 }
index b35faa8..9342286 100644 (file)
@@ -31,6 +31,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.Desktop;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.StructureCommandsBase;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
@@ -43,6 +44,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Routines for generating Chimera commands for Jalview/Chimera binding
@@ -50,39 +52,28 @@ import java.util.Map;
  * @author JimP
  * 
  */
-public class ChimeraCommands
+public class ChimeraCommands extends StructureCommandsBase
 {
-
   public static final String NAMESPACE_PREFIX = "jv_";
 
-  /**
-   * Constructs Chimera commands to colour residues as per the Jalview alignment
-   * 
-   * @param ssm
-   * @param files
-   * @param sequence
-   * @param sr
-   * @param fr
-   * @param viewPanel
-   * @param isChimeraX
-   * @return
-   */
-  public static StructureMappingcommandSet[] getColourBySequenceCommand(
+  protected static final String CMD_SEPARATOR = ";";
+
+  private static final String CMD_COLOUR_BY_CHARGE = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
+
+  private static final String CMD_COLOUR_BY_CHAIN = "rainbow chain";
+
+  @Override
+  public String[] colourBySequence(
           StructureSelectionManager ssm, String[] files,
           SequenceI[][] sequence, SequenceRenderer sr,
-          AlignmentViewPanel viewPanel, boolean isChimeraX)
+          AlignmentViewPanel viewPanel)
   {
     Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
-            sequence, sr, viewPanel, isChimeraX);
-
-    List<String> colourCommands = buildColourCommands(colourMap,
-            isChimeraX);
+            sequence, sr, viewPanel);
 
-    StructureMappingcommandSet cs = new StructureMappingcommandSet(
-            ChimeraCommands.class, null,
-            colourCommands.toArray(new String[colourCommands.size()]));
+    List<String> colourCommands = buildColourCommands(colourMap);
 
-    return new StructureMappingcommandSet[] { cs };
+    return colourCommands.toArray(new String[colourCommands.size()]);
   }
 
   /**
@@ -98,11 +89,10 @@ public class ChimeraCommands
    * </pre>
    * 
    * @param colourMap
-   * @param isChimeraX
    * @return
    */
-  protected static List<String> buildColourCommands(
-          Map<Object, AtomSpecModel> colourMap, boolean isChimeraX)
+  protected List<String> buildColourCommands(
+          Map<Object, AtomSpecModel> colourMap)
   {
     /*
      * This version concatenates all commands into a single String (semi-colon
@@ -120,22 +110,20 @@ public class ChimeraCommands
       {
         sb.append("; ");
       }
-      sb.append("color ");
       firstColour = false;
       final AtomSpecModel colourData = colourMap.get(colour);
-      if (isChimeraX)
-      {
-        sb.append(colourData.getAtomSpecX()).append(" ").append(colourCode);
-      }
-      else
-      {
-        sb.append(colourCode).append(" ").append(colourData.getAtomSpec());
-      }
+      sb.append(getColourCommand(colourData, colourCode));
     }
     commands.add(sb.toString());
     return commands;
   }
 
+  protected String getColourCommand(AtomSpecModel colourData,
+          String colourCode)
+  {
+    return "color " + colourCode + " " + colourData.getAtomSpec();
+  }
+
   /**
    * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
    * builds a Chimera format atom spec
@@ -203,13 +191,12 @@ public class ChimeraCommands
    * @param sequence
    * @param sr
    * @param viewPanel
-   * @param isChimeraX
    * @return
    */
   protected static Map<Object, AtomSpecModel> buildColoursMap(
           StructureSelectionManager ssm, String[] files,
           SequenceI[][] sequence, SequenceRenderer sr,
-          AlignmentViewPanel viewPanel, boolean isChimeraX)
+          AlignmentViewPanel viewPanel)
   {
     FeatureRenderer fr = viewPanel.getFeatureRenderer();
     FeatureColourFinder finder = new FeatureColourFinder(fr);
@@ -221,7 +208,7 @@ public class ChimeraCommands
 
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
-      final int modelNumber = pdbfnum + (isChimeraX ? 1 : 0);
+      final int modelNumber = pdbfnum + getModelStartNo();
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
 
       if (mapping == null || mapping.length < 1)
@@ -301,6 +288,11 @@ public class ChimeraCommands
     return colourMap;
   }
 
+  protected static int getModelStartNo()
+  {
+    return 0;
+  }
+
   /**
    * Helper method to add one contiguous range to the AtomSpec model for the given
    * value (creating the model if necessary). As used by Jalview, {@code value} is
@@ -630,10 +622,10 @@ public class ChimeraCommands
          */
         AtomSpecModel atomSpecModel = values.get(value);
         StringBuilder sb = new StringBuilder(128);
-        sb.append("setattr ");
+        sb.append("setattr");
         if (isChimeraX)
         {
-          sb.append(atomSpecModel.getAtomSpecX());
+          sb.append(" ").append(atomSpecModel.getAtomSpecX());
         }
         String featureValue = value.toString();
         featureValue = featureValue.replaceAll("\\'", "&#39;");
@@ -690,4 +682,82 @@ public class ChimeraCommands
     return attName;
   }
 
+  @Override
+  public String colourByChain()
+  {
+    return CMD_COLOUR_BY_CHAIN;
+  }
+
+  @Override
+  public String colourByCharge()
+  {
+    return CMD_COLOUR_BY_CHARGE;
+  }
+
+  @Override
+  public String colourByResidues(Map<String, Color> colours)
+  {
+    StringBuilder cmd = new StringBuilder(12 * colours.size());
+
+    /*
+     * concatenate commands like
+     * color #4949b6 ::VAL
+     */
+    for (Entry<String, Color> entry : colours.entrySet())
+    {
+      String colorSpec = ColorUtils.toTkCode(entry.getValue());
+      String resCode = entry.getKey();
+      cmd.append("color ").append(colorSpec).append(" ::").append(resCode)
+              .append(CMD_SEPARATOR);
+    }
+    return cmd.toString();
+  }
+
+  @Override
+  public String setBackgroundColour(Color col)
+  {
+    return "set bgColor " + ColorUtils.toTkCode(col);
+  }
+
+  @Override
+  public String focusView()
+  {
+    return "focus";
+  }
+
+  @Override
+  public String showChains(List<String> toShow)
+  {
+    /*
+     * Construct a chimera command like
+     * 
+     * ~display #*;~ribbon #*;ribbon :.A,:.B
+     */
+    StringBuilder cmd = new StringBuilder(64);
+    boolean first = true;
+    for (String chain : toShow)
+    {
+      String[] tokens = chain.split(":");
+      if (tokens.length == 2)
+      {
+        String showChainCmd = tokens[0] + ":." + tokens[1];
+        if (!first)
+        {
+          cmd.append(",");
+        }
+        cmd.append(showChainCmd);
+        first = false;
+      }
+    }
+
+    /*
+     * could append ";focus" to this command to resize the display to fill the
+     * window, but it looks more helpful not to (easier to relate chains to the
+     * whole)
+     */
+    final String command = "~display #*; ~ribbon #*; ribbon :"
+            + cmd.toString();
+    return command;
+  }
+
 }
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..3947bb0
--- /dev/null
@@ -0,0 +1,627 @@
+/*
+ * 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 jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.Desktop;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Routines for generating ChimeraX commands for Jalview/ChimeraX binding
+ */
+public class ChimeraXCommands extends ChimeraCommands
+{
+  public static final String NAMESPACE_PREFIX = "jv_";
+
+  private static final String CMD_COLOUR_BY_CHARGE = "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow";
+
+  /**
+   * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
+   * builds a Chimera format atom spec
+   * 
+   * @param modelAndChainRanges
+   */
+  protected static String getAtomSpec(
+          Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModelForColour = true;
+    for (Integer model : modelAndChainRanges.keySet())
+    {
+      boolean firstPositionForModel = true;
+      if (!firstModelForColour)
+      {
+        sb.append("|");
+      }
+      firstModelForColour = false;
+      sb.append("#").append(model).append(":");
+
+      final Map<String, List<int[]>> modelData = modelAndChainRanges
+              .get(model);
+      for (String chain : modelData.keySet())
+      {
+        boolean hasChain = !"".equals(chain.trim());
+        for (int[] range : modelData.get(chain))
+        {
+          if (!firstPositionForModel)
+          {
+            sb.append(",");
+          }
+          if (range[0] == range[1])
+          {
+            sb.append(range[0]);
+          }
+          else
+          {
+            sb.append(range[0]).append("-").append(range[1]);
+          }
+          if (hasChain)
+          {
+            sb.append(".").append(chain);
+          }
+          firstPositionForModel = false;
+        }
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * <pre>
+   * Build a data structure which records contiguous subsequences for each colour. 
+   * From this we can easily generate the Chimera command for colour by sequence.
+   * Color
+   *     Model number
+   *         Chain
+   *             list of start/end ranges
+   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+   * </pre>
+   */
+  protected static Map<Object, AtomSpecModel> buildColoursMap(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
+  {
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
+    AlignmentI al = viewport.getAlignment();
+    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
+    Color lastColour = null;
+
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+
+      if (mapping == null || mapping.length < 1)
+      {
+        continue;
+      }
+
+      int startPos = -1, lastPos = -1;
+      String lastChain = "";
+      for (int s = 0; s < sequence[pdbfnum].length; s++)
+      {
+        for (int sp, m = 0; m < mapping.length; m++)
+        {
+          final SequenceI seq = sequence[pdbfnum][s];
+          if (mapping[m].getSequence() == seq
+                  && (sp = al.findIndex(seq)) > -1)
+          {
+            SequenceI asp = al.getSequenceAt(sp);
+            for (int r = 0; r < asp.getLength(); r++)
+            {
+              // no mapping to gaps in sequence
+              if (Comparison.isGap(asp.getCharAt(r)))
+              {
+                continue;
+              }
+              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
+
+              if (pos < 1 || pos == lastPos)
+              {
+                continue;
+              }
+
+              Color colour = sr.getResidueColour(seq, r, finder);
+
+              /*
+               * darker colour for hidden regions
+               */
+              if (!cs.isVisible(r))
+              {
+                colour = Color.GRAY;
+              }
+
+              final String chain = mapping[m].getChain();
+
+              /*
+               * Just keep incrementing the end position for this colour range
+               * _unless_ colour, PDB model or chain has changed, or there is a
+               * gap in the mapped residue sequence
+               */
+              final boolean newColour = !colour.equals(lastColour);
+              final boolean nonContig = lastPos + 1 != pos;
+              final boolean newChain = !chain.equals(lastChain);
+              if (newColour || nonContig || newChain)
+              {
+                if (startPos != -1)
+                {
+                  addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
+                          lastPos, lastChain);
+                }
+                startPos = pos;
+              }
+              lastColour = colour;
+              lastPos = pos;
+              lastChain = chain;
+            }
+            // final colour range
+            if (lastColour != null)
+            {
+              addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
+                      lastPos, lastChain);
+            }
+            // break;
+          }
+        }
+      }
+    }
+    return colourMap;
+  }
+
+  /**
+   * Helper method to add one contiguous range to the AtomSpec model for the given
+   * value (creating the model if necessary). As used by Jalview, {@code value} is
+   * <ul>
+   * <li>a colour, when building a 'colour structure by sequence' command</li>
+   * <li>a feature value, when building a 'set Chimera attributes from features'
+   * command</li>
+   * </ul>
+   * 
+   * @param map
+   * @param value
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  protected static void addAtomSpecRange(Map<Object, AtomSpecModel> map,
+          Object value, int model, int startPos, int endPos, String chain)
+  {
+    /*
+     * Get/initialize map of data for the colour
+     */
+    AtomSpecModel atomSpec = map.get(value);
+    if (atomSpec == null)
+    {
+      atomSpec = new AtomSpecModel();
+      map.put(value, atomSpec);
+    }
+
+    atomSpec.addRange(model, startPos, endPos, chain);
+  }
+
+  /**
+   * Constructs and returns Chimera commands to set attributes on residues
+   * corresponding to features in Jalview. Attribute names are the Jalview
+   * feature type, with a "jv_" prefix.
+   * 
+   * @param ssm
+   * @param files
+   * @param seqs
+   * @param viewPanel
+   * @return
+   */
+  public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
+          StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
+          AlignmentViewPanel viewPanel)
+  {
+    Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
+            ssm, files, seqs, viewPanel);
+
+    List<String> commands = buildSetAttributeCommands(featureMap);
+
+    StructureMappingcommandSet cs = new StructureMappingcommandSet(
+            ChimeraXCommands.class, null,
+            commands.toArray(new String[commands.size()]));
+
+    return cs;
+  }
+
+  /**
+   * <pre>
+   * Helper method to build a map of 
+   *   { featureType, { feature value, AtomSpecModel } }
+   * </pre>
+   * 
+   * @param ssm
+   * @param files
+   * @param seqs
+   * @param viewPanel
+   * @return
+   */
+  protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
+          StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
+          AlignmentViewPanel viewPanel)
+  {
+    Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
+
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    if (fr == null)
+    {
+      return theMap;
+    }
+
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
+
+    /*
+     * if alignment is showing features from complement, we also transfer
+     * these features to the corresponding mapped structure residues
+     */
+    boolean showLinkedFeatures = viewport.isShowComplementFeatures();
+    List<String> complementFeatures = new ArrayList<>();
+    FeatureRenderer complementRenderer = null;
+    if (showLinkedFeatures)
+    {
+      AlignViewportI comp = fr.getViewport().getCodingComplement();
+      if (comp != null)
+      {
+        complementRenderer = Desktop.getAlignFrameFor(comp)
+                .getFeatureRenderer();
+        complementFeatures = complementRenderer.getDisplayedFeatureTypes();
+      }
+    }
+    if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
+    {
+      return theMap;
+    }
+
+    AlignmentI alignment = viewPanel.getAlignment();
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+
+      if (mapping == null || mapping.length < 1)
+      {
+        continue;
+      }
+
+      for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
+      {
+        for (int m = 0; m < mapping.length; m++)
+        {
+          final SequenceI seq = seqs[pdbfnum][seqNo];
+          int sp = alignment.findIndex(seq);
+          StructureMapping structureMapping = mapping[m];
+          if (structureMapping.getSequence() == seq && sp > -1)
+          {
+            /*
+             * found a sequence with a mapping to a structure;
+             * now scan its features
+             */
+            if (!visibleFeatures.isEmpty())
+            {
+              scanSequenceFeatures(visibleFeatures, structureMapping, seq,
+                      theMap, pdbfnum);
+            }
+            if (showLinkedFeatures)
+            {
+              scanComplementFeatures(complementRenderer, structureMapping,
+                      seq, theMap, pdbfnum);
+            }
+          }
+        }
+      }
+    }
+    return theMap;
+  }
+
+  /**
+   * Scans visible features in mapped positions of the CDS/peptide complement, and
+   * adds any found to the map of attribute values/structure positions
+   * 
+   * @param complementRenderer
+   * @param structureMapping
+   * @param seq
+   * @param theMap
+   * @param modelNumber
+   */
+  protected static void scanComplementFeatures(
+          FeatureRenderer complementRenderer,
+          StructureMapping structureMapping, SequenceI seq,
+          Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+  {
+    /*
+     * for each sequence residue mapped to a structure position...
+     */
+    for (int seqPos : structureMapping.getMapping().keySet())
+    {
+      /*
+       * find visible complementary features at mapped position(s)
+       */
+      MappedFeatures mf = complementRenderer
+              .findComplementFeaturesAtResidue(seq, seqPos);
+      if (mf != null)
+      {
+        for (SequenceFeature sf : mf.features)
+        {
+          String type = sf.getType();
+
+          /*
+           * Don't copy features which originated from Chimera
+           */
+          if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
+                  .equals(sf.getFeatureGroup()))
+          {
+            continue;
+          }
+
+          /*
+           * record feature 'value' (score/description/type) as at the
+           * corresponding structure position
+           */
+          List<int[]> mappedRanges = structureMapping
+                  .getPDBResNumRanges(seqPos, seqPos);
+
+          if (!mappedRanges.isEmpty())
+          {
+            String value = sf.getDescription();
+            if (value == null || value.length() == 0)
+            {
+              value = type;
+            }
+            float score = sf.getScore();
+            if (score != 0f && !Float.isNaN(score))
+            {
+              value = Float.toString(score);
+            }
+            Map<Object, AtomSpecModel> featureValues = theMap.get(type);
+            if (featureValues == null)
+            {
+              featureValues = new HashMap<>();
+              theMap.put(type, featureValues);
+            }
+            for (int[] range : mappedRanges)
+            {
+              addAtomSpecRange(featureValues, value, modelNumber, range[0],
+                      range[1], structureMapping.getChain());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Inspect features on the sequence; for each feature that is visible, determine
+   * its mapped ranges in the structure (if any) according to the given mapping,
+   * and add them to the map.
+   * 
+   * @param visibleFeatures
+   * @param mapping
+   * @param seq
+   * @param theMap
+   * @param modelNumber
+   */
+  protected static void scanSequenceFeatures(List<String> visibleFeatures,
+          StructureMapping mapping, SequenceI seq,
+          Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+  {
+    List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
+            visibleFeatures.toArray(new String[visibleFeatures.size()]));
+    for (SequenceFeature sf : sfs)
+    {
+      String type = sf.getType();
+
+      /*
+       * Don't copy features which originated from Chimera
+       */
+      if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
+              .equals(sf.getFeatureGroup()))
+      {
+        continue;
+      }
+
+      List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
+              sf.getEnd());
+
+      if (!mappedRanges.isEmpty())
+      {
+        String value = sf.getDescription();
+        if (value == null || value.length() == 0)
+        {
+          value = type;
+        }
+        float score = sf.getScore();
+        if (score != 0f && !Float.isNaN(score))
+        {
+          value = Float.toString(score);
+        }
+        Map<Object, AtomSpecModel> featureValues = theMap.get(type);
+        if (featureValues == null)
+        {
+          featureValues = new HashMap<>();
+          theMap.put(type, featureValues);
+        }
+        for (int[] range : mappedRanges)
+        {
+          addAtomSpecRange(featureValues, value, modelNumber, range[0],
+                  range[1], mapping.getChain());
+        }
+      }
+    }
+  }
+
+  /**
+   * Traverse the map of features/values/models/chains/positions to construct a
+   * list of 'setattr' commands (one per distinct feature type and value).
+   * <p>
+   * The format of each command is
+   * 
+   * <pre>
+   * <blockquote> setattr r <featureName> " " #modelnumber:range.chain 
+   * e.g. setattr r jv:chain <value> #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+   * </blockquote>
+   * </pre>
+   * 
+   * @param featureMap
+   * @return
+   */
+  protected static List<String> buildSetAttributeCommands(
+          Map<String, Map<Object, AtomSpecModel>> featureMap)
+  {
+    List<String> commands = new ArrayList<>();
+    for (String featureType : featureMap.keySet())
+    {
+      String attributeName = makeAttributeName(featureType);
+
+      /*
+       * clear down existing attributes for this feature
+       */
+      // 'problem' - sets attribute to None on all residues - overkill?
+      // commands.add("~setattr r " + attributeName + " :*");
+
+      Map<Object, AtomSpecModel> values = featureMap.get(featureType);
+      for (Object value : values.keySet())
+      {
+        /*
+         * for each distinct value recorded for this feature type,
+         * add a command to set the attribute on the mapped residues
+         * Put values in single quotes, encoding any embedded single quotes
+         */
+        StringBuilder sb = new StringBuilder(128);
+        String featureValue = value.toString();
+        featureValue = featureValue.replaceAll("\\'", "&#39;");
+        sb.append("setattr r ").append(attributeName).append(" '")
+                .append(featureValue).append("' ");
+        sb.append(values.get(value).getAtomSpec());
+        commands.add(sb.toString());
+      }
+    }
+
+    return commands;
+  }
+
+  /**
+   * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied
+   * for a 'Jalview' namespace, and any non-alphanumeric character is converted
+   * to an underscore.
+   * 
+   * @param featureType
+   * @return
+   * 
+   *         <pre>
+   * &#64;see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
+   *         </pre>
+   */
+  protected static String makeAttributeName(String featureType)
+  {
+    StringBuilder sb = new StringBuilder();
+    if (featureType != null)
+    {
+      for (char c : featureType.toCharArray())
+      {
+        sb.append(Character.isLetterOrDigit(c) ? c : '_');
+      }
+    }
+    String attName = NAMESPACE_PREFIX + sb.toString();
+
+    /*
+     * Chimera treats an attribute name ending in 'color' as colour-valued;
+     * Jalview doesn't, so prevent this by appending an underscore
+     */
+    if (attName.toUpperCase().endsWith("COLOR"))
+    {
+      attName += "_";
+    }
+
+    return attName;
+  }
+
+  @Override
+  public String colourByCharge()
+  {
+    return CMD_COLOUR_BY_CHARGE;
+  }
+
+  @Override
+  public String colourByResidues(Map<String, Color> colours)
+  {
+    StringBuilder cmd = new StringBuilder(12 * colours.size());
+
+    /*
+     * concatenate commands like
+     * color :VAL #4949b6
+     */
+    for (Entry<String, Color> entry : colours.entrySet())
+    {
+      String colorSpec = ColorUtils.toTkCode(entry.getValue());
+      String resCode = entry.getKey();
+      cmd.append("color :").append(resCode).append(" ").append(colorSpec)
+              .append(CMD_SEPARATOR);
+    }
+    return cmd.toString();
+  }
+
+  @Override
+  public String setBackgroundColour(Color col)
+  {
+    return "set bgColor " + ColorUtils.toTkCode(col);
+  }
+
+  @Override
+  protected String getColourCommand(AtomSpecModel colourData,
+          String colourCode)
+  {
+    return "color " + colourData.getAtomSpecX() + " " + colourCode;
+  }
+
+  @Override
+  public String focusView()
+  {
+    return "view";
+  }
+
+}
index fc9b445..d7bcc66 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.ext.rbvi.chimera;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.SequenceRenderer;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
@@ -35,16 +34,12 @@ import jalview.gui.Preferences;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.httpserver.AbstractRequestHandler;
 import jalview.io.DataSourceType;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
-import jalview.util.ColorUtils;
 import jalview.util.MessageManager;
 
-import java.awt.Color;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -53,7 +48,6 @@ import java.net.BindException;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
-import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -71,19 +65,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   // Chimera clause to exclude alternate locations in atom selection
   private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
 
-  private static final String COLOURING_CHIMERA = MessageManager
-          .getString("status.colouring_chimera");
-
   private static final boolean debug = false;
 
   private static final String PHOSPHORUS = "P";
 
   private static final String ALPHACARBON = "CA";
 
-  private List<String> chainNames = new ArrayList<>();
-
-  private Hashtable<String, String> chainFile = new Hashtable<>();
-
   /*
    * Object through which we talk to Chimera
    */
@@ -168,7 +155,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
           int modelNumber = chimeraMaps.size() + 1;
           String command = "setattr #" + modelNumber + " models name "
                   + pe.getId();
-          sendChimeraCommand(command, false);
+          executeCommand(command, false);
           modelsToMap.add(new ChimeraModel(pe.getId(), ModelType.PDB_MODEL,
                   modelNumber, 0));
         }
@@ -222,7 +209,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     chimeraManager = new ChimeraManager(new StructureManager(true));
     String viewerType = Cache.getProperty(Preferences.STRUCTURE_DISPLAY);
     chimeraManager.setChimeraX(ViewerType.CHIMERAX.name().equals(viewerType));
-
+    setStructureCommands(new ChimeraCommands());
   }
 
   /**
@@ -274,43 +261,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Tells Chimera to display only the specified chains
-   * 
-   * @param toshow
-   */
-  public void showChains(List<String> toshow)
-  {
-    /*
-     * Construct a chimera command like
-     * 
-     * ~display #*;~ribbon #*;ribbon :.A,:.B
-     */
-    StringBuilder cmd = new StringBuilder(64);
-    boolean first = true;
-    for (String chain : toshow)
-    {
-      int modelNumber = getModelNoForChain(chain);
-      String showChainCmd = modelNumber == -1 ? ""
-              : modelNumber + ":." + chain.split(":")[1];
-      if (!first)
-      {
-        cmd.append(",");
-      }
-      cmd.append(showChainCmd);
-      first = false;
-    }
-
-    /*
-     * could append ";focus" to this command to resize the display to fill the
-     * window, but it looks more helpful not to (easier to relate chains to the
-     * whole)
-     */
-    final String command = "~display #*; ~ribbon #*; ribbon :"
-            + cmd.toString();
-    sendChimeraCommand(command, false);
-  }
-
-  /**
    * Close down the Jalview viewer and listener, and (optionally) the associated
    * Chimera window.
    */
@@ -335,32 +285,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     releaseUIResources();
   }
 
-  @Override
-  public void colourByChain()
-  {
-    colourBySequence = false;
-    sendAsynchronousCommand("rainbow chain", COLOURING_CHIMERA);
-  }
-
-  /**
-   * Constructs and sends a Chimera command to colour by charge
-   * <ul>
-   * <li>Aspartic acid and Glutamic acid (negative charge) red</li>
-   * <li>Lysine and Arginine (positive charge) blue</li>
-   * <li>Cysteine - yellow</li>
-   * <li>all others - white</li>
-   * </ul>
-   */
-  @Override
-  public void colourByCharge()
-  {
-    colourBySequence = false;
-    String command = chimeraManager.isChimeraX()
-            ? "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow"
-            : "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
-    sendAsynchronousCommand(command, COLOURING_CHIMERA);
-  }
-
   /**
    * {@inheritDoc}
    */
@@ -623,7 +547,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
       // allComs.append("; ~display all; chain @CA|P; ribbon ")
       // .append(selectioncom.toString()).append("; focus");
-      List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
+      List<String> chimeraReplies = executeCommand(allComs.toString(),
               true);
       for (String reply : chimeraReplies)
       {
@@ -714,10 +638,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * @param command
    * @param getResponse
    */
-  public List<String> sendChimeraCommand(final String command,
+  @Override
+  public List<String> executeCommand(final String command,
           boolean getResponse)
   {
-    if (chimeraManager == null)
+    if (chimeraManager == null || command == null)
     {
       // ? thread running after viewer shut down
       return null;
@@ -755,44 +680,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
           String progressMsg);
 
   /**
-   * Sends a set of colour commands to the structure viewer
-   * 
-   * @param colourBySequenceCommands
-   */
-  @Override
-  protected void colourBySequence(
-          StructureMappingcommandSet[] colourBySequenceCommands)
-  {
-    for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
-    {
-      for (String command : cpdbbyseq.commands)
-      {
-        sendAsynchronousCommand(command, COLOURING_CHIMERA);
-      }
-    }
-  }
-
-  /**
-   * @param files
-   * @param sr
-   * @param viewPanel
-   * @return
-   */
-  @Override
-  protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
-  {
-    return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, viewPanel, chimeraManager.isChimeraX());
-  }
-
-  /**
    * @param command
    */
   protected void executeWhenReady(String command)
   {
     waitForChimera();
-    sendChimeraCommand(command, false);
+    executeCommand(command, false);
     waitForChimera();
   }
 
@@ -1007,51 +900,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     return loadNotifiesHandled;
   }
 
-  @Override
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    colourBySequence = false;
-
-    if (cs == null)
-    {
-      return;
-    }
-
-    viewerCommandHistory(false);
-    StringBuilder command = new StringBuilder(128);
-
-    List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
-            false);
-
-    /*
-     * concatenate colour commands, one per residue symbol
-     * Chimera format:  color colorCode ::VAL
-     * ChimeraX format: color :VAL colourCode
-     */
-    boolean chimeraX = chimeraManager.isChimeraX();
-    for (String resName : residueSet)
-    {
-      char res = resName.length() == 3
-              ? ResidueProperties.getSingleCharacterCode(resName)
-              : resName.charAt(0);
-      Color col = cs.findColour(res, 0, null, null, 0f);
-      command.append("color ");
-      String colorSpec = ColorUtils.toTkCode(col);
-      if (chimeraX)
-      {
-        command.append(":").append(resName).append(" ").append(colorSpec);
-      }
-      else
-      {
-        command.append(colorSpec).append(" ::").append(resName);
-      }
-      command.append(";");
-    }
-
-    sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA);
-    viewerCommandHistory(true);
-  }
-
   /**
    * called when the binding thinks the UI needs to be refreshed after a Chimera
    * state change. this could be because structures were loaded, or because an
@@ -1089,23 +937,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Send the Chimera 'background solid <color>" command.
-   * 
-   * @see https
-   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
-   *      .html
-   * @param col
-   */
-  @Override
-  public void setBackgroundColour(Color col)
-  {
-    viewerCommandHistory(false);
-    String command = "set bgColor " + ColorUtils.toTkCode(col);
-    chimeraManager.sendChimeraCommand(command, false);
-    viewerCommandHistory(true);
-  }
-
-  /**
    * Ask Chimera to save its session to the given file. Returns true if
    * successful, else false.
    * 
@@ -1150,32 +981,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * Chimera:  https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/open.html
      * ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html
      */
-    sendChimeraCommand("open " + filepath, true);
+    executeCommand("open " + filepath, true);
     // todo: test for failure - how?
     return true;
   }
 
   /**
-   * Returns a list of chains mapped in this viewer. Note this list is not
-   * currently scoped per structure.
-   * 
-   * @return
-   */
-  @Override
-  public List<String> getChainNames()
-  {
-    return chainNames;
-  }
-
-  /**
-   * Send a 'focus' command to Chimera to recentre the visible display
-   */
-  public void focusView()
-  {
-    sendChimeraCommand(chimeraManager.isChimeraX() ? "view" : "focus", false);
-  }
-
-  /**
    * Send a 'show' command for all atoms in the currently selected columns
    * 
    * TODO: pull up to abstract structure viewer interface
@@ -1291,7 +1102,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     // fails for 'average.bfactor' (which is bad):
 
     String cmd = "list residues attr '" + attName + "'";
-    List<String> residues = sendChimeraCommand(cmd, true);
+    List<String> residues = executeCommand(cmd, true);
 
     boolean featureAdded = createFeaturesForAttributes(attName, residues);
     if (featureAdded)
@@ -1405,19 +1216,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     return CHIMERA_FEATURE_GROUP;
   }
 
-  public Hashtable<String, String> getChainFile()
-  {
-    return chainFile;
-  }
-
-  public List<ChimeraModel> getChimeraModelByChain(String chain)
-  {
-    return chimeraMaps.get(chainFile.get(chain));
-  }
-
-  public int getModelNoForChain(String chain)
+  @Override
+  public int getModelNoForFile(String pdbFile)
   {
-    List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
+    List<ChimeraModel> foundModels = chimeraMaps.get(pdbFile);
     if (foundModels != null && !foundModels.isEmpty())
     {
       return foundModels.get(0).getModelNumber();
index 83ec177..daf31fc 100644 (file)
@@ -42,9 +42,7 @@ import java.awt.event.ActionEvent;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
-import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JSplitPane;
 import javax.swing.SwingUtilities;
@@ -247,30 +245,12 @@ public class AppJmol extends StructureViewerBase
     {
       command = "";
     }
-    jmb.evalStateCommand(command);
-    jmb.evalStateCommand("set hoverDelay=0.1");
+    jmb.executeCommand(command, false);
+    jmb.executeCommand("set hoverDelay=0.1", false);
     jmb.setFinishedInit(true);
   }
 
   @Override
-  void showSelectedChains()
-  {
-    Vector<String> toshow = new Vector<>();
-    for (int i = 0; i < chainMenu.getItemCount(); i++)
-    {
-      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-      {
-        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-        if (item.isSelected())
-        {
-          toshow.addElement(item.getText());
-        }
-      }
-    }
-    jmb.centerViewer(toshow);
-  }
-
-  @Override
   public void closeViewer(boolean closeExternalViewer)
   {
     // Jmol does not use an external viewer
@@ -348,7 +328,7 @@ public class AppJmol extends StructureViewerBase
 
       try
       {
-        jmb.evalStateCommand(command);
+        jmb.executeCommand(command, false);
       } catch (OutOfMemoryError oomerror)
       {
         new OOMWarning("When trying to add structures to the Jmol viewer!",
index 22876a4..a92fe77 100644 (file)
@@ -48,7 +48,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JInternalFrame;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
@@ -339,27 +338,6 @@ public class ChimeraViewFrame extends StructureViewerBase
   }
 
   /**
-   * Show only the selected chain(s) in the viewer
-   */
-  @Override
-  void showSelectedChains()
-  {
-    List<String> toshow = new ArrayList<>();
-    for (int i = 0; i < chainMenu.getItemCount(); i++)
-    {
-      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-      {
-        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-        if (item.isSelected())
-        {
-          toshow.add(item.getText());
-        }
-      }
-    }
-    jmb.showChains(toshow);
-  }
-
-  /**
    * Close down this instance of Jalview's Chimera viewer, giving the user the
    * option to close the associated Chimera window (process). They may wish to
    * keep it open until they have had an opportunity to save any work.
@@ -604,7 +582,7 @@ public class ChimeraViewFrame extends StructureViewerBase
       String chid = new String(
               pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
       jmb.getChainNames().add(chid);
-      jmb.getChainFile().put(chid, file);
+      jmb.addChainFile(chid, file);
     }
   }
 
diff --git a/src/jalview/gui/ChimeraXViewFrame.java b/src/jalview/gui/ChimeraXViewFrame.java
new file mode 100644 (file)
index 0000000..de8820d
--- /dev/null
@@ -0,0 +1,28 @@
+package jalview.gui;
+
+import jalview.gui.StructureViewer.ViewerType;
+
+/**
+ * A class for the gui frame through which Jalview interacts with the ChimeraX
+ * structure viewer. Mostly the same as ChimeraViewFrame with a few overrides
+ * for the differences.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class ChimeraXViewFrame extends ChimeraViewFrame
+{
+
+  @Override
+  public ViewerType getViewerType()
+  {
+    return null;// ViewerType.CHIMERAX;
+  }
+
+  @Override
+  protected String getViewerName()
+  {
+    return "ChimeraX";
+  }
+
+}
index 31f24df..f9071ef 100644 (file)
@@ -101,8 +101,9 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
   protected void sendAsynchronousCommand(final String command,
           final String progressMsg)
   {
+    final JalviewStructureDisplayI theViewer = getViewer();
     final long handle = progressMsg == null ? 0
-            : getViewer().startProgressBar(progressMsg);
+            : theViewer.startProgressBar(progressMsg);
     SwingUtilities.invokeLater(new Runnable()
     {
       @Override
@@ -110,12 +111,12 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
       {
         try
         {
-          sendChimeraCommand(command, false);
+          executeCommand(command, false);
         } finally
         {
           if (progressMsg != null)
           {
-            getViewer().stopProgressBar(null, handle);
+            theViewer.stopProgressBar(null, handle);
           }
         }
       }
diff --git a/src/jalview/gui/JalviewChimeraXBindingModel.java b/src/jalview/gui/JalviewChimeraXBindingModel.java
new file mode 100644 (file)
index 0000000..e2aaa65
--- /dev/null
@@ -0,0 +1,18 @@
+package jalview.gui;
+
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.structure.StructureSelectionManager;
+
+public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel
+{
+
+  public JalviewChimeraXBindingModel(ChimeraViewFrame chimeraViewFrame,
+          StructureSelectionManager ssm, PDBEntry[] pdbentry,
+          SequenceI[][] sequenceIs, DataSourceType protocol)
+  {
+    super(chimeraViewFrame, ssm, pdbentry, sequenceIs, protocol);
+  }
+
+}
index 8a4b61c..d3e6d75 100644 (file)
@@ -560,8 +560,6 @@ public abstract class StructureViewerBase extends GStructureViewer
     }
   }
 
-  abstract void showSelectedChains();
-
   /**
    * Action on selecting one of Jalview's registered colour schemes
    */
@@ -572,7 +570,7 @@ public abstract class StructureViewerBase extends GStructureViewer
     ColourSchemeI cs = ColourSchemes.getInstance()
             .getColourScheme(colourSchemeName, getAlignmentPanel().av, al,
                     null);
-    getBinding().setJalviewColourScheme(cs);
+    getBinding().colourByJalviewColourScheme(cs);
   }
 
   /**
@@ -753,12 +751,6 @@ public abstract class StructureViewerBase extends GStructureViewer
     buildColourMenu();
   }
 
-  @Override
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    getBinding().setJalviewColourScheme(cs);
-  }
-
   /**
    * Sends commands to the structure viewer to superimpose structures based on
    * currently associated alignments. May optionally return an error message for
@@ -1093,4 +1085,24 @@ public abstract class StructureViewerBase extends GStructureViewer
     // default does nothing
   }
 
+  /**
+   * Show only the selected chain(s) in the viewer
+   */
+  protected void showSelectedChains()
+  {
+    List<String> toshow = new ArrayList<>();
+    for (int i = 0; i < chainMenu.getItemCount(); i++)
+    {
+      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+      {
+        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
+        if (item.isSelected())
+        {
+          toshow.add(item.getText());
+        }
+      }
+    }
+    getBinding().showChains(toshow);
+  }
+
 }
index 6071933..8d83e75 100644 (file)
@@ -30,7 +30,6 @@ import jalview.ext.jmol.JmolCommands;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.HttpUtils;
 
@@ -220,21 +219,22 @@ public class MouseOverStructureListener extends JSFunctionExec
 
       // Form a colour command from the given alignment panel for each distinct
       // structure
-      ArrayList<String[]> ccomands = new ArrayList<String[]>();
-      ArrayList<String> pdbfn = new ArrayList<String>();
-      StructureMappingcommandSet[] colcommands = JmolCommands
-              .getColourBySequenceCommand(ssm, modelSet, sequence, sr,
+      ArrayList<String[]> ccomands = new ArrayList<>();
+      ArrayList<String> pdbfn = new ArrayList<>();
+      String[] colcommands = new JmolCommands()
+              .colourBySequence(ssm, modelSet, sequence, sr,
                       (AlignmentViewPanel) source);
       if (colcommands == null)
       {
         return;
       }
       int sz = 0;
-      for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
+      // for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
+      for (String command : colcommands)
       {
-        sz += ccset.commands.length;
-        ccomands.add(ccset.commands);
-        pdbfn.add(ccset.mapping);
+        // sz += ccset.commands.length;
+        // ccomands.add(command); // ccset.commands);
+        // pdbfn.add(ccset.mapping);
       }
 
       String mclass, mhandle;
diff --git a/src/jalview/structure/StructureCommandsBase.java b/src/jalview/structure/StructureCommandsBase.java
new file mode 100644 (file)
index 0000000..321fc25
--- /dev/null
@@ -0,0 +1,12 @@
+package jalview.structure;
+
+/**
+ * A base class holding methods useful to all classes that implement commands
+ * for structure viewers
+ * 
+ * @author gmcarstairs
+ *
+ */
+public abstract class StructureCommandsBase implements StructureCommandsI
+{
+}
diff --git a/src/jalview/structure/StructureCommandsFactory.java b/src/jalview/structure/StructureCommandsFactory.java
new file mode 100644 (file)
index 0000000..9319427
--- /dev/null
@@ -0,0 +1,32 @@
+package jalview.structure;
+
+import jalview.ext.jmol.JmolCommands;
+import jalview.ext.rbvi.chimera.ChimeraCommands;
+import jalview.ext.rbvi.chimera.ChimeraXCommands;
+import jalview.gui.StructureViewer.ViewerType;
+
+/**
+ * A factory that serves a class that can generate structure commands for a
+ * specified structure viewer
+ */
+public class StructureCommandsFactory
+{
+  public StructureCommandsI getStructureCommands(ViewerType viewer)
+  {
+    StructureCommandsI commands = null;
+    switch (viewer)
+    {
+    case JMOL:
+      commands = new JmolCommands();
+      break;
+    case CHIMERA:
+      commands = new ChimeraCommands();
+      break;
+    case CHIMERAX:
+      commands = new ChimeraXCommands();
+      break;
+    default:
+    }
+    return commands;
+  }
+}
diff --git a/src/jalview/structure/StructureCommandsI.java b/src/jalview/structure/StructureCommandsI.java
new file mode 100644 (file)
index 0000000..a5a419c
--- /dev/null
@@ -0,0 +1,90 @@
+package jalview.structure;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.api.SequenceRenderer;
+import jalview.datamodel.SequenceI;
+
+import java.awt.Color;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Methods that generate commands that can be sent to a molecular structure
+ * viewer program (e.g. Jmol, Chimera, ChimeraX)
+ * 
+ * @author gmcarstairs
+ *
+ */
+public interface StructureCommandsI
+{
+
+  /**
+   * Returns the command to colour by chain
+   * 
+   * @return
+   */
+  String colourByChain();
+
+  /**
+   * Returns the command to colour residues using a charge-based scheme:
+   * <ul>
+   * <li>Aspartic acid and Glutamic acid (negative charge) red</li>
+   * <li>Lysine and Arginine (positive charge) blue</li>
+   * <li>Cysteine - yellow</li>
+   * <li>all others - white</li>
+   * </ul>
+   * 
+   * @return
+   */
+  String colourByCharge();
+
+  /**
+   * Returns the command to colour residues with the colours provided in the
+   * map, one per three letter residue code
+   * 
+   * @param colours
+   * @return
+   */
+  String colourByResidues(Map<String, Color> colours);
+
+  /**
+   * Returns the command to set the background colour of the structure viewer
+   * 
+   * @param col
+   * @return
+   */
+  String setBackgroundColour(Color col);
+
+  /**
+   * Returns commands to colour mapped residues of structures according to
+   * Jalview's colouring (including feature colouring if applied)
+   * 
+   * @param structureSelectionManager
+   * @param files
+   * @param seqs
+   * @param sr
+   * @param alignmentv
+   * @return
+   */
+  String[] colourBySequence(
+          StructureSelectionManager structureSelectionManager,
+          String[] files, SequenceI[][] seqs, SequenceRenderer sr,
+          AlignmentViewPanel alignmentv);
+
+  /**
+   * Returns a command to centre the display in the structure viewer
+   * 
+   * @return
+   */
+  String focusView();
+
+  /**
+   * Returns a command to show only the selected chains. The items in the input
+   * list should be formatted as "modelno:chainid".
+   * 
+   * @param toShow
+   * @return
+   */
+  String showChains(List<String> toShow);
+
+}
index b4e9dd2..8c2dc46 100644 (file)
@@ -31,10 +31,11 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommandsI;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
@@ -43,7 +44,11 @@ import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import javax.swing.SwingUtilities;
 
 /**
  * 
@@ -58,15 +63,33 @@ public abstract class AAStructureBindingModel
         extends SequenceStructureBindingModel
         implements StructureListener, StructureSelectionManagerProvider
 {
+  private static final String COLOURING_STRUCTURES = MessageManager
+          .getString("status.colouring_structures");
+
   /*
    * the Jalview panel through which the user interacts
    * with the structure viewer
    */
   private JalviewStructureDisplayI viewer;
 
+  /*
+   * helper that generates command syntax
+   */
+  private StructureCommandsI commandGenerator;
+
   private StructureSelectionManager ssm;
 
   /*
+   * modelled chains, formatted as "pdbid:chainCode"
+   */
+  private List<String> chainNames;
+
+  /*
+   * lookup of pdb file name by key "pdbid:chainCode"
+   */
+  private Map<String, String> chainFile;
+
+  /*
    * distinct PDB entries (pdb files) associated
    * with sequences
    */
@@ -140,6 +163,8 @@ public abstract class AAStructureBindingModel
   {
     this.ssm = ssm;
     this.sequence = seqs;
+    chainNames = new ArrayList<>();
+    chainFile = new HashMap<>();
   }
 
   /**
@@ -154,8 +179,7 @@ public abstract class AAStructureBindingModel
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
           DataSourceType protocol)
   {
-    this.ssm = ssm;
-    this.sequence = sequenceIs;
+    this(ssm, sequenceIs);
     this.nucleotide = Comparison.isNucleotide(sequenceIs);
     this.pdbEntry = pdbentry;
     this.protocol = protocol;
@@ -738,11 +762,15 @@ public abstract class AAStructureBindingModel
   }
 
   /**
-   * Returns a list of chains mapped in this viewer.
+   * Returns a list of chains mapped in this viewer, formatted as
+   * "pdbid:chainCode"
    * 
    * @return
    */
-  public abstract List<String> getChainNames();
+  public List<String> getChainNames()
+  {
+    return chainNames;
+  }
 
   /**
    * Returns the Jalview panel hosting the structure viewer (if any)
@@ -759,8 +787,6 @@ public abstract class AAStructureBindingModel
     viewer = v;
   }
 
-  public abstract void setJalviewColourScheme(ColourSchemeI cs);
-
   /**
    * Constructs and sends a command to align structures against a reference
    * structure, based on one or more sequence alignments. May optionally return
@@ -780,11 +806,6 @@ public abstract class AAStructureBindingModel
   public abstract String superposeStructures(AlignmentI[] alignments,
           int[] structureIndices, HiddenColumns[] hiddenCols);
 
-  public abstract void setBackgroundColour(Color col);
-
-  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel avp);
-
   /**
    * returns the current sequenceRenderer that should be used to colour the
    * structures
@@ -796,12 +817,174 @@ public abstract class AAStructureBindingModel
   public abstract SequenceRenderer getSequenceRenderer(
           AlignmentViewPanel alignment);
 
-  protected abstract void colourBySequence(
-          StructureMappingcommandSet[] colourBySequenceCommands);
+  /**
+   * Sends a command to the structure viewer to colour each chain with a
+   * distinct colour (to the extent supported by the viewer)
+   */
+  public void colourByChain()
+  {
+    colourBySequence = false;
+
+    // TODO: JAL-628 colour chains distinctly across all visible models
+
+    executeCommand(commandGenerator.colourByChain(), false,
+            COLOURING_STRUCTURES);
+  }
+
+  /**
+   * Sends a command to the structure viewer to colour each chain with a
+   * distinct colour (to the extent supported by the viewer)
+   */
+  public void colourByCharge()
+  {
+    colourBySequence = false;
+
+    executeCommand(commandGenerator.colourByCharge(), false,
+            COLOURING_STRUCTURES);
+  }
+
+  /**
+   * Sends a command to the structure to apply a colour scheme (defined in
+   * Jalview but not necessarily applied to the alignment), which defines a
+   * colour per residue letter. More complex schemes (e.g. that depend on
+   * consensus) cannot be used here and are ignored.
+   * 
+   * @param cs
+   */
+  public void colourByJalviewColourScheme(ColourSchemeI cs)
+  {
+    colourBySequence = false;
+
+    if (cs == null || !cs.isSimple())
+    {
+      return;
+    }
+    
+    /*
+     * build a map of {Residue3LetterCode, Color}
+     */
+    Map<String, Color> colours = new HashMap<>();
+    List<String> residues = ResidueProperties.getResidues(isNucleotide(),
+            false);
+    for (String resName : residues)
+    {
+      char res = resName.length() == 3
+              ? ResidueProperties.getSingleCharacterCode(resName)
+              : resName.charAt(0);
+      Color colour = cs.findColour(res, 0, null, null, 0f);
+      colours.put(resName, colour);
+    }
+
+    /*
+     * pass to the command constructor, and send the command
+     */
+    String cmd = commandGenerator.colourByResidues(colours);
+    executeCommand(cmd, false, COLOURING_STRUCTURES);
+  }
+
+  public void setBackgroundColour(Color col)
+  {
+    String cmd = commandGenerator.setBackgroundColour(col);
+    executeCommand(cmd, false, null);
+  }
+
+  /**
+   * Sends one command to the structure viewer. If {@code getReply} is true, the
+   * command is sent synchronously, otherwise in a deferred thread.
+   * <p>
+   * If a progress message is supplied, this is displayed before command
+   * execution, and removed afterwards.
+   * 
+   * @param cmd
+   * @param getReply
+   * @param msg
+   * @return
+   */
+  private List<String> executeCommand(String cmd, boolean getReply,
+          String msg)
+  {
+    if (getReply)
+    {
+      return executeSynchronous(cmd, msg, getReply);
+    }
+    else
+    {
+      executeAsynchronous(cmd, msg);
+      return null;
+    }
+  }
+
+  /**
+   * Sends the command in the current thread. If a message is supplied, this is
+   * shown before the thread is started, and removed when it completes. May
+   * return a reply to the command if requested.
+   * 
+   * @param cmd
+   * @param msg
+   * @param getReply
+   * @return
+   */
+  private List<String> executeSynchronous(String cmd, String msg, boolean getReply)
+  {
+    final JalviewStructureDisplayI theViewer = getViewer();
+    final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
+    try
+    {
+      return executeCommand(cmd, getReply);
+    } finally
+    {
+      if (msg != null)
+      {
+        theViewer.stopProgressBar(null, handle);
+      }
+    }
+  }
+
+  /**
+   * Sends the command in a separate thread. If a message is supplied, this is
+   * shown before the thread is started, and removed when it completes. No value
+   * is returned.
+   * 
+   * @param cmd
+   * @param msg
+   */
+  private void executeAsynchronous(String cmd, String msg)
+  {
+    final JalviewStructureDisplayI theViewer = getViewer();
+    final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
 
-  public abstract void colourByChain();
+    SwingUtilities.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          executeCommand(cmd, false);
+        } finally
+        {
+          if (msg != null)
+          {
+            theViewer.stopProgressBar(null, handle);
+          }
+        }
+      }
+    });
+  }
+
+  protected abstract List<String> executeCommand(String command,
+          boolean getReply);
 
-  public abstract void colourByCharge();
+  protected List<String> executeCommands(boolean getReply,
+          String... commands)
+  {
+    List<String> response = null;
+    for (String cmd : commands)
+    {
+      response = executeCommand(cmd, getReply);
+    }
+    return response;
+  }
 
   /**
    * colour any structures associated with sequences in the given alignment
@@ -822,11 +1005,60 @@ public abstract class AAStructureBindingModel
 
     SequenceRenderer sr = getSequenceRenderer(alignmentv);
 
-    StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
-            files, sr, alignmentv);
-    colourBySequence(colourBySequenceCommands);
+    String[] colourBySequenceCommands = commandGenerator
+            .colourBySequence(getSsm(), files, getSequence(), sr,
+                    alignmentv);
+    executeCommands(false, colourBySequenceCommands);
+  }
+
+  /**
+   * Centre the display in the structure viewer
+   */
+  public void focusView()
+  {
+    executeCommand(commandGenerator.focusView(), false);
+  }
+
+  /**
+   * Generates and executes a command to show only specified chains in the
+   * structure viewer. The list of chains to show should contain entries
+   * formatted as "pdbid:chaincode".
+   * 
+   * @param toShow
+   */
+  public void showChains(List<String> toShow)
+  {
+    // todo or reformat toShow list entries as modelNo:pdbId:chainCode ?
+
+    /*
+     * Reformat the pdbid:chainCode values as modelNo:chainCode
+     * since this is what is needed to construct the viewer command
+     * todo: find a less messy way to do this
+     */
+    List<String> showThese = new ArrayList<>();
+    for (String chainId : toShow)
+    {
+      String[] tokens = chainId.split("\\:");
+      if (tokens.length == 2)
+      {
+        String pdbFile = getFileForChain(chainId);
+        int modelNo = getModelNoForFile(pdbFile);
+        String model = modelNo == -1 ? "" : String.valueOf(modelNo);
+        showThese.add(model + ":" + tokens[1]);
+      }
+    }
+    executeCommand(commandGenerator.showChains(showThese), false);
   }
 
+  /**
+   * Answers the structure viewer's model number given a PDB file name. Returns
+   * -1 if model number is not found.
+   * 
+   * @param chainId
+   * @return
+   */
+  protected abstract int getModelNoForFile(String chainId);
+
   public boolean hasFileLoadingError()
   {
     return fileLoadingError != null && fileLoadingError.length() > 0;
@@ -847,4 +1079,33 @@ public abstract class AAStructureBindingModel
             ? ap.getFeatureRenderer()
             : null;
   }
+
+  protected void setStructureCommands(StructureCommandsI cmd)
+  {
+    commandGenerator = cmd;
+  }
+
+  /**
+   * Records association of one chain id (formatted as "pdbid:chainCode") with
+   * the corresponding PDB file name
+   * 
+   * @param chainId
+   * @param fileName
+   */
+  public void addChainFile(String chainId, String fileName)
+  {
+    chainFile.put(chainId, fileName);
+  }
+
+  /**
+   * Returns the PDB filename for the given chain id (formatted as
+   * "pdbid:chainCode"), or null if not found
+   * 
+   * @param chainId
+   * @return
+   */
+  protected String getFileForChain(String chainId)
+  {
+    return chainFile.get(chainId);
+  }
 }
index e42b54f..5846b33 100644 (file)
@@ -33,7 +33,6 @@ import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
 import jalview.schemes.JalviewColourScheme;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
 import java.util.HashMap;
@@ -65,8 +64,9 @@ public class JmolCommandsTest
 
     // need some mappings!
 
-    StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+    String[] commands = new JmolCommands().colourBySequence(ssm, files,
+            seqs, sr, af.alignPanel);
+    assertEquals(commands.length, 0);
   }
 
   @Test(groups = { "Functional" })
@@ -91,11 +91,11 @@ public class JmolCommandsTest
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
     StructureSelectionManager ssm = new StructureSelectionManager();
-  
+
     /*
      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
      */
-    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    HashMap<Integer, int[]> map = new HashMap<>();
     for (int pos = 1; pos <= seq1.getLength(); pos++)
     {
       map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
@@ -106,37 +106,36 @@ public class JmolCommandsTest
     StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
             "B", map, null);
     ssm.addStructureMapping(sm2);
-  
-    StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+
+    String[] commands = new JmolCommands().colourBySequence(ssm, files,
+            seqs, sr, af.alignPanel);
     assertEquals(commands.length, 2);
-    assertEquals(commands[0].commands.length, 1);
 
-    String chainACommand = commands[0].commands[0];
+    String chainACommand = commands[0];
     // M colour is #82827d == (130, 130, 125) (see strand.html help page)
-    assertTrue(chainACommand
-            .contains("select 21:A/1.1;color[130,130,125]")); // first one
+    assertTrue(
+            chainACommand.contains("select 21:A/1.1;color[130,130,125]")); // first
+                                                                           // one
     // H colour is #60609f == (96, 96, 159)
     assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]"));
     // hidden columns are Gray (128, 128, 128)
     assertTrue(chainACommand
             .contains(";select 23-25:A/1.1;color[128,128,128]"));
     // S and G are both coloured #4949b6 == (73, 73, 182)
-    assertTrue(chainACommand
-            .contains(";select 26-30:A/1.1;color[73,73,182]"));
+    assertTrue(
+            chainACommand.contains(";select 26-30:A/1.1;color[73,73,182]"));
 
-    String chainBCommand = commands[1].commands[0];
+    String chainBCommand = commands[1];
     // M colour is #82827d == (130, 130, 125)
-    assertTrue(chainBCommand
-            .contains("select 21:B/2.1;color[130,130,125]"));
+    assertTrue(
+            chainBCommand.contains("select 21:B/2.1;color[130,130,125]"));
     // V colour is #ffff00 == (255, 255, 0)
-    assertTrue(chainBCommand
-.contains(";select 22:B/2.1;color[255,255,0]"));
+    assertTrue(chainBCommand.contains(";select 22:B/2.1;color[255,255,0]"));
     // hidden columns are Gray (128, 128, 128)
     assertTrue(chainBCommand
             .contains(";select 23-25:B/2.1;color[128,128,128]"));
     // S and G are both coloured #4949b6 == (73, 73, 182)
-    assertTrue(chainBCommand
-            .contains(";select 26-30:B/2.1;color[73,73,182]"));
+    assertTrue(
+            chainBCommand.contains(";select 26-30:B/2.1;color[73,73,182]"));
   }
 }
index 8e68d86..6a02576 100644 (file)
@@ -29,11 +29,9 @@ import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
-import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
 import jalview.schemes.JalviewColourScheme;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
@@ -42,19 +40,11 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class ChimeraCommandsTest
 {
 
-  @BeforeClass(alwaysRun = true)
-  public void setUpJvOptionPane()
-  {
-    JvOptionPane.setInteractiveMode(false);
-    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-  }
-
   @Test(groups = { "Functional" })
   public void testBuildColourCommands()
   {
@@ -72,7 +62,7 @@ public class ChimeraCommandsTest
 
     // Colours should appear in the Chimera command in the order in which
     // they were added; within colour, by model, by chain, ranges in start order
-    String command = ChimeraCommands.buildColourCommands(map, false).get(0);
+    String command = new ChimeraCommands().buildColourCommands(map).get(0);
     assertEquals(
             command,
             "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A");
@@ -101,7 +91,7 @@ public class ChimeraCommandsTest
      * feature name gets a jv_ namespace prefix
      * feature value is quoted in case it contains spaces
      */
-    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:8-20.A");
+    assertEquals(commands.get(0), "setattr res jv_chain 'X' #0:8-20.A");
 
     // add same feature value, overlapping range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A");
@@ -110,7 +100,7 @@ public class ChimeraCommandsTest
     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap,
             false);
     assertEquals(1, commands.size());
-    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A");
+    assertEquals(commands.get(0), "setattr res jv_chain 'X' #0:3-25.A");
 
     // same feature value and model, different chain
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B");
@@ -120,7 +110,7 @@ public class ChimeraCommandsTest
             false);
     assertEquals(1, commands.size());
     assertEquals(commands.get(0),
-            "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
+            "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
 
     // same feature, different value
     ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A");
@@ -130,8 +120,9 @@ public class ChimeraCommandsTest
     // commands are ordered by feature type but not by value
     // so use contains to test for the expected command:
     assertTrue(commands
-            .contains("setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
-    assertTrue(commands.contains("setattr r jv_chain 'Y' #0:40-50.A"));
+            .contains(
+                    "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
+    assertTrue(commands.contains("setattr res jv_chain 'Y' #0:40-50.A"));
 
     featuresMap.clear();
     featureValues.clear();
@@ -144,7 +135,8 @@ public class ChimeraCommandsTest
     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap,
             false);
     assertTrue(commands
-            .contains("setattr r jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
+            .contains(
+                    "setattr res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
   }
 
   /**
@@ -205,12 +197,10 @@ public class ChimeraCommandsTest
             "B", map, null);
     ssm.addStructureMapping(sm2);
 
-    StructureMappingcommandSet[] commands = ChimeraCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel,
-                    false);
+    String[] commands = new ChimeraCommands()
+            .colourBySequence(ssm, files, seqs, sr, af.alignPanel);
     assertEquals(1, commands.length);
-    assertEquals(1, commands[0].commands.length);
-    String theCommand = commands[0].commands[0];
+    String theCommand = commands[0];
     // M colour is #82827d (see strand.html help page)
     assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B"));
     // H colour is #60609f
index 734f7eb..725e15b 100644 (file)
@@ -290,7 +290,7 @@ public class JalviewChimeraView
     /*
      * ask Chimera for its residue attribute names
      */
-    List<String> reply = binding.sendChimeraCommand("list resattr", true);
+    List<String> reply = binding.executeCommand("list resattr", true);
     // prefixed and sanitised attribute names for Jalview features:
     assertTrue(reply.contains("resattr jv_domain"));
     assertTrue(reply.contains("resattr jv_metal_ion_binding_site"));
@@ -306,7 +306,7 @@ public class JalviewChimeraView
      * ask Chimera for residues with an attribute
      * 91 and 96 on sequence --> residues 40 and 45 on chains A and B
      */
-    reply = binding.sendChimeraCommand(
+    reply = binding.executeCommand(
             "list resi att jv_metal_ion_binding_site", true);
     assertEquals(reply.size(), 4);
     assertTrue(reply
@@ -322,7 +322,7 @@ public class JalviewChimeraView
      * check attributes with score values
      * sequence positions 62 and 65 --> residues 11 and 14 on chains A and B
      */
-    reply = binding.sendChimeraCommand("list resi att jv_kd", true);
+    reply = binding.executeCommand("list resi att jv_kd", true);
     assertEquals(reply.size(), 4);
     assertTrue(reply.contains("residue id #0:11.A jv_kd -2.1 index 11"));
     assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
@@ -332,7 +332,7 @@ public class JalviewChimeraView
     /*
      * list residues with positive kd score 
      */
-    reply = binding.sendChimeraCommand(
+    reply = binding.executeCommand(
             "list resi spec :*/jv_kd>0 attr jv_kd", true);
     assertEquals(reply.size(), 2);
     assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
index 286be1b..b86e91f 100644 (file)
@@ -124,10 +124,10 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     acf3.addMap(new Sequence("s3", "ttt"), new Sequence("p3", "p"),
             new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 1, 1));
 
-    List<AlignedCodonFrame> set1 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> set1 = new ArrayList<>();
     set1.add(acf1);
     set1.add(acf2);
-    List<AlignedCodonFrame> set2 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> set2 = new ArrayList<>();
     set2.add(acf2);
     set2.add(acf3);
 
@@ -218,7 +218,7 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     assertEquals(1, pmap.getSeqs().size());
     assertEquals("4IM2|A", pmap.getSeqs().get(0).getName());
 
-    List<int[]> structuremap1 = new ArrayList(
+    List<int[]> structuremap1 = new ArrayList<>(
             sm.getMapping(P4IM2_MISSING)[0]
                     .getPDBResNumRanges(seq.getStart(), seq.getEnd()));
 
@@ -313,8 +313,7 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     // positional mapping to atoms for color by structure is still wrong, even
     // though panel looks correct.
 
-    StructureMappingcommandSet smcr[] = JmolCommands
-            .getColourBySequenceCommand(apssm,
+    String[] smcr = new JmolCommands().colourBySequence(apssm,
             new String[]
             { pdbe.getFile() },
             new SequenceI[][]
@@ -322,12 +321,10 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
                     new SequenceRenderer(alf.alignPanel.getAlignViewport()),
                     alf.alignPanel);
     // Expected - all residues are white
-    for (StructureMappingcommandSet smm : smcr)
+    for (String c : smcr)
     {
-      for (String c : smm.commands)
-      {
-        System.out.println(c);
-      }
+      assertTrue(c.contains("color[255,255,255]"));
+      System.out.println(c);
     }
   }
 
index 2e2a9f2..c890536 100644 (file)
@@ -36,13 +36,10 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormats;
-import jalview.schemes.ColourSchemeI;
 import jalview.structure.AtomSpec;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel.SuperposeData;
 
-import java.awt.Color;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -138,21 +135,16 @@ public class AAStructureBindingModelTest
       @Override
       public void updateColours(Object source)
       {
-        // TODO Auto-generated method stub
-        
       }
       
       @Override
       public void releaseReferences(Object svl)
       {
-        // TODO Auto-generated method stub
-        
       }
       
       @Override
       public String[] getStructureFiles()
       {
-        // TODO Auto-generated method stub
         return null;
       }
       
@@ -160,73 +152,31 @@ public class AAStructureBindingModelTest
       public String superposeStructures(AlignmentI[] alignments,
               int[] structureIndices, HiddenColumns[] hiddenCols)
       {
-        // TODO Auto-generated method stub
         return null;
       }
       
       @Override
-      public void setJalviewColourScheme(ColourSchemeI cs)
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
-      public void setBackgroundColour(Color col)
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
       public void highlightAtoms(List<AtomSpec> atoms)
       {
-        // TODO Auto-generated method stub
-        
       }
       
       @Override
       public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
       {
-        // TODO Auto-generated method stub
-        return null;
-      }
-      
-      @Override
-      protected StructureMappingcommandSet[] getColourBySequenceCommands(
-              String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
-      {
-        // TODO Auto-generated method stub
         return null;
       }
-      
+
       @Override
-      public List<String> getChainNames()
+      protected List<String> executeCommand(String command,
+              boolean getReply)
       {
-        // TODO Auto-generated method stub
         return null;
       }
-      
-      @Override
-      protected void colourBySequence(
-              StructureMappingcommandSet[] colourBySequenceCommands)
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
-      public void colourByCharge()
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
+
       @Override
-      public void colourByChain()
+      protected int getModelNoForFile(String chainId)
       {
-        // TODO Auto-generated method stub
-        
+        return 0;
       }
     };
     String[][] chains = binder.getChains();
@@ -297,17 +247,6 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      public List<String> getChainNames()
-      {
-        return null;
-      }
-
-      @Override
-      public void setJalviewColourScheme(ColourSchemeI cs)
-      {
-      }
-
-      @Override
       public String superposeStructures(AlignmentI[] als, int[] alm,
               HiddenColumns[] alc)
       {
@@ -315,18 +254,6 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      public void setBackgroundColour(Color col)
-      {
-      }
-
-      @Override
-      protected StructureMappingcommandSet[] getColourBySequenceCommands(
-              String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
-      {
-        return null;
-      }
-
-      @Override
       public SequenceRenderer getSequenceRenderer(
               AlignmentViewPanel alignment)
       {
@@ -334,19 +261,16 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      protected void colourBySequence(
-              StructureMappingcommandSet[] colourBySequenceCommands)
-      {
-      }
-
-      @Override
-      public void colourByChain()
+      protected List<String> executeCommand(String command,
+              boolean getReply)
       {
+        return null;
       }
 
       @Override
-      public void colourByCharge()
+      protected int getModelNoForFile(String chainId)
       {
+        return 0;
       }
     };
   }