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