Merge branch 'JAL-3878_ws-overhaul-3' into with_ws_overhaul-3
[jalview.git] / src / jalview / gui / PymolBindingModel.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.gui;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import jalview.api.AlignmentViewPanel;
29 import jalview.bin.Console;
30 import jalview.datamodel.PDBEntry;
31 import jalview.datamodel.SequenceI;
32 import jalview.ext.pymol.PymolCommands;
33 import jalview.ext.pymol.PymolManager;
34 import jalview.gui.StructureViewer.ViewerType;
35 import jalview.structure.AtomSpec;
36 import jalview.structure.AtomSpecModel;
37 import jalview.structure.StructureCommand;
38 import jalview.structure.StructureCommandI;
39 import jalview.structure.StructureSelectionManager;
40 import jalview.structures.models.AAStructureBindingModel;
41
42 public class PymolBindingModel extends AAStructureBindingModel
43 {
44   /*
45    * format for labels shown on structures when mousing over sequence;
46    * see https://pymolwiki.org/index.php/Label#examples
47    * left not final so customisable e.g. with a Groovy script
48    */
49   private static String LABEL_FORMAT = "\"%s %s\" % (resn,resi)";
50
51   private PymolManager pymolManager;
52
53   /*
54    * full paths to structure files opened in PyMOL
55    */
56   List<String> structureFiles = new ArrayList<>();
57
58   /*
59    * lookup from file path to PyMOL object name
60    */
61   Map<String, String> pymolObjects = new HashMap<>();
62
63   private String lastLabelSpec;
64
65   /**
66    * Constructor
67    * 
68    * @param viewer
69    * @param ssm
70    * @param pdbentry
71    * @param sequenceIs
72    */
73   public PymolBindingModel(StructureViewerBase viewer,
74           StructureSelectionManager ssm, PDBEntry[] pdbentry,
75           SequenceI[][] sequenceIs)
76   {
77     super(ssm, pdbentry, sequenceIs, null);
78     pymolManager = new PymolManager();
79     setStructureCommands(new PymolCommands());
80     setViewer(viewer);
81   }
82
83   @Override
84   public String[] getStructureFiles()
85   {
86     return structureFiles.toArray(new String[structureFiles.size()]);
87   }
88
89   @Override
90   public void highlightAtoms(List<AtomSpec> atoms)
91   {
92     /*
93      * https://pymolwiki.org/index.php/indicate#examples
94      */
95     StringBuilder sb = new StringBuilder();
96     for (AtomSpec atom : atoms)
97     {
98       // todo promote to StructureCommandsI.showLabel()
99       String modelId = getModelIdForFile(atom.getPdbFile());
100       sb.append(String.format(" %s//%s/%d/*", modelId, atom.getChain(),
101               atom.getPdbResNum()));
102     }
103     String labelSpec = sb.toString();
104     if (labelSpec.equals(lastLabelSpec))
105     {
106       return;
107     }
108     StructureCommandI command = new StructureCommand("indicate", labelSpec);
109     executeCommand(command, false);
110
111     lastLabelSpec = labelSpec;
112   }
113
114   @Override
115   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
116   {
117     return new SequenceRenderer(avp.getAlignViewport());
118   }
119
120   @Override
121   protected List<String> executeCommand(StructureCommandI command,
122           boolean getReply)
123   {
124     // System.out.println(command.toString()); // debug
125     return pymolManager.sendCommand(command, getReply);
126   }
127
128   @Override
129   protected String getModelIdForFile(String file)
130   {
131     return pymolObjects.containsKey(file) ? pymolObjects.get(file) : "";
132   }
133
134   @Override
135   protected ViewerType getViewerType()
136   {
137     return ViewerType.PYMOL;
138   }
139
140   @Override
141   public boolean isViewerRunning()
142   {
143     return pymolManager != null && pymolManager.isPymolLaunched();
144   }
145
146   @Override
147   public void closeViewer(boolean closePymol)
148   {
149     super.closeViewer(closePymol);
150     pymolManager = null;
151   }
152
153   public boolean launchPymol()
154   {
155     if (pymolManager.isPymolLaunched())
156     {
157       return true;
158     }
159
160     Process pymol = pymolManager.launchPymol();
161     if (pymol != null)
162     {
163       // start listening for PyMOL selections - how??
164       startExternalViewerMonitor(pymol);
165     }
166     else
167     {
168       Console.error("Failed to launch PyMOL!");
169     }
170     return pymol != null;
171   }
172
173   public void openFile(PDBEntry pe)
174   {
175     // todo : check not already open, remap / rename, etc
176     String file = pe.getFile();
177     StructureCommandI cmd = getCommandGenerator().loadFile(file);
178
179     /*
180      * a second parameter sets the pdbid as the loaded PyMOL object name
181      */
182     String pdbId = pe.getId();
183     try
184     {
185       String safePDBId = java.net.URLEncoder.encode(pdbId, "UTF-8");
186       pdbId = safePDBId.replace('%', '_');
187       pdbId = pdbId.replace("-", "__");
188       char fc = pdbId.charAt(0);
189       // put an 's' before any numerics
190       if (fc >= '0' && fc <= '9')
191       {
192         pdbId = 's' + pdbId;
193       }
194       // pdbId.replace('-', 0)
195     } catch (Exception x)
196     {
197       Console.error("Unxpected encoding exception for '" + pdbId + "'", x);
198     }
199     cmd.addParameter(pdbId);
200
201     executeCommand(cmd, false);
202
203     pymolObjects.put(file, pdbId);
204     if (!structureFiles.contains(file))
205     {
206       structureFiles.add(file);
207     }
208     if (getSsm() != null)
209     {
210       getSsm().addStructureViewerListener(this);
211     }
212
213   }
214
215   @Override
216   protected String getModelId(int pdbfnum, String file)
217   {
218     return file;
219   }
220
221   /**
222    * Returns the file extension to use for a saved viewer session file (.pse)
223    * 
224    * @return
225    * @see https://pymolwiki.org/index.php/Save
226    */
227   @Override
228   public String getSessionFileExtension()
229   {
230     return ".pse";
231   }
232
233   @Override
234   public String getHelpURL()
235   {
236     return "https://pymolwiki.org/";
237   }
238
239   /**
240    * Constructs and sends commands to set atom properties for visible Jalview
241    * features on residues mapped to structure
242    * 
243    * @param avp
244    * @return
245    */
246   public int sendFeaturesToViewer(AlignmentViewPanel avp)
247   {
248     // todo pull up this and JalviewChimeraBinding variant
249     Map<String, Map<Object, AtomSpecModel>> featureValues = buildFeaturesMap(
250             avp);
251     List<StructureCommandI> commands = getCommandGenerator()
252             .setAttributes(featureValues);
253     executeCommands(commands, false, null);
254     return commands.size();
255   }
256
257 }