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