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