JAL-3390 unit tests and command and menu refinements
[jalview.git] / src / jalview / ext / pymol / PymolCommands.java
1 package jalview.ext.pymol;
2
3 import java.awt.Color;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.Map;
7
8 import jalview.structure.AtomSpecModel;
9 import jalview.structure.StructureCommand;
10 import jalview.structure.StructureCommandI;
11 import jalview.structure.StructureCommandsBase;
12
13 /**
14  * A class that generates commands to send to PyMol over its XML-RPC interface.
15  * <p>
16  * Note that because the xml-rpc interface can only accept one command at a
17  * time, we can't concatenate commands, and must instead form and send them
18  * individually.
19  * 
20  * @see https://pymolwiki.org/index.php/Category:Commands
21  * @see https://pymolwiki.org/index.php/RPC
22  */
23 public class PymolCommands extends StructureCommandsBase
24 {
25   private static final StructureCommand SHOW_RIBBON = new StructureCommand("show", "ribbon");
26
27   private static final StructureCommand SHOW_CARTOON = new StructureCommand("show", "cartoon");
28
29   private static final StructureCommand HIDE_EVERYTHING = new StructureCommand("hide", "everything");
30
31   private static final StructureCommand COLOUR_BY_CHAIN = new StructureCommand("spectrum", "chain");
32
33   private static final List<StructureCommandI> COLOR_BY_CHARGE = new ArrayList<>();
34
35   private static final List<StructureCommandI> SHOW_BACKBONE = new ArrayList<>();
36
37   static {
38     COLOR_BY_CHARGE.add(new StructureCommand("color", "white", "*"));
39     COLOR_BY_CHARGE
40             .add(new StructureCommand("color", "red", "resn ASP resn GLU"));
41     COLOR_BY_CHARGE.add(
42             new StructureCommand("color", "blue", "resn LYS resn ARG"));
43     COLOR_BY_CHARGE
44             .add(new StructureCommand("color", "yellow", "resn CYS"));
45     SHOW_BACKBONE.add(HIDE_EVERYTHING);
46     SHOW_BACKBONE.add(SHOW_RIBBON);
47   }
48
49   @Override
50   public StructureCommandI colourByChain()
51   {
52     return COLOUR_BY_CHAIN;
53   }
54
55   @Override
56   public List<StructureCommandI> colourByCharge()
57   {
58     return COLOR_BY_CHARGE;
59   }
60
61   @Override
62   public StructureCommandI setBackgroundColour(Color col)
63   {
64     // https://pymolwiki.org/index.php/Bg_Color
65     return new StructureCommand("bg_color", getColourString(col));
66   }
67
68   /**
69    * Returns a colour formatted suitable for use in viewer command syntax. For
70    * example, red is {@code "0xff0000"}.
71    * 
72    * @param c
73    * @return
74    */
75   protected String getColourString(Color c)
76   {
77     return String.format("0x%02x%02x%02x", c.getRed(), c.getGreen(),
78             c.getBlue());
79   }
80
81   @Override
82   public StructureCommandI focusView()
83   {
84     // TODO what?
85     return null;
86   }
87
88   @Override
89   public List<StructureCommandI> superposeStructures(AtomSpecModel refAtoms,
90           AtomSpecModel atomSpec)
91   {
92     // https://pymolwiki.org/index.php/Super
93     List<StructureCommandI> commands = new ArrayList<>();
94     String refAtomsAlphaOnly = getAtomSpec(refAtoms, true);
95     String atomSpec2AlphaOnly = getAtomSpec(atomSpec, true);
96     commands.add(new StructureCommand("super", refAtomsAlphaOnly,
97             atomSpec2AlphaOnly));
98
99     /*
100      * and show superposed residues as cartoon
101      */
102     String refAtomsAll = getAtomSpec(refAtoms, false);
103     String atomSpec2All = getAtomSpec(atomSpec, false);
104     commands.add(new StructureCommand("show", "cartoon",
105             refAtomsAll + " " + atomSpec2All));
106
107     return commands;
108   }
109
110   @Override
111   public StructureCommandI openCommandFile(String path)
112   {
113     // https://pymolwiki.org/index.php/Run
114     return new StructureCommand("run", path); // should be .pml
115   }
116
117   @Override
118   public StructureCommandI saveSession(String filepath)
119   {
120     // https://pymolwiki.org/index.php/Save#EXAMPLES
121     return new StructureCommand("save", filepath); // should be .pse
122   }
123
124   /**
125    * Returns a selection string in PyMOL 'selection macro' format:
126    * 
127    * <pre>
128    * modelId// chain/residues/
129    * </pre>
130    * 
131    * If more than one chain, makes a selection expression for each, and they are
132    * separated by spaces.
133    * 
134    * @see https://pymolwiki.org/index.php/Selection_Macros
135    */
136   @Override
137   public String getAtomSpec(AtomSpecModel model, boolean alphaOnly)
138   {
139     StringBuilder sb = new StringBuilder(64);
140     boolean first = true;
141     for (String modelId : model.getModels())
142     {
143       for (String chain : model.getChains(modelId))
144       {
145         if (!first)
146         {
147           sb.append(" ");
148         }
149         first = false;
150         List<int[]> rangeList = model.getRanges(modelId, chain);
151         chain = chain.trim();
152         sb.append(modelId).append("//").append(chain).append("/");
153         boolean firstRange = true;
154         for (int[] range : rangeList)
155         {
156           if (!firstRange)
157           {
158             sb.append("+");
159           }
160           firstRange = false;
161           sb.append(String.valueOf(range[0]));
162           if (range[0] != range[1])
163           {
164             sb.append("-").append(String.valueOf(range[1]));
165           }
166         }
167         sb.append("/");
168         if (alphaOnly)
169         {
170           sb.append("CA");
171         }
172       }
173     }
174     return sb.toString();
175   }
176
177   @Override
178   public List<StructureCommandI> showBackbone()
179   {
180     return SHOW_BACKBONE;
181   }
182
183   @Override
184   protected StructureCommandI colourResidues(String atomSpec, Color colour)
185   {
186     // https://pymolwiki.org/index.php/Color
187     return new StructureCommand("color", getColourString(colour), atomSpec);
188   }
189
190   @Override
191   protected String getResidueSpec(String residue)
192   {
193     // https://pymolwiki.org/index.php/Selection_Algebra
194     return "resn " + residue;
195   }
196
197   @Override
198   public StructureCommandI loadFile(String file)
199   {
200     return new StructureCommand("load", file);
201   }
202
203   /**
204    * Overrides the default implementation (which generates concatenated
205    * commands) to generate one per colour (because the XML-RPC interface to
206    * PyMOL only accepts one command at a time)
207    * 
208    * @param colourMap
209    * @return
210    */
211   @Override
212   public List<StructureCommandI> colourBySequence(
213           Map<Object, AtomSpecModel> colourMap)
214   {
215     List<StructureCommandI> commands = new ArrayList<>();
216     for (Object key : colourMap.keySet())
217     {
218       Color colour = (Color) key;
219       final AtomSpecModel colourData = colourMap.get(colour);
220       commands.add(getColourCommand(colourData, colour));
221     }
222   
223     return commands;
224   }
225
226   /**
227    * Returns a viewer command to set the given atom property value on atoms
228    * specified by the AtomSpecModel, for example
229    * 
230    * <pre>
231    * iterate 4zho//B/12-34,48-55/CA,jv_chain='primary'
232    * </pre>
233    * 
234    * @param attributeName
235    * @param attributeValue
236    * @param atomSpecModel
237    * @return
238    */
239   protected StructureCommandI setAttribute(String attributeName,
240           String attributeValue,
241           AtomSpecModel atomSpecModel)
242   {
243     StringBuilder sb = new StringBuilder(128);
244     sb.append("p.").append(attributeName).append("='")
245             .append(attributeValue).append("'");
246     String atomSpec = getAtomSpec(atomSpecModel, false);
247     return new StructureCommand("iterate", atomSpec, sb.toString());
248   }
249
250   /**
251    * Traverse the map of features/values/models/chains/positions to construct a
252    * list of 'set property' commands (one per distinct feature type and value).
253    * The values are stored in the 'p' dictionary of user-defined properties of
254    * each atom.
255    * <p>
256    * The format of each command is
257    * 
258    * <pre>
259    * <blockquote> iterate atomspec, p.featureName='value' 
260    * e.g. iterate 4zho//A/23,28-29/CA, p.jv_Metal='Fe'
261    * </blockquote>
262    * </pre>
263    * 
264    * @param featureMap
265    * @return
266    */
267   @Override
268   public List<StructureCommandI> setAttributes(
269           Map<String, Map<Object, AtomSpecModel>> featureMap)
270   {
271     List<StructureCommandI> commands = new ArrayList<>();
272     for (String featureType : featureMap.keySet())
273     {
274       String attributeName = makeAttributeName(featureType);
275   
276       /*
277        * todo: clear down existing attributes for this feature?
278        */
279       // commands.add(new StructureCommand("iterate", "all",
280       // "p."+attributeName+"='None'"); //?
281   
282       Map<Object, AtomSpecModel> values = featureMap.get(featureType);
283       for (Object value : values.keySet())
284       {
285         /*
286          * for each distinct value recorded for this feature type,
287          * add a command to set the attribute on the mapped residues
288          * Put values in single quotes, encoding any embedded single quotes
289          */
290         AtomSpecModel atomSpecModel = values.get(value);
291         String featureValue = value.toString();
292         featureValue = featureValue.replaceAll("\\'", "&#39;");
293         StructureCommandI cmd = setAttribute(attributeName, featureValue,
294                 atomSpecModel);
295         commands.add(cmd);
296       }
297     }
298   
299     return commands;
300   }
301
302   @Override
303   public StructureCommandI openSession(String filepath)
304   {
305     // https://pymolwiki.org/index.php/Load
306     // this version of the command has no dependency on file extension
307     return new StructureCommand("load", filepath, "", "0", "pse");
308   }
309
310   @Override
311   public StructureCommandI showStructures(AtomSpecModel restrictTo)
312   {
313     if (restrictTo == null)
314     {
315       return SHOW_CARTOON;
316     }
317     else
318     {
319       return new StructureCommand("show", "cartoon",
320               getAtomSpec(restrictTo, false));
321     }
322   }
323
324   @Override
325   public StructureCommandI hideChain(String modelId, String chainId)
326   {
327     return new StructureCommand("hide", modelId + "//" + chainId + "//");
328   }
329
330   @Override
331   public StructureCommandI hideAll()
332   {
333     return HIDE_EVERYTHING;
334   }
335
336 }