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