fbac400b17cfbd6a5e8e3f8ab2f2ba5e079c94f4
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.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.jmol;
22
23 import jalview.api.AlignmentViewPanel;
24 import jalview.api.FeatureRenderer;
25 import jalview.api.SequenceRenderer;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.ColumnSelection;
28 import jalview.datamodel.PDBEntry;
29 import jalview.datamodel.SequenceI;
30 import jalview.io.AppletFormatAdapter;
31 import jalview.io.StructureFile;
32 import jalview.schemes.ColourSchemeI;
33 import jalview.schemes.ResidueProperties;
34 import jalview.structure.AtomSpec;
35 import jalview.structure.StructureMappingcommandSet;
36 import jalview.structure.StructureSelectionManager;
37 import jalview.structures.models.AAStructureBindingModel;
38
39 import java.awt.Color;
40 import java.awt.Container;
41 import java.awt.event.ComponentEvent;
42 import java.awt.event.ComponentListener;
43 import java.io.File;
44 import java.net.URL;
45 import java.security.AccessControlException;
46 import java.util.ArrayList;
47 import java.util.Hashtable;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Vector;
51
52 import javajs.awt.Dimension;
53
54 import org.jmol.adapter.smarter.SmarterJmolAdapter;
55 import org.jmol.api.JmolAppConsoleInterface;
56 import org.jmol.api.JmolSelectionListener;
57 import org.jmol.api.JmolStatusListener;
58 import org.jmol.api.JmolViewer;
59 import org.jmol.c.CBK;
60 import org.jmol.script.T;
61 import org.jmol.viewer.Viewer;
62
63 public abstract class JalviewJmolBinding extends AAStructureBindingModel
64         implements JmolStatusListener, JmolSelectionListener,
65         ComponentListener
66 {
67   boolean allChainsSelected = false;
68
69   /*
70    * when true, try to search the associated datamodel for sequences that are
71    * associated with any unknown structures in the Jmol view.
72    */
73   private boolean associateNewStructs = false;
74
75   Vector<String> atomsPicked = new Vector<String>();
76
77   public Vector<String> chainNames;
78
79   Hashtable<String, String> chainFile;
80
81   public String fileLoadingError;
82
83   /*
84    * the default or current model displayed if the model cannot be identified
85    * from the selection message
86    */
87   int frameNo = 0;
88
89   // protected JmolGenericPopup jmolpopup; // not used - remove?
90
91   String lastCommand;
92
93   String lastMessage;
94
95   boolean loadedInline;
96
97   StringBuffer resetLastRes = new StringBuffer();
98
99   public Viewer viewer;
100
101   public JalviewJmolBinding(StructureSelectionManager ssm,
102           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
103           String protocol)
104   {
105     super(ssm, pdbentry, sequenceIs, chains, protocol);
106     /*
107      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
108      * "jalviewJmol", ap.av.applet .getDocumentBase(),
109      * ap.av.applet.getCodeBase(), "", this);
110      * 
111      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
112      */
113   }
114
115   public JalviewJmolBinding(StructureSelectionManager ssm,
116           SequenceI[][] seqs, Viewer theViewer)
117   {
118     super(ssm, seqs);
119
120     viewer = theViewer;
121     viewer.setJmolStatusListener(this);
122     viewer.addSelectionListener(this);
123   }
124
125   /**
126    * construct a title string for the viewer window based on the data jalview
127    * knows about
128    * 
129    * @return
130    */
131   public String getViewerTitle()
132   {
133     return getViewerTitle("Jmol", true);
134   }
135
136   /**
137    * prepare the view for a given set of models/chains. chainList contains
138    * strings of the form 'pdbfilename:Chaincode'
139    * 
140    * @param chainList
141    *          list of chains to make visible
142    */
143   public void centerViewer(Vector<String> chainList)
144   {
145     StringBuilder cmd = new StringBuilder(128);
146     int mlength, p;
147     for (String lbl : chainList)
148     {
149       mlength = 0;
150       do
151       {
152         p = mlength;
153         mlength = lbl.indexOf(":", p);
154       } while (p < mlength && mlength < (lbl.length() - 2));
155       // TODO: lookup each pdb id and recover proper model number for it.
156       cmd.append(":" + lbl.substring(mlength + 1) + " /"
157               + (1 + getModelNum(chainFile.get(lbl))) + " or ");
158     }
159     if (cmd.length() > 0)
160     {
161       cmd.setLength(cmd.length() - 4);
162     }
163     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
164   }
165
166   public void closeViewer()
167   {
168     // remove listeners for all structures in viewer
169     getSsm().removeStructureViewerListener(this, this.getPdbFile());
170     viewer.dispose();
171     lastCommand = null;
172     viewer = null;
173     releaseUIResources();
174   }
175
176   public void colourByChain()
177   {
178     colourBySequence = false;
179     // TODO: colour by chain should colour each chain distinctly across all
180     // visible models
181     // TODO: http://issues.jalview.org/browse/JAL-628
182     evalStateCommand("select *;color chain");
183   }
184
185   public void colourByCharge()
186   {
187     colourBySequence = false;
188     evalStateCommand("select *;color white;select ASP,GLU;color red;"
189             + "select LYS,ARG;color blue;select CYS;color yellow");
190   }
191
192   /**
193    * superpose the structures associated with sequences in the alignment
194    * according to their corresponding positions.
195    */
196   public void superposeStructures(AlignmentI alignment)
197   {
198     superposeStructures(alignment, -1, null);
199   }
200
201   /**
202    * superpose the structures associated with sequences in the alignment
203    * according to their corresponding positions. ded)
204    * 
205    * @param refStructure
206    *          - select which pdb file to use as reference (default is -1 - the
207    *          first structure in the alignment)
208    */
209   public void superposeStructures(AlignmentI alignment, int refStructure)
210   {
211     superposeStructures(alignment, refStructure, null);
212   }
213
214   /**
215    * superpose the structures associated with sequences in the alignment
216    * according to their corresponding positions. ded)
217    * 
218    * @param refStructure
219    *          - select which pdb file to use as reference (default is -1 - the
220    *          first structure in the alignment)
221    * @param hiddenCols
222    *          TODO
223    */
224   public void superposeStructures(AlignmentI alignment, int refStructure,
225           ColumnSelection hiddenCols)
226   {
227     superposeStructures(new AlignmentI[] { alignment },
228             new int[] { refStructure },
229             new ColumnSelection[] { hiddenCols });
230   }
231
232   /**
233    * Construct and send a command to align structures against a reference
234    * structure, based on one or more sequence alignments
235    * 
236    * @param _alignment
237    *          an array of alignments to process
238    * @param _refStructure
239    *          an array of corresponding reference structures (index into pdb
240    *          file array); if a negative value is passed, the first PDB file
241    *          mapped to an alignment sequence is used as the reference for
242    *          superposition
243    * @param _hiddenCols
244    *          an array of corresponding hidden columns for each alignment
245    */
246   public void superposeStructures(AlignmentI[] _alignment,
247           int[] _refStructure, ColumnSelection[] _hiddenCols)
248   {
249     while (viewer.isScriptExecuting())
250     {
251       try
252       {
253         Thread.sleep(10);
254       } catch (InterruptedException i)
255       {
256       }
257     }
258
259     /*
260      * get the distinct structure files modelled
261      * (a file with multiple chains may map to multiple sequences)
262      */
263     String[] files = getPdbFile();
264     if (!waitForFileLoad(files))
265     {
266       return;
267     }
268
269     StringBuilder selectioncom = new StringBuilder(256);
270     // In principle - nSeconds specifies the speed of animation for each
271     // superposition - but is seems to behave weirdly, so we don't specify it.
272     String nSeconds = " ";
273     if (files.length > 10)
274     {
275       nSeconds = " 0.005 ";
276     }
277     else
278     {
279       nSeconds = " " + (2.0 / files.length) + " ";
280       // if (nSeconds).substring(0,5)+" ";
281     }
282     // see JAL-1345 - should really automatically turn off the animation for
283     // large numbers of structures, but Jmol doesn't seem to allow that.
284     // nSeconds = " ";
285     // union of all aligned positions are collected together.
286     for (int a = 0; a < _alignment.length; a++)
287     {
288       int refStructure = _refStructure[a];
289       AlignmentI alignment = _alignment[a];
290       ColumnSelection hiddenCols = _hiddenCols[a];
291       if (a > 0
292               && selectioncom.length() > 0
293               && !selectioncom.substring(selectioncom.length() - 1).equals(
294                       "|"))
295       {
296         selectioncom.append("|");
297       }
298       // process this alignment
299       if (refStructure >= files.length)
300       {
301         System.err.println("Invalid reference structure value "
302                 + refStructure);
303         refStructure = -1;
304       }
305
306       /*
307        * 'matched' array will hold 'true' for visible alignment columns where
308        * all sequences have a residue with a mapping to the PDB structure
309        */
310       // TODO could use a BitSet for matched
311       boolean matched[] = new boolean[alignment.getWidth()];
312       for (int m = 0; m < matched.length; m++)
313       {
314         matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
315       }
316
317       SuperposeData[] structures = new SuperposeData[files.length];
318       for (int f = 0; f < files.length; f++)
319       {
320         structures[f] = new SuperposeData(alignment.getWidth());
321       }
322
323       /*
324        * Calculate the superposable alignment columns ('matched'), and the
325        * corresponding structure residue positions (structures.pdbResNo)
326        */
327       int candidateRefStructure = findSuperposableResidues(alignment,
328               matched, structures);
329       if (refStructure < 0)
330       {
331         /*
332          * If no reference structure was specified, pick the first one that has
333          * a mapping in the alignment
334          */
335         refStructure = candidateRefStructure;
336       }
337
338       String[] selcom = new String[files.length];
339       int nmatched = 0;
340       for (boolean b : matched)
341       {
342         if (b)
343         {
344           nmatched++;
345         }
346       }
347       if (nmatched < 4)
348       {
349         // TODO: bail out here because superposition illdefined?
350       }
351
352       /*
353        * generate select statements to select regions to superimpose structures
354        */
355       {
356         // TODO extract method to construct selection statements
357         for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
358         {
359           String chainCd = ":" + structures[pdbfnum].chain;
360           int lpos = -1;
361           boolean run = false;
362           StringBuilder molsel = new StringBuilder();
363           molsel.append("{");
364           for (int r = 0; r < matched.length; r++)
365           {
366             if (matched[r])
367             {
368               int pdbResNo = structures[pdbfnum].pdbResNo[r];
369               if (lpos != pdbResNo - 1)
370               {
371                 // discontinuity
372                 if (lpos != -1)
373                 {
374                   molsel.append(lpos);
375                   molsel.append(chainCd);
376                   molsel.append("|");
377                 }
378                 run = false;
379               }
380               else
381               {
382                 // continuous run - and lpos >-1
383                 if (!run)
384                 {
385                   // at the beginning, so add dash
386                   molsel.append(lpos);
387                   molsel.append("-");
388                 }
389                 run = true;
390               }
391               lpos = pdbResNo;
392             }
393           }
394           /*
395            * add final selection phrase
396            */
397           if (lpos != -1)
398           {
399             molsel.append(lpos);
400             molsel.append(chainCd);
401             molsel.append("}");
402           }
403           if (molsel.length() > 1)
404           {
405             selcom[pdbfnum] = molsel.toString();
406             selectioncom.append("((");
407             selectioncom.append(selcom[pdbfnum].substring(1,
408                     selcom[pdbfnum].length() - 1));
409             selectioncom.append(" )& ");
410             selectioncom.append(pdbfnum + 1);
411             selectioncom.append(".1)");
412             if (pdbfnum < files.length - 1)
413             {
414               selectioncom.append("|");
415             }
416           }
417           else
418           {
419             selcom[pdbfnum] = null;
420           }
421         }
422       }
423       StringBuilder command = new StringBuilder(256);
424       // command.append("set spinFps 10;\n");
425
426       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
427       {
428         if (pdbfnum == refStructure || selcom[pdbfnum] == null
429                 || selcom[refStructure] == null)
430         {
431           continue;
432         }
433         command.append("echo ");
434         command.append("\"Superposing (");
435         command.append(structures[pdbfnum].pdbId);
436         command.append(") against reference (");
437         command.append(structures[refStructure].pdbId);
438         command.append(")\";\ncompare " + nSeconds);
439         command.append("{");
440         command.append(Integer.toString(1 + pdbfnum));
441         command.append(".1} {");
442         command.append(Integer.toString(1 + refStructure));
443         // conformation=1 excludes alternate locations for CA (JAL-1757)
444         command.append(".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
445
446         // for (int s = 0; s < 2; s++)
447         // {
448         // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
449         // }
450         command.append(selcom[pdbfnum]);
451         command.append(selcom[refStructure]);
452         command.append(" ROTATE TRANSLATE;\n");
453       }
454       if (selectioncom.length() > 0)
455       {
456         // TODO is performing selectioncom redundant here? is done later on
457         // System.out.println("Select regions:\n" + selectioncom.toString());
458         evalStateCommand("select *; cartoons off; backbone; select ("
459                 + selectioncom.toString() + "); cartoons; ");
460         // selcom.append("; ribbons; ");
461         String cmdString = command.toString();
462         // System.out.println("Superimpose command(s):\n" + cmdString);
463
464         evalStateCommand(cmdString);
465       }
466     }
467     if (selectioncom.length() > 0)
468     {// finally, mark all regions that were superposed.
469       if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
470       {
471         selectioncom.setLength(selectioncom.length() - 1);
472       }
473       // System.out.println("Select regions:\n" + selectioncom.toString());
474       evalStateCommand("select *; cartoons off; backbone; select ("
475               + selectioncom.toString() + "); cartoons; ");
476       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
477     }
478   }
479
480   public void evalStateCommand(String command)
481   {
482     jmolHistory(false);
483     if (lastCommand == null || !lastCommand.equals(command))
484     {
485       viewer.evalStringQuiet(command + "\n");
486     }
487     jmolHistory(true);
488     lastCommand = command;
489   }
490
491   /**
492    * colour any structures associated with sequences in the given alignment
493    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
494    * if colourBySequence is enabled.
495    */
496   public void colourBySequence(AlignmentViewPanel alignmentv)
497   {
498     boolean showFeatures = alignmentv.getAlignViewport()
499             .isShowSequenceFeatures();
500     if (!colourBySequence || !isLoadingFinished())
501     {
502       return;
503     }
504     if (getSsm() == null)
505     {
506       return;
507     }
508     String[] files = getPdbFile();
509
510     SequenceRenderer sr = getSequenceRenderer(alignmentv);
511
512     FeatureRenderer fr = null;
513     if (showFeatures)
514     {
515       fr = getFeatureRenderer(alignmentv);
516     }
517     AlignmentI alignment = alignmentv.getAlignment();
518
519     for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
520             files, sr, fr, alignment))
521     {
522       for (String cbyseq : cpdbbyseq.commands)
523       {
524         executeWhenReady(cbyseq);
525       }
526     }
527   }
528
529   /**
530    * @param files
531    * @param sr
532    * @param fr
533    * @param alignment
534    * @return
535    */
536   protected StructureMappingcommandSet[] getColourBySequenceCommands(
537           String[] files, SequenceRenderer sr, FeatureRenderer fr,
538           AlignmentI alignment)
539   {
540     return JmolCommands.getColourBySequenceCommand(getSsm(), files,
541             getSequence(), sr, fr, alignment);
542   }
543
544   /**
545    * @param command
546    */
547   protected void executeWhenReady(String command)
548   {
549     evalStateCommand(command);
550   }
551
552   public void createImage(String file, String type, int quality)
553   {
554     System.out.println("JMOL CREATE IMAGE");
555   }
556
557   @Override
558   public String createImage(String fileName, String type,
559           Object textOrBytes, int quality)
560   {
561     System.out.println("JMOL CREATE IMAGE");
562     return null;
563   }
564
565   @Override
566   public String eval(String strEval)
567   {
568     // System.out.println(strEval);
569     // "# 'eval' is implemented only for the applet.";
570     return null;
571   }
572
573   // End StructureListener
574   // //////////////////////////
575
576   @Override
577   public float[][] functionXY(String functionName, int x, int y)
578   {
579     return null;
580   }
581
582   @Override
583   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
584   {
585     // TODO Auto-generated method stub
586     return null;
587   }
588
589   public Color getColour(int atomIndex, int pdbResNum, String chain,
590           String pdbfile)
591   {
592     if (getModelNum(pdbfile) < 0)
593     {
594       return null;
595     }
596     // TODO: verify atomIndex is selecting correct model.
597     // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4
598     int colour = viewer.ms.at[atomIndex].atomPropertyInt(T.color);
599     return new Color(colour);
600   }
601
602   /**
603    * returns the current featureRenderer that should be used to colour the
604    * structures
605    * 
606    * @param alignment
607    * 
608    * @return
609    */
610   public abstract FeatureRenderer getFeatureRenderer(
611           AlignmentViewPanel alignment);
612
613   /**
614    * instruct the Jalview binding to update the pdbentries vector if necessary
615    * prior to matching the jmol view's contents to the list of structure files
616    * Jalview knows about.
617    */
618   public abstract void refreshPdbEntries();
619
620   private int getModelNum(String modelFileName)
621   {
622     String[] mfn = getPdbFile();
623     if (mfn == null)
624     {
625       return -1;
626     }
627     for (int i = 0; i < mfn.length; i++)
628     {
629       if (mfn[i].equalsIgnoreCase(modelFileName))
630       {
631         return i;
632       }
633     }
634     return -1;
635   }
636
637   /**
638    * map between index of model filename returned from getPdbFile and the first
639    * index of models from this file in the viewer. Note - this is not trimmed -
640    * use getPdbFile to get number of unique models.
641    */
642   private int _modelFileNameMap[];
643
644   // ////////////////////////////////
645   // /StructureListener
646   @Override
647   public synchronized String[] getPdbFile()
648   {
649     if (viewer == null)
650     {
651       return new String[0];
652     }
653     if (modelFileNames == null)
654     {
655       List<String> mset = new ArrayList<String>();
656       _modelFileNameMap = new int[viewer.ms.mc];
657       String m = viewer.ms.getModelFileName(0);
658       if (m != null)
659       {
660         String filePath = m;
661         try
662         {
663           filePath = new File(m).getAbsolutePath();
664         } catch (AccessControlException x)
665         {
666           // usually not allowed to do this in applet
667           System.err
668                   .println("jmolBinding: Using local file string from Jmol: "
669                           + m);
670         }
671         if (filePath.indexOf("/file:") != -1)
672         {
673           // applet path with docroot - discard as format won't match pdbfile
674           filePath = m;
675         }
676         mset.add(filePath);
677         _modelFileNameMap[0] = 0; // filename index for first model is always 0.
678       }
679       int j = 1;
680       for (int i = 1; i < viewer.ms.mc; i++)
681       {
682         m = viewer.ms.getModelFileName(i);
683         String filePath = m;
684         if (m != null)
685         {
686           try
687           {
688             filePath = new File(m).getAbsolutePath();
689           } catch (AccessControlException x)
690           {
691             // usually not allowed to do this in applet, so keep raw handle
692             // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
693           }
694         }
695
696         /*
697          * add this model unless it is read from a structure file we have
698          * already seen (example: 2MJW is an NMR structure with 10 models)
699          */
700         if (!mset.contains(filePath))
701         {
702           mset.add(filePath);
703           _modelFileNameMap[j] = i; // record the model index for the filename
704           j++;
705         }
706       }
707       modelFileNames = mset.toArray(new String[mset.size()]);
708     }
709     return modelFileNames;
710   }
711
712   /**
713    * map from string to applet
714    */
715   @Override
716   public Map<String, Object> getRegistryInfo()
717   {
718     // TODO Auto-generated method stub
719     return null;
720   }
721
722   /**
723    * returns the current sequenceRenderer that should be used to colour the
724    * structures
725    * 
726    * @param alignment
727    * 
728    * @return
729    */
730   public abstract SequenceRenderer getSequenceRenderer(
731           AlignmentViewPanel alignment);
732
733   // ///////////////////////////////
734   // JmolStatusListener
735
736   public void handlePopupMenu(int x, int y)
737   {
738     // jmolpopup.show(x, y);
739     // jmolpopup.jpiShow(x, y);
740   }
741
742   /**
743    * Highlight zero, one or more atoms on the structure
744    */
745   @Override
746   public void highlightAtoms(List<AtomSpec> atoms)
747   {
748     if (atoms != null)
749     {
750       if (resetLastRes.length() > 0)
751       {
752         viewer.evalStringQuiet(resetLastRes.toString());
753         resetLastRes.setLength(0);
754       }
755       for (AtomSpec atom : atoms)
756       {
757         highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
758                 atom.getChain(), atom.getPdbFile());
759       }
760     }
761   }
762
763   // jmol/ssm only
764   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
765           String pdbfile)
766   {
767     if (modelFileNames == null)
768     {
769       return;
770     }
771
772     // look up file model number for this pdbfile
773     int mdlNum = 0;
774     // may need to adjust for URLencoding here - we don't worry about that yet.
775     while (mdlNum < modelFileNames.length
776             && !pdbfile.equals(modelFileNames[mdlNum]))
777     {
778       mdlNum++;
779     }
780     if (mdlNum == modelFileNames.length)
781     {
782       return;
783     }
784
785     jmolHistory(false);
786
787     StringBuilder cmd = new StringBuilder(64);
788     cmd.append("select " + pdbResNum); // +modelNum
789
790     resetLastRes.append("select " + pdbResNum); // +modelNum
791
792     cmd.append(":");
793     resetLastRes.append(":");
794     if (!chain.equals(" "))
795     {
796       cmd.append(chain);
797       resetLastRes.append(chain);
798     }
799     {
800       cmd.append(" /" + (mdlNum + 1));
801       resetLastRes.append("/" + (mdlNum + 1));
802     }
803     cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;");
804
805     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
806             + " and not hetero; spacefill 0;");
807
808     cmd.append("spacefill 200;select none");
809
810     viewer.evalStringQuiet(cmd.toString());
811     jmolHistory(true);
812
813   }
814
815   boolean debug = true;
816
817   private void jmolHistory(boolean enable)
818   {
819     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
820   }
821
822   public void loadInline(String string)
823   {
824     loadedInline = true;
825     // TODO: re JAL-623
826     // viewer.loadInline(strModel, isAppend);
827     // could do this:
828     // construct fake fullPathName and fileName so we can identify the file
829     // later.
830     // Then, construct pass a reader for the string to Jmol.
831     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
832     // fileName, null, reader, false, null, null, 0);
833     viewer.openStringInline(string);
834   }
835
836   public void mouseOverStructure(int atomIndex, String strInfo)
837   {
838     int pdbResNum;
839     int alocsep = strInfo.indexOf("^");
840     int mdlSep = strInfo.indexOf("/");
841     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
842
843     if (chainSeparator == -1)
844     {
845       chainSeparator = strInfo.indexOf(".");
846       if (mdlSep > -1 && mdlSep < chainSeparator)
847       {
848         chainSeparator1 = chainSeparator;
849         chainSeparator = mdlSep;
850       }
851     }
852     // handle insertion codes
853     if (alocsep != -1)
854     {
855       pdbResNum = Integer.parseInt(strInfo.substring(
856               strInfo.indexOf("]") + 1, alocsep));
857
858     }
859     else
860     {
861       pdbResNum = Integer.parseInt(strInfo.substring(
862               strInfo.indexOf("]") + 1, chainSeparator));
863     }
864     String chainId;
865
866     if (strInfo.indexOf(":") > -1)
867     {
868       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
869               strInfo.indexOf("."));
870     }
871     else
872     {
873       chainId = " ";
874     }
875
876     String pdbfilename = modelFileNames[frameNo]; // default is first or current
877     // model
878     if (mdlSep > -1)
879     {
880       if (chainSeparator1 == -1)
881       {
882         chainSeparator1 = strInfo.indexOf(".", mdlSep);
883       }
884       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
885               chainSeparator1) : strInfo.substring(mdlSep + 1);
886       try
887       {
888         // recover PDB filename for the model hovered over.
889         int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
890                 .intValue() - 1;
891         while (mnumber < _modelFileNameMap[_mp])
892         {
893           _mp--;
894         }
895         pdbfilename = modelFileNames[_mp];
896         if (pdbfilename == null)
897         {
898           pdbfilename = new File(viewer.ms.getModelFileName(mnumber))
899                   .getAbsolutePath();
900         }
901
902       } catch (Exception e)
903       {
904       }
905       ;
906     }
907     if (lastMessage == null || !lastMessage.equals(strInfo))
908     {
909       getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
910     }
911
912     lastMessage = strInfo;
913   }
914
915   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
916   {
917     if (data != null)
918     {
919       System.err.println("Ignoring additional hover info: " + data
920               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
921     }
922     mouseOverStructure(atomIndex, strInfo);
923   }
924
925   /*
926    * { if (history != null && strStatus != null &&
927    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
928    * } }
929    */
930
931   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
932   {
933     /**
934      * this implements the toggle label behaviour copied from the original
935      * structure viewer, MCView
936      */
937     if (strData != null)
938     {
939       System.err.println("Ignoring additional pick data string " + strData);
940     }
941     int chainSeparator = strInfo.indexOf(":");
942     int p = 0;
943     if (chainSeparator == -1)
944     {
945       chainSeparator = strInfo.indexOf(".");
946     }
947
948     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
949             chainSeparator);
950     String mdlString = "";
951     if ((p = strInfo.indexOf(":")) > -1)
952     {
953       picked += strInfo.substring(p, strInfo.indexOf("."));
954     }
955
956     if ((p = strInfo.indexOf("/")) > -1)
957     {
958       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
959     }
960     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
961             + mdlString + "))";
962     jmolHistory(false);
963
964     if (!atomsPicked.contains(picked))
965     {
966       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
967       atomsPicked.addElement(picked);
968     }
969     else
970     {
971       viewer.evalString("select " + picked + ";label off");
972       atomsPicked.removeElement(picked);
973     }
974     jmolHistory(true);
975     // TODO: in application this happens
976     //
977     // if (scriptWindow != null)
978     // {
979     // scriptWindow.sendConsoleMessage(strInfo);
980     // scriptWindow.sendConsoleMessage("\n");
981     // }
982
983   }
984
985   @Override
986   public void notifyCallback(CBK type, Object[] data)
987   {
988     try
989     {
990       switch (type)
991       {
992       case LOADSTRUCT:
993         notifyFileLoaded((String) data[1], (String) data[2],
994                 (String) data[3], (String) data[4],
995                 ((Integer) data[5]).intValue());
996
997         break;
998       case PICK:
999         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
1000                 (String) data[0]);
1001         // also highlight in alignment
1002       case HOVER:
1003         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
1004                 (String) data[0]);
1005         break;
1006       case SCRIPT:
1007         notifyScriptTermination((String) data[2],
1008                 ((Integer) data[3]).intValue());
1009         break;
1010       case ECHO:
1011         sendConsoleEcho((String) data[1]);
1012         break;
1013       case MESSAGE:
1014         sendConsoleMessage((data == null) ? ((String) null)
1015                 : (String) data[1]);
1016         break;
1017       case ERROR:
1018         // System.err.println("Ignoring error callback.");
1019         break;
1020       case SYNC:
1021       case RESIZE:
1022         refreshGUI();
1023         break;
1024       case MEASURE:
1025
1026       case CLICK:
1027       default:
1028         System.err.println("Unhandled callback " + type + " "
1029                 + data[1].toString());
1030         break;
1031       }
1032     } catch (Exception e)
1033     {
1034       System.err.println("Squashed Jmol callback handler error:");
1035       e.printStackTrace();
1036     }
1037   }
1038
1039   @Override
1040   public boolean notifyEnabled(CBK callbackPick)
1041   {
1042     switch (callbackPick)
1043     {
1044     case ECHO:
1045     case LOADSTRUCT:
1046     case MEASURE:
1047     case MESSAGE:
1048     case PICK:
1049     case SCRIPT:
1050     case HOVER:
1051     case ERROR:
1052       return true;
1053     default:
1054       return false;
1055     }
1056   }
1057
1058   // incremented every time a load notification is successfully handled -
1059   // lightweight mechanism for other threads to detect when they can start
1060   // referrring to new structures.
1061   private long loadNotifiesHandled = 0;
1062
1063   public long getLoadNotifiesHandled()
1064   {
1065     return loadNotifiesHandled;
1066   }
1067
1068   public void notifyFileLoaded(String fullPathName, String fileName2,
1069           String modelName, String errorMsg, int modelParts)
1070   {
1071     if (errorMsg != null)
1072     {
1073       fileLoadingError = errorMsg;
1074       refreshGUI();
1075       return;
1076     }
1077     // TODO: deal sensibly with models loaded inLine:
1078     // modelName will be null, as will fullPathName.
1079
1080     // the rest of this routine ignores the arguments, and simply interrogates
1081     // the Jmol view to find out what structures it contains, and adds them to
1082     // the structure selection manager.
1083     fileLoadingError = null;
1084     String[] oldmodels = modelFileNames;
1085     modelFileNames = null;
1086     chainNames = new Vector<String>();
1087     chainFile = new Hashtable<String, String>();
1088     boolean notifyLoaded = false;
1089     String[] modelfilenames = getPdbFile();
1090     // first check if we've lost any structures
1091     if (oldmodels != null && oldmodels.length > 0)
1092     {
1093       int oldm = 0;
1094       for (int i = 0; i < oldmodels.length; i++)
1095       {
1096         for (int n = 0; n < modelfilenames.length; n++)
1097         {
1098           if (modelfilenames[n] == oldmodels[i])
1099           {
1100             oldmodels[i] = null;
1101             break;
1102           }
1103         }
1104         if (oldmodels[i] != null)
1105         {
1106           oldm++;
1107         }
1108       }
1109       if (oldm > 0)
1110       {
1111         String[] oldmfn = new String[oldm];
1112         oldm = 0;
1113         for (int i = 0; i < oldmodels.length; i++)
1114         {
1115           if (oldmodels[i] != null)
1116           {
1117             oldmfn[oldm++] = oldmodels[i];
1118           }
1119         }
1120         // deregister the Jmol instance for these structures - we'll add
1121         // ourselves again at the end for the current structure set.
1122         getSsm().removeStructureViewerListener(this, oldmfn);
1123       }
1124     }
1125     refreshPdbEntries();
1126     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1127     {
1128       String fileName = modelfilenames[modelnum];
1129       boolean foundEntry = false;
1130       StructureFile pdb = null;
1131       String pdbfile = null;
1132       // model was probably loaded inline - so check the pdb file hashcode
1133       if (loadedInline)
1134       {
1135         // calculate essential attributes for the pdb data imported inline.
1136         // prolly need to resolve modelnumber properly - for now just use our
1137         // 'best guess'
1138         pdbfile = viewer.getData("" + (1 + _modelFileNameMap[modelnum])
1139                 + ".0", "PDB");
1140       }
1141       // search pdbentries and sequences to find correct pdbentry for this
1142       // model
1143       for (int pe = 0; pe < getPdbCount(); pe++)
1144       {
1145         boolean matches = false;
1146         if (fileName == null)
1147         {
1148           if (false)
1149           // see JAL-623 - need method of matching pasted data up
1150           {
1151             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
1152                     pdbfile, AppletFormatAdapter.PASTE);
1153             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
1154             matches = true;
1155             foundEntry = true;
1156           }
1157         }
1158         else
1159         {
1160           File fl = new File(getPdbEntry(pe).getFile());
1161           matches = fl.equals(new File(fileName));
1162           if (matches)
1163           {
1164             foundEntry = true;
1165             // TODO: Jmol can in principle retrieve from CLASSLOADER but
1166             // this
1167             // needs
1168             // to be tested. See mantis bug
1169             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1170             String protocol = AppletFormatAdapter.URL;
1171             try
1172             {
1173               if (fl.exists())
1174               {
1175                 protocol = AppletFormatAdapter.FILE;
1176               }
1177             } catch (Exception e)
1178             {
1179             } catch (Error e)
1180             {
1181             }
1182             // Explicitly map to the filename used by Jmol ;
1183             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
1184                     fileName, protocol);
1185             // pdbentry[pe].getFile(), protocol);
1186
1187           }
1188         }
1189         if (matches)
1190         {
1191           // add an entry for every chain in the model
1192           for (int i = 0; i < pdb.getChains().size(); i++)
1193           {
1194             String chid = new String(pdb.getId() + ":"
1195                     + pdb.getChains().elementAt(i).id);
1196             chainFile.put(chid, fileName);
1197             chainNames.addElement(chid);
1198           }
1199           notifyLoaded = true;
1200         }
1201       }
1202
1203       if (!foundEntry && associateNewStructs)
1204       {
1205         // this is a foreign pdb file that jalview doesn't know about - add
1206         // it to the dataset and try to find a home - either on a matching
1207         // sequence or as a new sequence.
1208         String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1209                 "PDB");
1210         // parse pdb file into a chain, etc.
1211         // locate best match for pdb in associated views and add mapping to
1212         // ssm
1213         // if properly registered then
1214         notifyLoaded = true;
1215
1216       }
1217     }
1218     // FILE LOADED OK
1219     // so finally, update the jmol bits and pieces
1220     // if (jmolpopup != null)
1221     // {
1222     // // potential for deadlock here:
1223     // // jmolpopup.updateComputedMenus();
1224     // }
1225     if (!isLoadingFromArchive())
1226     {
1227       viewer.evalStringQuiet("model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
1228     }
1229     // register ourselves as a listener and notify the gui that it needs to
1230     // update itself.
1231     getSsm().addStructureViewerListener(this);
1232     if (notifyLoaded)
1233     {
1234       FeatureRenderer fr = getFeatureRenderer(null);
1235       if (fr != null)
1236       {
1237         fr.featuresAdded();
1238       }
1239       refreshGUI();
1240       loadNotifiesHandled++;
1241     }
1242     setLoadingFromArchive(false);
1243   }
1244
1245   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1246   {
1247     notifyAtomPicked(iatom, strMeasure, null);
1248   }
1249
1250   public abstract void notifyScriptTermination(String strStatus,
1251           int msWalltime);
1252
1253   /**
1254    * display a message echoed from the jmol viewer
1255    * 
1256    * @param strEcho
1257    */
1258   public abstract void sendConsoleEcho(String strEcho); /*
1259                                                          * { showConsole(true);
1260                                                          * 
1261                                                          * history.append("\n" +
1262                                                          * strEcho); }
1263                                                          */
1264
1265   // /End JmolStatusListener
1266   // /////////////////////////////
1267
1268   /**
1269    * @param strStatus
1270    *          status message - usually the response received after a script
1271    *          executed
1272    */
1273   public abstract void sendConsoleMessage(String strStatus);
1274
1275   @Override
1276   public void setCallbackFunction(String callbackType,
1277           String callbackFunction)
1278   {
1279     System.err.println("Ignoring set-callback request to associate "
1280             + callbackType + " with function " + callbackFunction);
1281
1282   }
1283
1284   public void setJalviewColourScheme(ColourSchemeI cs)
1285   {
1286     colourBySequence = false;
1287
1288     if (cs == null)
1289     {
1290       return;
1291     }
1292
1293     jmolHistory(false);
1294     StringBuilder command = new StringBuilder(128);
1295     command.append("select *;color white;");
1296     List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
1297             false);
1298     for (String res : residueSet)
1299     {
1300       Color col = cs.findColour(res.charAt(0));
1301       command.append("select " + res + ";color[" + col.getRed() + ","
1302               + col.getGreen() + "," + col.getBlue() + "];");
1303     }
1304
1305     evalStateCommand(command.toString());
1306     jmolHistory(true);
1307   }
1308
1309   public void showHelp()
1310   {
1311     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1312   }
1313
1314   /**
1315    * open the URL somehow
1316    * 
1317    * @param target
1318    */
1319   public abstract void showUrl(String url, String target);
1320
1321   /**
1322    * called when the binding thinks the UI needs to be refreshed after a Jmol
1323    * state change. this could be because structures were loaded, or because an
1324    * error has occured.
1325    */
1326   public abstract void refreshGUI();
1327
1328   /**
1329    * called to show or hide the associated console window container.
1330    * 
1331    * @param show
1332    */
1333   public abstract void showConsole(boolean show);
1334
1335   /**
1336    * @param renderPanel
1337    * @param jmolfileio
1338    *          - when true will initialise jmol's file IO system (should be false
1339    *          in applet context)
1340    * @param htmlName
1341    * @param documentBase
1342    * @param codeBase
1343    * @param commandOptions
1344    */
1345   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1346           String htmlName, URL documentBase, URL codeBase,
1347           String commandOptions)
1348   {
1349     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
1350             codeBase, commandOptions, null, null);
1351   }
1352
1353   /**
1354    * 
1355    * @param renderPanel
1356    * @param jmolfileio
1357    *          - when true will initialise jmol's file IO system (should be false
1358    *          in applet context)
1359    * @param htmlName
1360    * @param documentBase
1361    * @param codeBase
1362    * @param commandOptions
1363    * @param consolePanel
1364    *          - panel to contain Jmol console
1365    * @param buttonsToShow
1366    *          - buttons to show on the console, in ordr
1367    */
1368   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1369           String htmlName, URL documentBase, URL codeBase,
1370           String commandOptions, final Container consolePanel,
1371           String buttonsToShow)
1372   {
1373     if (commandOptions == null)
1374     {
1375       commandOptions = "";
1376     }
1377     viewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
1378             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1379                     + ((Object) this).toString(), documentBase, codeBase,
1380             commandOptions, this);
1381
1382     viewer.setJmolStatusListener(this); // extends JmolCallbackListener
1383
1384     console = createJmolConsole(consolePanel, buttonsToShow);
1385     if (consolePanel != null)
1386     {
1387       consolePanel.addComponentListener(this);
1388
1389     }
1390
1391   }
1392
1393   protected abstract JmolAppConsoleInterface createJmolConsole(
1394           Container consolePanel, String buttonsToShow);
1395
1396   protected org.jmol.api.JmolAppConsoleInterface console = null;
1397
1398   public void setBackgroundColour(java.awt.Color col)
1399   {
1400     jmolHistory(false);
1401     viewer.evalStringQuiet("background [" + col.getRed() + ","
1402             + col.getGreen() + "," + col.getBlue() + "];");
1403     jmolHistory(true);
1404   }
1405
1406   @Override
1407   public Dimension resizeInnerPanel(String data)
1408   {
1409     // Jalview doesn't honour resize panel requests
1410     return null;
1411   }
1412
1413   /**
1414    * 
1415    */
1416   protected void closeConsole()
1417   {
1418     if (console != null)
1419     {
1420       try
1421       {
1422         console.setVisible(false);
1423       } catch (Error e)
1424       {
1425       } catch (Exception x)
1426       {
1427       }
1428       ;
1429       console = null;
1430     }
1431   }
1432
1433   /**
1434    * ComponentListener method
1435    */
1436   @Override
1437   public void componentMoved(ComponentEvent e)
1438   {
1439   }
1440
1441   /**
1442    * ComponentListener method
1443    */
1444   @Override
1445   public void componentResized(ComponentEvent e)
1446   {
1447   }
1448
1449   /**
1450    * ComponentListener method
1451    */
1452   @Override
1453   public void componentShown(ComponentEvent e)
1454   {
1455     showConsole(true);
1456   }
1457
1458   /**
1459    * ComponentListener method
1460    */
1461   @Override
1462   public void componentHidden(ComponentEvent e)
1463   {
1464     showConsole(false);
1465   }
1466 }