JAL-1859 use unresolved PDB filename
[jalview.git] / src / jalview / javascript / MouseOverStructureListener.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.javascript;
22
23 import jalview.api.AlignmentViewPanel;
24 import jalview.api.FeatureRenderer;
25 import jalview.api.SequenceRenderer;
26 import jalview.appletgui.AlignFrame;
27 import jalview.bin.JalviewLite;
28 import jalview.datamodel.SequenceI;
29 import jalview.ext.jmol.JmolCommands;
30 import jalview.structure.AtomSpec;
31 import jalview.structure.StructureListener;
32 import jalview.structure.StructureMapping;
33 import jalview.structure.StructureMappingcommandSet;
34 import jalview.structure.StructureSelectionManager;
35
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.net.URL;
39 import java.util.ArrayList;
40 import java.util.List;
41
42 /**
43  * Propagate events involving PDB structures associated with sequences to a
44  * javascript function. Generally, the javascript handler is called with a
45  * series of arguments like (eventname, ... ). As of Jalview 2.7, the following
46  * different types of events are supported:
47  * <ul>
48  * <li>mouseover: javascript function called with arguments
49  * 
50  * <pre>
51  * ['mouseover',String(pdb file URI), String(pdb file chain ID), String(residue
52  * number moused over), String(atom index corresponding to residue)]
53  * </pre>
54  * 
55  * </li>
56  * <li>colourstruct: javascript function called with arguments
57  * 
58  * <pre>
59  * ['colourstruct',String(alignment view id),String(number of javascript message
60  * chunks to collect),String(length of first chunk in set of messages - or zero
61  * for null message)]
62  * </pre>
63  * 
64  * <br>
65  * The message contains a series of Jmol script commands that will colour
66  * structures according to their associated sequences in the current view. Use
67  * jalview
68  * .javascript.JalviewLiteJsApi.getJsMessage('colourstruct',String(alignment
69  * view id)) to retrieve successive chunks of the message.</li>
70  * </ul>
71  * 
72  * @author Jim Procter (jprocter)
73  * 
74  */
75 public class MouseOverStructureListener extends JSFunctionExec implements
76         JsCallBack, StructureListener
77 {
78
79   String _listenerfn;
80
81   String[] modelSet;
82
83   public MouseOverStructureListener(JalviewLite jalviewLite,
84           String listener, String[] modelList)
85   {
86     super(jalviewLite);
87     _listenerfn = listener;
88     modelSet = modelList;
89     if (modelSet != null)
90     {
91       for (int i = 0; i < modelSet.length; i++)
92       {
93         // ? leave file names 'as is' to match StructureMapping.pdbfile
94         // modelSet[i] = resolveModelFile(modelSet[i]);
95       }
96     }
97   }
98
99   /**
100    * Returns the first out of file, file prefixed by document base, or file
101    * prefixed by codebase which can be resolved to a valid URL. If none can,
102    * returns the input parameter value.
103    * 
104    * @param file
105    */
106   public String resolveModelFile(String file)
107   {
108     if (isValidUrl(file))
109     {
110       return file;
111     }
112
113     String db = jvlite.getDocumentBase().toString();
114     db = db.substring(0, db.lastIndexOf("/"));
115     String docBaseFile = db + "/" + file;
116     if (isValidUrl(docBaseFile))
117     {
118       return docBaseFile;
119     }
120
121     String cb = jvlite.getCodeBase() + file;
122     if (isValidUrl(cb))
123     {
124       return cb;
125     }
126
127     return file;
128   }
129
130   /**
131    * Returns true if it is possible to open an input stream at the given URL,
132    * else false. The input stream is closed.
133    * 
134    * @param file
135    * @return
136    */
137   private boolean isValidUrl(String file)
138   {
139     InputStream is = null;
140     try
141     {
142       is = new URL(file).openStream();
143       if (is != null)
144       {
145         return true;
146       }
147     } catch (IOException x)
148     {
149       // MalformedURLException, FileNotFoundException
150       return false;
151     } finally
152     {
153       if (is != null)
154       {
155         try
156         {
157           is.close();
158         } catch (IOException e)
159         {
160           // ignore
161         }
162       }
163     }
164     return false;
165   }
166
167   @Override
168   public String[] getPdbFile()
169   {
170     return modelSet;
171   }
172
173   public void mouseOverStructure(int atomIndex, String strInfo)
174   {
175
176     // StructureSelectionManager.getStructureSelectionManager().mouseOverStructure(atomIndex,
177     // chain, pdbfile)
178     // TODO Auto-generated method stub
179
180   }
181
182   @Override
183   public void highlightAtoms(List<AtomSpec> atoms)
184   {
185     for (AtomSpec atom : atoms)
186     {
187       try
188       {
189         // TODO is this right? StructureSelectionManager passes pdbFile as the
190         // field that is interpreted (in 2.8.2) as pdbId?
191         // JBPComment: yep - this is right! the Javascript harness uses the
192         // absolute pdbFile URI to locate the PDB file in the external viewer
193         executeJavascriptFunction(_listenerfn,
194                 new String[] { "mouseover", "" + atom.getPdbFile(),
195                     "" + atom.getChain(), "" + (atom.getPdbResNum()),
196                     "" + atom.getAtomIndex() });
197       } catch (Exception ex)
198       {
199         System.err.println("Couldn't execute callback with " + _listenerfn
200                 + " for atomSpec: " + atom);
201         ex.printStackTrace();
202       }
203     }
204   }
205
206   @Override
207   public synchronized void updateColours(Object srce)
208   {
209     final Object source = srce;
210     StructureSelectionManager ssm = StructureSelectionManager
211             .getStructureSelectionManager(jvlite);
212
213     if (JalviewLite.debug)
214     {
215       System.err.println(this.getClass().getName() + " modelSet[0]: "
216               + modelSet[0]);
217       ssm.reportMapping();
218     }
219
220     if (source instanceof jalview.api.AlignmentViewPanel)
221     {
222       SequenceI[][] sequence = new SequenceI[modelSet.length][];
223       for (int m = 0; m < modelSet.length; m++)
224       {
225         StructureMapping[] sm = ssm.getMapping(modelSet[m]);
226         if (sm != null && sm.length > 0)
227         {
228           sequence[m] = new SequenceI[sm.length];
229           for (int i = 0; i < sm.length; i++)
230           {
231             sequence[m][i] = sm[i].getSequence();
232           }
233         }
234         else
235         {
236           sequence[m] = new SequenceI[0];
237         }
238         // if (jvlite.debug)
239         // {
240         // System.err.println("Mapped '" + modelSet[m] + "' to "
241         // + sequence[m].length + " sequences.");
242         // }
243       }
244
245       SequenceRenderer sr = ((jalview.appletgui.AlignmentPanel) source)
246               .getSequenceRenderer();
247       FeatureRenderer fr = ((jalview.appletgui.AlignmentPanel) source).av
248               .isShowSequenceFeatures() ? new jalview.appletgui.FeatureRenderer(
249               ((jalview.appletgui.AlignmentPanel) source).av) : null;
250       if (fr != null)
251       {
252         ((jalview.appletgui.FeatureRenderer) fr)
253                 .transferSettings(((jalview.appletgui.AlignmentPanel) source)
254                         .getFeatureRenderer());
255       }
256       ;
257
258       // Form a colour command from the given alignment panel for each distinct
259       // structure
260       ArrayList<String[]> ccomands = new ArrayList<String[]>();
261       ArrayList<String> pdbfn = new ArrayList<String>();
262       StructureMappingcommandSet[] colcommands = JmolCommands
263               .getColourBySequenceCommand(ssm, modelSet, sequence, sr, fr,
264                       ((AlignmentViewPanel) source).getAlignment());
265       if (colcommands == null)
266       {
267         return;
268       }
269       int sz = 0;
270       for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
271       {
272         sz += ccset.commands.length;
273         ccomands.add(ccset.commands);
274         pdbfn.add(ccset.mapping);
275       }
276
277       String mclass, mhandle;
278       String ccomandset[] = new String[sz];
279       sz = 0;
280       for (String[] ccset : ccomands)
281       {
282         System.arraycopy(ccset, 0, ccomandset, sz, ccset.length);
283         sz += ccset.length;
284       }
285       if (jvlite.isJsMessageSetChanged(
286               mclass = "colourstruct",
287               mhandle = ((jalview.appletgui.AlignmentPanel) source).av
288                       .getViewId(), ccomandset))
289       {
290         jvlite.setJsMessageSet(mclass, mhandle, ccomandset);
291         // and notify javascript handler
292         String st[] = new String[] {
293             "colourstruct",
294             "" + ((jalview.appletgui.AlignmentPanel) source).av.getViewId(),
295             "" + ccomandset.length,
296             jvlite.arrayToSeparatorList(pdbfn.toArray(new String[pdbfn
297                     .size()])) };
298         try
299         {
300           executeJavascriptFunction(true, _listenerfn, st);
301         } catch (Exception ex)
302         {
303           System.err.println("Couldn't execute callback with "
304                   + _listenerfn + " using args { " + st[0] + ", " + st[1]
305                   + ", " + st[2] + "," + st[3] + "}"); // + ","+st[4]+"\n");
306           ex.printStackTrace();
307
308         }
309       }
310       /*
311        * new Thread(new Runnable() { public void run() { // and send to
312        * javascript handler String st[] = new String[0]; int i = 0; for (String
313        * colcommand : colcommands) { // do sync execution for each chunk try {
314        * executeJavascriptFunction( false, _listenerfn, st = new String[] {
315        * "colourstruct", "" + ((jalview.appletgui.AlignmentPanel) source).av
316        * .getViewId(), handle, "" }); } catch (Exception ex) {
317        * System.err.println("Couldn't execute callback with " + _listenerfn +
318        * " using args { " + st[0] + ", " + st[1] + ", " + st[2] + "," + st[3] +
319        * "\n"); ex.printStackTrace();
320        * 
321        * } } } }).start();
322        */
323     }
324
325   }
326
327   @Override
328   public AlignFrame getAlignFrame()
329   {
330     // associated with all alignframes, always.
331     return null;
332   }
333
334   @Override
335   public String getListenerFunction()
336   {
337     return _listenerfn;
338   }
339
340   public void finalise()
341   {
342     jvlite = null;
343     super.finalize();
344   }
345
346   @Override
347   public void releaseReferences(Object svl)
348   {
349
350     // TODO Auto-generated method stub
351
352   }
353
354   @Override
355   public boolean isListeningFor(SequenceI seq)
356   {
357     return true;
358   }
359
360 }