new abstract proxy for use by applet and application
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ext.jmol;
19
20 import java.util.*;
21 import java.awt.*;
22 import java.awt.event.*;
23
24 import jalview.api.FeatureRenderer;
25 import jalview.api.SequenceRenderer;
26 import jalview.api.SequenceStructureBinding;
27 import jalview.datamodel.*;
28 import jalview.structure.*;
29 import jalview.io.*;
30
31 import org.jmol.api.*;
32 import org.jmol.adapter.smarter.SmarterJmolAdapter;
33
34 import org.jmol.popup.*;
35 import org.jmol.viewer.JmolConstants;
36
37 import jalview.schemes.*;
38
39 public abstract class JalviewJmolBinding implements StructureListener,
40         JmolStatusListener, SequenceStructureBinding
41
42 {
43
44   boolean allChainsSelected = false;
45
46   /**
47    * when true, try to search the associated datamodel for sequences that are
48    * associated with any unknown structures in the Jmol view.
49    */
50   private boolean associateNewStructs = false;
51
52   Vector atomsPicked = new Vector();
53
54   private Vector chainNames;
55
56   String[] chains;
57
58   boolean colourBySequence = true;
59
60   StringBuffer eval = new StringBuffer();
61
62   String fileLoadingError;
63
64   /**
65    * the default or current model displayed if the model cannot be identified
66    * from the selection message
67    */
68   int frameNo = 0;
69
70   JmolPopup jmolpopup;
71
72   String lastCommand;
73
74   String lastMessage;
75
76   boolean loadedInline;
77
78   /**
79    * current set of model filenames loaded in the Jmol instance
80    */
81   String[] modelFileNames = null;
82
83   PDBEntry[] pdbentry;
84
85   /**
86    * datasource protocol for access to PDBEntry
87    */
88   String protocol = null;
89
90   StringBuffer resetLastRes = new StringBuffer();
91
92   SequenceI[] sequence;
93
94   StructureSelectionManager ssm;
95
96   JmolViewer viewer;
97
98   public JalviewJmolBinding(PDBEntry[] pdbentry, SequenceI[] seq,
99           String[] chains, String protocol)
100   {
101     /*
102      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
103      * "jalviewJmol", ap.av.applet .getDocumentBase(),
104      * ap.av.applet.getCodeBase(), "", this);
105      * 
106      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
107      */
108   }
109
110   /**
111    * prepare the view for a given set of models/chains. chainList contains
112    * strings of the form 'pdbfilename:Chaincode'
113    * 
114    * @param chainList
115    *          list of chains to make visible
116    */
117   void centerViewer(Vector chainList)
118   {
119     StringBuffer cmd = new StringBuffer();
120     String lbl;
121     int mlength, p;
122     for (int i = 0, iSize = chainList.size(); i < iSize; i++)
123     {
124       mlength = 0;
125       lbl = (String) chainList.elementAt(i);
126       do
127       {
128         p = mlength;
129         mlength = lbl.indexOf(":", p);
130       } while (p < mlength && mlength < (lbl.length() - 2));
131       cmd.append(":" + lbl.substring(mlength + 1) + " /"
132               + getModelNum(lbl.substring(0, mlength)) + " or ");
133     }
134     if (cmd.length() > 0)
135       cmd.setLength(cmd.length() - 4);
136
137     jmolHistory(false);
138     viewer
139             .evalString("select *;restrict " + cmd + ";cartoon;center "
140                     + cmd);
141     jmolHistory(true);
142   }
143
144   void closeViewer()
145   {
146     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
147     // remove listeners for all structures in viewer
148     StructureSelectionManager.getStructureSelectionManager()
149             .removeStructureViewerListener(this, this.getPdbFile());
150     // and shut down jmol
151     viewer.evalStringQuiet("zap");
152     viewer.setJmolStatusListener(null);
153
154     viewer = null;
155   }
156
157   public void colourByChain()
158   {
159     jmolHistory(false);
160     colourBySequence = false;
161     viewer.evalStringQuiet("select *;color chain");
162     jmolHistory(true);
163   } 
164
165   public void colourByCharge()
166   {
167     jmolHistory(false);
168     colourBySequence = false;
169     viewer.evalStringQuiet("select *;color white;select ASP,GLU;color red;"
170             + "select LYS,ARG;color blue;select CYS;color yellow");
171     jmolHistory(true);
172   }
173
174   /**
175    * colour any structures associated with sequences in the given alignment
176    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
177    * if colourBySequence is enabled.
178    */
179   public void colourBySequence(boolean showFeatures, AlignmentI alignment)
180   {
181     if (!colourBySequence)
182       return;
183     String[] files = getPdbFile();
184     SequenceRenderer sr = getSequenceRenderer();
185
186     FeatureRenderer fr = null;
187     if (showFeatures)
188     {
189       fr = getFeatureRenderer();
190     }
191
192     StringBuffer command = new StringBuffer();
193
194     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
195     {
196       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
197
198       if (mapping == null || mapping.length < 1)
199         continue;
200
201       int lastPos = -1;
202       for (int s = 0; s < sequence.length; s++)
203       {
204         for (int sp, m = 0; m < mapping.length; m++)
205         {
206           if (mapping[m].getSequence() == sequence[s]
207                   && (sp = alignment.findIndex(sequence[s])) > -1)
208           {
209             SequenceI asp = alignment.getSequenceAt(sp);
210             for (int r = 0; r < asp.getLength(); r++)
211             {
212               // no mapping to gaps in sequence
213               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
214               {
215                 continue;
216               }
217               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
218
219               if (pos < 1 || pos == lastPos)
220                 continue;
221
222               lastPos = pos;
223
224               Color col = sr.getResidueBoxColour(sequence[s], r);
225
226               if (showFeatures)
227                 col = fr.findFeatureColour(col, sequence[s], r);
228               String newSelcom = (mapping[m].getChain() != " " ? ":"
229                       + mapping[m].getChain() : "")
230                       + "/"
231                       + (pdbfnum + 1)
232                       + ".1"
233                       + ";color["
234                       + col.getRed()
235                       + ","
236                       + col.getGreen()
237                       + ","
238                       + col.getBlue() + "]";
239               if (command.toString().endsWith(newSelcom))
240               {
241                 command = condenseCommand(command.toString(), pos);
242                 continue;
243               }
244               // TODO: deal with case when buffer is too large for Jmol to parse
245               // - execute command and flush
246
247               command.append(";select " + pos);
248               command.append(newSelcom);
249             }
250             break;
251           }
252         }
253       }
254     }
255
256     jmolHistory(false);
257     if (lastCommand == null || !lastCommand.equals(command.toString()))
258     {
259       viewer.evalStringQuiet(command.toString());
260     }
261     jmolHistory(true);
262     lastCommand = command.toString();
263   }
264
265   StringBuffer condenseCommand(String command, int pos)
266   {
267
268     StringBuffer sb = new StringBuffer(command.substring(0, command
269             .lastIndexOf("select") + 7));
270
271     command = command.substring(sb.length());
272
273     String start;
274
275     if (command.indexOf("-") > -1)
276     {
277       start = command.substring(0, command.indexOf("-"));
278     }
279     else
280     {
281       start = command.substring(0, command.indexOf(":"));
282     }
283
284     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
285
286     return sb;
287   }
288
289   public void createImage(String file, String type, int quality)
290   {
291   }
292
293   public String createImage(String fileName, String type,
294           Object textOrBytes, int quality)
295   {
296     // TODO Auto-generated method stub
297     return null;
298   }
299
300   public String eval(String strEval)
301   {
302     // System.out.println(strEval);
303     // "# 'eval' is implemented only for the applet.";
304     return null;
305   }
306
307   // End StructureListener
308   // //////////////////////////
309
310   public float[][] functionXY(String functionName, int x, int y)
311   {
312     return null;
313   }
314
315   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
316   {
317     // TODO Auto-generated method stub
318     return null;
319   }
320
321   public Color getColour(int atomIndex, int pdbResNum, String chain,
322           String pdbfile)
323   {
324     if (getModelNum(pdbfile) < 0)
325       return null;
326     // TODO: verify atomIndex is selecting correct model.
327     return new Color(viewer.getAtomArgb(atomIndex));
328   }
329
330   /**
331    * returns the current featureRenderer that should be used to colour the
332    * structures
333    * 
334    * @return
335    */
336   abstract FeatureRenderer getFeatureRenderer();
337
338   private int getModelNum(String modelFileName)
339   {
340     String[] mfn = getPdbFile();
341     if (mfn == null)
342     {
343       return -1;
344     }
345     for (int i = 0; i < mfn.length; i++)
346     {
347       if (mfn[i].equalsIgnoreCase(modelFileName))
348         return i;
349     }
350     return -1;
351   }
352
353   // ////////////////////////////////
354   // /StructureListener
355   public String[] getPdbFile()
356   {
357     if (modelFileNames == null)
358     {
359       String mset[] = new String[viewer.getModelCount()];
360       for (int i = 0; i < mset.length; i++)
361       {
362         mset[i] = viewer.getModelFileName(i);
363       }
364       modelFileNames = mset;
365     }
366     return modelFileNames;
367   }
368
369   public Hashtable getRegistryInfo()
370   {
371     // TODO Auto-generated method stub
372     return null;
373   }
374
375   /**
376    * returns the current sequenceRenderer that should be used to colour the
377    * structures
378    * 
379    * @return
380    */
381   abstract SequenceRenderer getSequenceRenderer();
382
383   // ///////////////////////////////
384   // JmolStatusListener
385
386   public void handlePopupMenu(int x, int y)
387   {
388     jmolpopup.show(x, y);
389   }
390
391   // jmol/ssm only
392   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
393           String pdbfile)
394   {
395     if (modelFileNames == null)
396     {
397       return;
398     }
399
400     // look up file model number for this pdbfile
401     int mdlNum = 0;
402     String fn;
403     // may need to adjust for URLencoding here - we don't worry about that yet.
404     while (mdlNum < modelFileNames.length
405             && !pdbfile.equals(modelFileNames[mdlNum]))
406     {
407       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
408       mdlNum++;
409     }
410     if (mdlNum == modelFileNames.length)
411     {
412       return;
413     }
414
415     jmolHistory(false);
416     // if (!pdbfile.equals(pdbentry.getFile()))
417     // return;
418     if (resetLastRes.length() > 0)
419     {
420       viewer.evalStringQuiet(resetLastRes.toString());
421     }
422
423     eval.setLength(0);
424     eval.append("select " + pdbResNum); // +modelNum
425
426     resetLastRes.setLength(0);
427     resetLastRes.append("select " + pdbResNum); // +modelNum
428
429     eval.append(":");
430     resetLastRes.append(":");
431     if (!chain.equals(" "))
432     {
433       eval.append(chain);
434       resetLastRes.append(chain);
435     }
436     {
437       eval.append(" /" + (mdlNum + 1));
438       resetLastRes.append("/" + (mdlNum + 1));
439     }
440     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
441
442     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
443             + " and not hetero; spacefill 0;");
444
445     eval.append("spacefill 200;select none");
446
447     viewer.evalStringQuiet(eval.toString());
448     jmolHistory(true);
449
450   }
451
452   private void jmolHistory(boolean enable)
453   {
454     viewer.setBooleanProperty("history", enable);
455   }
456
457   public void loadInline(String string)
458   {
459     loadedInline = true;
460     viewer.openStringInline(string);
461   }
462
463   public void mouseOverStructure(int atomIndex, String strInfo)
464   {
465     int pdbResNum;
466     int mdlSep = strInfo.indexOf("/");
467     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
468
469     if (chainSeparator == -1)
470     {
471       chainSeparator = strInfo.indexOf(".");
472       if (mdlSep > -1 && mdlSep < chainSeparator)
473       {
474         chainSeparator1 = chainSeparator;
475         chainSeparator = mdlSep;
476       }
477     }
478     pdbResNum = Integer.parseInt(strInfo.substring(
479             strInfo.indexOf("]") + 1, chainSeparator));
480
481     String chainId;
482
483     if (strInfo.indexOf(":") > -1)
484       chainId = strInfo.substring(strInfo.indexOf(":") + 1, strInfo
485               .indexOf("."));
486     else
487     {
488       chainId = " ";
489     }
490
491     String pdbfilename = modelFileNames[frameNo]; // default is first or current
492     // model
493     if (mdlSep > -1)
494     {
495       if (chainSeparator1 == -1)
496       {
497         chainSeparator1 = strInfo.indexOf(".", mdlSep);
498       }
499       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
500               chainSeparator1) : strInfo.substring(mdlSep + 1);
501       try
502       {
503         // recover PDB filename for the model hovered over.
504         pdbfilename = viewer
505                 .getModelFileName(new Integer(mdlId).intValue() - 1);
506       } catch (Exception e)
507       {
508       }
509       ;
510     }
511     if (lastMessage == null || !lastMessage.equals(strInfo))
512       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
513
514     lastMessage = strInfo;
515   }
516
517   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
518   {
519     if (data != null)
520     {
521       System.err.println("Ignoring additional hover info: " + data);
522     }
523     mouseOverStructure(atomIndex, strInfo);
524   }
525
526   /*
527    * { if (history != null && strStatus != null &&
528    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
529    * } }
530    */
531
532   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
533   {
534     /**
535      * this implements the toggle label behaviour copied from the original
536      * structure viewer, MCView
537      */
538     if (strData != null)
539     {
540       System.err.println("Ignoring additional pick data string " + strData);
541     }
542     int chainSeparator = strInfo.indexOf(":");
543     int p = 0;
544     if (chainSeparator == -1)
545       chainSeparator = strInfo.indexOf(".");
546
547     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
548             chainSeparator);
549     String mdlString = "";
550     if ((p = strInfo.indexOf(":")) > -1)
551       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
552
553     if ((p = strInfo.indexOf("/")) > -1)
554     {
555       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
556     }
557     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
558             + mdlString + "))";
559     jmolHistory(false);
560
561     if (!atomsPicked.contains(picked))
562     {
563       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
564       atomsPicked.addElement(picked);
565     }
566     else
567     {
568       viewer.evalString("select " + picked + ";label off");
569       atomsPicked.removeElement(picked);
570     }
571     jmolHistory(true);
572
573   }
574
575   public void notifyCallback(int type, Object[] data)
576   {
577     try
578     {
579       switch (type)
580       {
581       case JmolConstants.CALLBACK_LOADSTRUCT:
582         notifyFileLoaded((String) data[1], (String) data[2],
583                 (String) data[3], (String) data[4], ((Integer) data[5])
584                         .intValue());
585
586         break;
587       case JmolConstants.CALLBACK_PICK:
588         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
589                 (String) data[0]);
590         // also highlight in alignment
591       case JmolConstants.CALLBACK_HOVER:
592         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
593                 (String) data[0]);
594         break;
595       case JmolConstants.CALLBACK_SCRIPT:
596         notifyScriptTermination((String) data[2], ((Integer) data[3])
597                 .intValue());
598         break;
599       case JmolConstants.CALLBACK_ECHO:
600         sendConsoleEcho((String) data[1]);
601         break;
602       case JmolConstants.CALLBACK_MESSAGE:
603         sendConsoleMessage((data == null) ? ((String) null)
604                 : (String) data[1]);
605         break;
606       case JmolConstants.CALLBACK_MEASURE:
607       case JmolConstants.CALLBACK_CLICK:
608       default:
609         System.err.println("Unhandled callback " + type + " " + data);
610         break;
611       }
612     } catch (Exception e)
613     {
614       System.err.println("Squashed Jmol callback handler error:");
615       e.printStackTrace();
616     }
617   }
618
619   public boolean notifyEnabled(int callbackPick)
620   {
621     switch (callbackPick)
622     {
623     case JmolConstants.CALLBACK_ECHO:
624     case JmolConstants.CALLBACK_LOADSTRUCT:
625     case JmolConstants.CALLBACK_MEASURE:
626     case JmolConstants.CALLBACK_MESSAGE:
627     case JmolConstants.CALLBACK_PICK:
628     case JmolConstants.CALLBACK_SCRIPT:
629     case JmolConstants.CALLBACK_HOVER:
630     case JmolConstants.CALLBACK_ERROR:
631       return true;
632     case JmolConstants.CALLBACK_CLICK:
633     case JmolConstants.CALLBACK_ANIMFRAME:
634     case JmolConstants.CALLBACK_MINIMIZATION:
635     case JmolConstants.CALLBACK_RESIZE:
636     case JmolConstants.CALLBACK_SYNC:
637     }
638     return false;
639   }
640
641   public void notifyFileLoaded(String fullPathName, String fileName2,
642           String modelName, String errorMsg, int modelParts)
643   {
644     if (errorMsg != null)
645     {
646       fileLoadingError = errorMsg;
647       updateUI();
648       return;
649     }
650     fileLoadingError = null;
651     modelFileNames = null;
652     chainNames = new Vector();
653     boolean notifyLoaded = false;
654     String[] modelfilenames = getPdbFile();
655     ssm = StructureSelectionManager.getStructureSelectionManager();
656     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
657     {
658       String fileName = modelfilenames[modelnum];
659       if (fileName != null)
660       {
661         // search pdbentries and sequences to find correct pdbentry and
662         // sequence[] pair for this filename
663         if (pdbentry != null)
664         {
665           boolean foundEntry = false;
666           for (int pe = 0; pe < pdbentry.length; pe++)
667           {
668             if (pdbentry[pe].getFile().equals(fileName))
669             {
670               foundEntry = true;
671               MCview.PDBfile pdb;
672               if (loadedInline)
673               {
674                 // TODO: replace with getData ?
675                 pdb = ssm.setMapping(sequence, chains, pdbentry[pe]
676                         .getFile(), AppletFormatAdapter.PASTE);
677                 pdbentry[pe].setFile("INLINE" + pdb.id);
678               }
679               else
680               {
681                 // TODO: Jmol can in principle retrieve from CLASSLOADER but
682                 // this
683                 // needs
684                 // to be tested. See mantis bug
685                 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
686
687                 pdb = ssm.setMapping(sequence, chains, pdbentry[pe]
688                         .getFile(), AppletFormatAdapter.URL);
689
690               }
691
692               pdbentry[pe].setId(pdb.id);
693
694               for (int i = 0; i < pdb.chains.size(); i++)
695               {
696                 chainNames.addElement(new String(pdb.id + ":"
697                         + ((MCview.PDBChain) pdb.chains.elementAt(i)).id));
698               }
699               notifyLoaded = true;
700             }
701           }
702           if (!foundEntry && associateNewStructs)
703           {
704             // this is a foreign pdb file that jalview doesn't know about - add
705             // it
706             // to the dataset
707             // and try to find a home - either on a matching sequence or as a
708             // new
709             // sequence.
710             String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
711                     "PDB");
712             // parse pdb file into a chain, etc.
713             // locate best match for pdb in associated views and add mapping to
714             // ssm
715             // if properly registered then notifyLoaded=true;
716           }
717         }
718       }
719     }
720     // FILE LOADED OK
721     // so finally, update the jmol bits and pieces
722     jmolpopup.updateComputedMenus();
723     viewer
724             .evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
725     // register ourselves as a listener and notify the gui that it needs to
726     // update itself.
727     ssm.addStructureViewerListener(this);
728     if (notifyLoaded)
729     {
730       updateUI();
731     }
732
733   }
734
735   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
736   {
737     notifyAtomPicked(iatom, strMeasure, null);
738   }
739
740   public void notifyScriptTermination(String strStatus, int msWalltime)
741   {
742   }
743
744   /**
745    * display a message echoed from the jmol viewer
746    * 
747    * @param strEcho
748    */
749   public abstract void sendConsoleEcho(String strEcho); /*
750                                                          * { showConsole(true);
751                                                          * 
752                                                          * history.append("\n" +
753                                                          * strEcho); }
754                                                          */
755
756   // /End JmolStatusListener
757   // /////////////////////////////
758
759   /**
760    * @param strStatus
761    *          status message - usually the response received after a script
762    *          executed
763    */
764   public abstract void sendConsoleMessage(String strStatus);
765
766   public void setCallbackFunction(String callbackType,
767           String callbackFunction)
768   {
769     System.err.println("Ignoring set-callback request to associate "
770             + callbackType + " with function " + callbackFunction);
771
772   }
773
774   public void setJalviewColourScheme(ColourSchemeI cs)
775   {
776     colourBySequence = false;
777
778     if (cs == null)
779       return;
780
781     String res;
782     int index;
783     Color col;
784     jmolHistory(false);
785
786     Enumeration en = ResidueProperties.aa3Hash.keys();
787     StringBuffer command = new StringBuffer("select *;color white;");
788     while (en.hasMoreElements())
789     {
790       res = en.nextElement().toString();
791       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
792       if (index > 20)
793         continue;
794
795       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
796
797       command.append("select " + res + ";color[" + col.getRed() + ","
798               + col.getGreen() + "," + col.getBlue() + "];");
799     }
800
801     viewer.evalStringQuiet(command.toString());
802     jmolHistory(true);
803   }
804
805   public void showHelp()
806   {
807     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
808   }
809
810   /**
811    * open the URL somehow
812    * 
813    * @param target
814    */
815   public abstract void showUrl(String url, String target);
816
817   /**
818    * called when the binding thinks the UI needs to be refreshed after a Jmol
819    * state change. this could be because structures were loaded, or because an
820    * error has occured.
821    */
822   abstract void updateUI();
823
824 }