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