JAL-1750 View Mapping scoped to that viewer's mappings only
[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.StructureMapping;
54 import jalview.structure.StructureMappingcommandSet;
55 import jalview.structure.StructureSelectionManager;
56 import jalview.structures.models.AAStructureBindingModel;
57 import jalview.util.MessageManager;
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         // conformation=1 excludes alternate locations for CA (JAL-1757)
511         command.append(".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
512
513         // form the matched pair strings
514         String sep = "";
515         for (int s = 0; s < 2; s++)
516         {
517           command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
518         }
519         command.append(" ROTATE TRANSLATE;\n");
520       }
521       if (selectioncom.length() > 0)
522       {
523         System.out.println("Select regions:\n" + selectioncom.toString());
524         evalStateCommand("select *; cartoons off; backbone; select ("
525                 + selectioncom.toString() + "); cartoons; ");
526         // selcom.append("; ribbons; ");
527         System.out
528                 .println("Superimpose command(s):\n" + command.toString());
529
530         evalStateCommand(command.toString());
531       }
532     }
533     if (selectioncom.length() > 0)
534     {// finally, mark all regions that were superposed.
535       if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
536       {
537         selectioncom.setLength(selectioncom.length() - 1);
538       }
539       System.out.println("Select regions:\n" + selectioncom.toString());
540       evalStateCommand("select *; cartoons off; backbone; select ("
541               + selectioncom.toString() + "); cartoons; ");
542       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
543     }
544   }
545
546   public void evalStateCommand(String command)
547   {
548     jmolHistory(false);
549     if (lastCommand == null || !lastCommand.equals(command))
550     {
551       viewer.evalStringQuiet(command + "\n");
552     }
553     jmolHistory(true);
554     lastCommand = command;
555   }
556
557   /**
558    * colour any structures associated with sequences in the given alignment
559    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
560    * if colourBySequence is enabled.
561    */
562   public void colourBySequence(boolean showFeatures,
563           jalview.api.AlignmentViewPanel alignmentv)
564   {
565     if (!colourBySequence || !isLoadingFinished())
566     {
567       return;
568     }
569     if (getSsm() == null)
570     {
571       return;
572     }
573     String[] files = getPdbFile();
574
575     SequenceRenderer sr = getSequenceRenderer(alignmentv);
576
577     FeatureRenderer fr = null;
578     if (showFeatures)
579     {
580       fr = getFeatureRenderer(alignmentv);
581     }
582     AlignmentI alignment = alignmentv.getAlignment();
583
584     for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(files, sr, fr, alignment))
585     {
586       for (String cbyseq : cpdbbyseq.commands)
587       {
588         executeWhenReady(cbyseq);
589       }
590     }
591   }
592
593   /**
594    * @param files
595    * @param sr
596    * @param fr
597    * @param alignment
598    * @return
599    */
600   protected StructureMappingcommandSet[] getColourBySequenceCommands(
601           String[] files, SequenceRenderer sr, FeatureRenderer fr,
602           AlignmentI alignment)
603   {
604     return JmolCommands
605             .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
606                     fr,
607                     alignment);
608   }
609
610   /**
611    * @param command
612    */
613   protected void executeWhenReady(String command)
614   {
615     evalStateCommand(command);
616   }
617
618   public void createImage(String file, String type, int quality)
619   {
620     System.out.println("JMOL CREATE IMAGE");
621   }
622
623   public String createImage(String fileName, String type,
624           Object textOrBytes, int quality)
625   {
626     System.out.println("JMOL CREATE IMAGE");
627     return null;
628   }
629
630   public String eval(String strEval)
631   {
632     // System.out.println(strEval);
633     // "# 'eval' is implemented only for the applet.";
634     return null;
635   }
636
637   // End StructureListener
638   // //////////////////////////
639
640   public float[][] functionXY(String functionName, int x, int y)
641   {
642     return null;
643   }
644
645   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
646   {
647     // TODO Auto-generated method stub
648     return null;
649   }
650
651   public Color getColour(int atomIndex, int pdbResNum, String chain,
652           String pdbfile)
653   {
654     if (getModelNum(pdbfile) < 0)
655     {
656       return null;
657     }
658     // TODO: verify atomIndex is selecting correct model.
659     return new Color(viewer.getAtomArgb(atomIndex));
660   }
661
662   /**
663    * returns the current featureRenderer that should be used to colour the
664    * structures
665    * 
666    * @param alignment
667    * 
668    * @return
669    */
670   public abstract FeatureRenderer getFeatureRenderer(
671           AlignmentViewPanel alignment);
672
673   /**
674    * instruct the Jalview binding to update the pdbentries vector if necessary
675    * prior to matching the jmol view's contents to the list of structure files
676    * Jalview knows about.
677    */
678   public abstract void refreshPdbEntries();
679
680   private int getModelNum(String modelFileName)
681   {
682     String[] mfn = getPdbFile();
683     if (mfn == null)
684     {
685       return -1;
686     }
687     for (int i = 0; i < mfn.length; i++)
688     {
689       if (mfn[i].equalsIgnoreCase(modelFileName))
690       {
691         return i;
692       }
693     }
694     return -1;
695   }
696
697   /**
698    * map between index of model filename returned from getPdbFile and the first
699    * index of models from this file in the viewer. Note - this is not trimmed -
700    * use getPdbFile to get number of unique models.
701    */
702   private int _modelFileNameMap[];
703
704   // ////////////////////////////////
705   // /StructureListener
706   public synchronized String[] getPdbFile()
707   {
708     if (viewer == null)
709     {
710       return new String[0];
711     }
712     if (modelFileNames == null)
713     {
714
715       String mset[] = new String[viewer.getModelCount()];
716       _modelFileNameMap = new int[mset.length];
717       int j = 1;
718       String m = viewer.getModelFileName(0);
719       if (m != null)
720       {
721         try
722         {
723           mset[0] = new File(m).getAbsolutePath();
724         } catch (AccessControlException x)
725         {
726           // usually not allowed to do this in applet, so keep raw handle
727           mset[0] = m;
728           // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
729         }
730       }
731       for (int i = 1; i < mset.length; i++)
732       {
733         m = viewer.getModelFileName(i);
734         if (m != null)
735         {
736           try
737           {
738             mset[j] = new File(m).getAbsolutePath();
739           } catch (AccessControlException x)
740           {
741             // usually not allowed to do this in applet, so keep raw handle
742             mset[j] = m;
743             // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
744           }
745         }
746         _modelFileNameMap[j] = i; // record the model index for the filename
747         // skip any additional models in the same file (NMR structures)
748         if ((mset[j] == null ? mset[j] != mset[j - 1]
749                 : (mset[j - 1] == null || !mset[j].equals(mset[j - 1]))))
750         {
751           j++;
752         }
753       }
754       modelFileNames = new String[j];
755       System.arraycopy(mset, 0, modelFileNames, 0, j);
756     }
757     return modelFileNames;
758   }
759
760   /**
761    * map from string to applet
762    */
763   @Override
764   public Map<String, Object> getRegistryInfo()
765   {
766     // TODO Auto-generated method stub
767     return null;
768   }
769
770   /**
771    * returns the current sequenceRenderer that should be used to colour the
772    * structures
773    * 
774    * @param alignment
775    * 
776    * @return
777    */
778   public abstract SequenceRenderer getSequenceRenderer(
779           AlignmentViewPanel alignment);
780
781   // ///////////////////////////////
782   // JmolStatusListener
783
784   public void handlePopupMenu(int x, int y)
785   {
786     jmolpopup.show(x, y);
787   }
788
789   // jmol/ssm only
790   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
791           String pdbfile)
792   {
793     if (modelFileNames == null)
794     {
795       return;
796     }
797
798     // look up file model number for this pdbfile
799     int mdlNum = 0;
800     String fn;
801     // may need to adjust for URLencoding here - we don't worry about that yet.
802     while (mdlNum < modelFileNames.length
803             && !pdbfile.equals(modelFileNames[mdlNum]))
804     {
805       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
806       mdlNum++;
807     }
808     if (mdlNum == modelFileNames.length)
809     {
810       return;
811     }
812
813     jmolHistory(false);
814     // if (!pdbfile.equals(pdbentry.getFile()))
815     // return;
816     if (resetLastRes.length() > 0)
817     {
818       viewer.evalStringQuiet(resetLastRes.toString());
819     }
820
821     eval.setLength(0);
822     eval.append("select " + pdbResNum); // +modelNum
823
824     resetLastRes.setLength(0);
825     resetLastRes.append("select " + pdbResNum); // +modelNum
826
827     eval.append(":");
828     resetLastRes.append(":");
829     if (!chain.equals(" "))
830     {
831       eval.append(chain);
832       resetLastRes.append(chain);
833     }
834     {
835       eval.append(" /" + (mdlNum + 1));
836       resetLastRes.append("/" + (mdlNum + 1));
837     }
838     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
839
840     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
841             + " and not hetero; spacefill 0;");
842
843     eval.append("spacefill 200;select none");
844
845     viewer.evalStringQuiet(eval.toString());
846     jmolHistory(true);
847
848   }
849
850   boolean debug = true;
851
852   private void jmolHistory(boolean enable)
853   {
854     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
855   }
856
857   public void loadInline(String string)
858   {
859     loadedInline = true;
860     // TODO: re JAL-623
861     // viewer.loadInline(strModel, isAppend);
862     // could do this:
863     // construct fake fullPathName and fileName so we can identify the file
864     // later.
865     // Then, construct pass a reader for the string to Jmol.
866     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
867     // fileName, null, reader, false, null, null, 0);
868     viewer.openStringInline(string);
869   }
870
871   public void mouseOverStructure(int atomIndex, String strInfo)
872   {
873     int pdbResNum;
874     int alocsep = strInfo.indexOf("^");
875     int mdlSep = strInfo.indexOf("/");
876     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
877
878     if (chainSeparator == -1)
879     {
880       chainSeparator = strInfo.indexOf(".");
881       if (mdlSep > -1 && mdlSep < chainSeparator)
882       {
883         chainSeparator1 = chainSeparator;
884         chainSeparator = mdlSep;
885       }
886     }
887     // handle insertion codes
888     if (alocsep != -1)
889     {
890       pdbResNum = Integer.parseInt(strInfo.substring(
891               strInfo.indexOf("]") + 1, alocsep));
892
893     }
894     else
895     {
896       pdbResNum = Integer.parseInt(strInfo.substring(
897               strInfo.indexOf("]") + 1, chainSeparator));
898     }
899     String chainId;
900
901     if (strInfo.indexOf(":") > -1)
902     {
903       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
904               strInfo.indexOf("."));
905     }
906     else
907     {
908       chainId = " ";
909     }
910
911     String pdbfilename = modelFileNames[frameNo]; // default is first or current
912     // model
913     if (mdlSep > -1)
914     {
915       if (chainSeparator1 == -1)
916       {
917         chainSeparator1 = strInfo.indexOf(".", mdlSep);
918       }
919       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
920               chainSeparator1) : strInfo.substring(mdlSep + 1);
921       try
922       {
923         // recover PDB filename for the model hovered over.
924         int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
925                 .intValue() - 1;
926         while (mnumber < _modelFileNameMap[_mp])
927         {
928           _mp--;
929         }
930         pdbfilename = modelFileNames[_mp];
931         if (pdbfilename == null)
932         {
933           pdbfilename = new File(viewer.getModelFileName(mnumber))
934                   .getAbsolutePath();
935         }
936
937       } catch (Exception e)
938       {
939       }
940       ;
941     }
942     if (lastMessage == null || !lastMessage.equals(strInfo))
943     {
944       getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
945     }
946
947     lastMessage = strInfo;
948   }
949
950   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
951   {
952     if (data != null)
953     {
954       System.err.println("Ignoring additional hover info: " + data
955               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
956     }
957     mouseOverStructure(atomIndex, strInfo);
958   }
959
960   /*
961    * { if (history != null && strStatus != null &&
962    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
963    * } }
964    */
965
966   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
967   {
968     /**
969      * this implements the toggle label behaviour copied from the original
970      * structure viewer, MCView
971      */
972     if (strData != null)
973     {
974       System.err.println("Ignoring additional pick data string " + strData);
975     }
976     int chainSeparator = strInfo.indexOf(":");
977     int p = 0;
978     if (chainSeparator == -1)
979     {
980       chainSeparator = strInfo.indexOf(".");
981     }
982
983     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
984             chainSeparator);
985     String mdlString = "";
986     if ((p = strInfo.indexOf(":")) > -1)
987     {
988       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
989     }
990
991     if ((p = strInfo.indexOf("/")) > -1)
992     {
993       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
994     }
995     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
996             + mdlString + "))";
997     jmolHistory(false);
998
999     if (!atomsPicked.contains(picked))
1000     {
1001       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
1002       atomsPicked.addElement(picked);
1003     }
1004     else
1005     {
1006       viewer.evalString("select " + picked + ";label off");
1007       atomsPicked.removeElement(picked);
1008     }
1009     jmolHistory(true);
1010     // TODO: in application this happens
1011     //
1012     // if (scriptWindow != null)
1013     // {
1014     // scriptWindow.sendConsoleMessage(strInfo);
1015     // scriptWindow.sendConsoleMessage("\n");
1016     // }
1017
1018   }
1019
1020   @Override
1021   public void notifyCallback(EnumCallback type, Object[] data)
1022   {
1023     try
1024     {
1025       switch (type)
1026       {
1027       case LOADSTRUCT:
1028         notifyFileLoaded((String) data[1], (String) data[2],
1029                 (String) data[3], (String) data[4],
1030                 ((Integer) data[5]).intValue());
1031
1032         break;
1033       case PICK:
1034         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
1035                 (String) data[0]);
1036         // also highlight in alignment
1037       case HOVER:
1038         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
1039                 (String) data[0]);
1040         break;
1041       case SCRIPT:
1042         notifyScriptTermination((String) data[2],
1043                 ((Integer) data[3]).intValue());
1044         break;
1045       case ECHO:
1046         sendConsoleEcho((String) data[1]);
1047         break;
1048       case MESSAGE:
1049         sendConsoleMessage((data == null) ? ((String) null)
1050                 : (String) data[1]);
1051         break;
1052       case ERROR:
1053         // System.err.println("Ignoring error callback.");
1054         break;
1055       case SYNC:
1056       case RESIZE:
1057         refreshGUI();
1058         break;
1059       case MEASURE:
1060
1061       case CLICK:
1062       default:
1063         System.err.println("Unhandled callback " + type + " "
1064                 + data[1].toString());
1065         break;
1066       }
1067     } catch (Exception e)
1068     {
1069       System.err.println("Squashed Jmol callback handler error:");
1070       e.printStackTrace();
1071     }
1072   }
1073
1074   @Override
1075   public boolean notifyEnabled(EnumCallback callbackPick)
1076   {
1077     switch (callbackPick)
1078     {
1079     case ECHO:
1080     case LOADSTRUCT:
1081     case MEASURE:
1082     case MESSAGE:
1083     case PICK:
1084     case SCRIPT:
1085     case HOVER:
1086     case ERROR:
1087       return true;
1088     case RESIZE:
1089     case SYNC:
1090     case CLICK:
1091     case ANIMFRAME:
1092     case MINIMIZATION:
1093     }
1094     return false;
1095   }
1096
1097   // incremented every time a load notification is successfully handled -
1098   // lightweight mechanism for other threads to detect when they can start
1099   // referrring to new structures.
1100   private long loadNotifiesHandled = 0;
1101
1102   public long getLoadNotifiesHandled()
1103   {
1104     return loadNotifiesHandled;
1105   }
1106
1107   public void notifyFileLoaded(String fullPathName, String fileName2,
1108           String modelName, String errorMsg, int modelParts)
1109   {
1110     if (errorMsg != null)
1111     {
1112       fileLoadingError = errorMsg;
1113       refreshGUI();
1114       return;
1115     }
1116     // TODO: deal sensibly with models loaded inLine:
1117     // modelName will be null, as will fullPathName.
1118
1119     // the rest of this routine ignores the arguments, and simply interrogates
1120     // the Jmol view to find out what structures it contains, and adds them to
1121     // the structure selection manager.
1122     fileLoadingError = null;
1123     String[] oldmodels = modelFileNames;
1124     modelFileNames = null;
1125     chainNames = new Vector();
1126     chainFile = new Hashtable();
1127     boolean notifyLoaded = false;
1128     String[] modelfilenames = getPdbFile();
1129     // first check if we've lost any structures
1130     if (oldmodels != null && oldmodels.length > 0)
1131     {
1132       int oldm = 0;
1133       for (int i = 0; i < oldmodels.length; i++)
1134       {
1135         for (int n = 0; n < modelfilenames.length; n++)
1136         {
1137           if (modelfilenames[n] == oldmodels[i])
1138           {
1139             oldmodels[i] = null;
1140             break;
1141           }
1142         }
1143         if (oldmodels[i] != null)
1144         {
1145           oldm++;
1146         }
1147       }
1148       if (oldm > 0)
1149       {
1150         String[] oldmfn = new String[oldm];
1151         oldm = 0;
1152         for (int i = 0; i < oldmodels.length; i++)
1153         {
1154           if (oldmodels[i] != null)
1155           {
1156             oldmfn[oldm++] = oldmodels[i];
1157           }
1158         }
1159         // deregister the Jmol instance for these structures - we'll add
1160         // ourselves again at the end for the current structure set.
1161         getSsm().removeStructureViewerListener(this, oldmfn);
1162       }
1163     }
1164     refreshPdbEntries();
1165     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1166     {
1167       String fileName = modelfilenames[modelnum];
1168       boolean foundEntry = false;
1169       MCview.PDBfile pdb = null;
1170       String pdbfile = null, pdbfhash = null;
1171       // model was probably loaded inline - so check the pdb file hashcode
1172       if (loadedInline)
1173       {
1174         // calculate essential attributes for the pdb data imported inline.
1175         // prolly need to resolve modelnumber properly - for now just use our
1176         // 'best guess'
1177         pdbfile = viewer.getData("" + (1 + _modelFileNameMap[modelnum])
1178                 + ".0", "PDB");
1179         pdbfhash = "" + pdbfile.hashCode();
1180       }
1181         // search pdbentries and sequences to find correct pdbentry for this
1182         // model
1183       for (int pe = 0; pe < getPdbCount(); pe++)
1184       {
1185         boolean matches = false;
1186         if (fileName == null)
1187         {
1188           if (false)
1189           // see JAL-623 - need method of matching pasted data up
1190           {
1191             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
1192                     pdbfile, AppletFormatAdapter.PASTE);
1193             getPdbEntry(modelnum).setFile("INLINE" + pdb.id);
1194             matches = true;
1195             foundEntry = true;
1196           }
1197         }
1198         else
1199         {
1200           File fl;
1201           if (matches = (fl = new File(getPdbEntry(pe).getFile()))
1202                   .equals(new File(fileName)))
1203           {
1204             foundEntry = true;
1205             // TODO: Jmol can in principle retrieve from CLASSLOADER but
1206             // this
1207             // needs
1208             // to be tested. See mantis bug
1209             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1210             String protocol = AppletFormatAdapter.URL;
1211             try
1212             {
1213               if (fl.exists())
1214               {
1215                 protocol = AppletFormatAdapter.FILE;
1216               }
1217             } catch (Exception e)
1218             {
1219             } catch (Error e)
1220             {
1221             }
1222             // Explicitly map to the filename used by Jmol ;
1223             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
1224                     fileName, protocol);
1225             // pdbentry[pe].getFile(), protocol);
1226
1227           }
1228         }
1229         if (matches)
1230         {
1231           // add an entry for every chain in the model
1232           for (int i = 0; i < pdb.chains.size(); i++)
1233           {
1234             String chid = new String(pdb.id + ":"
1235                     + pdb.chains.elementAt(i).id);
1236             chainFile.put(chid, fileName);
1237             chainNames.addElement(chid);
1238           }
1239           notifyLoaded = true;
1240         }
1241       }
1242
1243       if (!foundEntry && associateNewStructs)
1244       {
1245         // this is a foreign pdb file that jalview doesn't know about - add
1246         // it to the dataset and try to find a home - either on a matching
1247         // sequence or as a new sequence.
1248         String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1249                 "PDB");
1250         // parse pdb file into a chain, etc.
1251         // locate best match for pdb in associated views and add mapping to
1252         // ssm
1253         // if properly registered then
1254         notifyLoaded = true;
1255
1256       }
1257     }
1258     // FILE LOADED OK
1259     // so finally, update the jmol bits and pieces
1260     if (jmolpopup != null)
1261     {
1262       // potential for deadlock here:
1263       // jmolpopup.updateComputedMenus();
1264     }
1265     if (!isLoadingFromArchive())
1266     {
1267       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
1268     }
1269     // register ourselves as a listener and notify the gui that it needs to
1270     // update itself.
1271     getSsm().addStructureViewerListener(this);
1272     if (notifyLoaded)
1273     {
1274       FeatureRenderer fr = getFeatureRenderer(null);
1275       if (fr != null)
1276       {
1277         fr.featuresAdded();
1278       }
1279       refreshGUI();
1280       loadNotifiesHandled++;
1281     }
1282     setLoadingFromArchive(false);
1283   }
1284
1285   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1286   {
1287     notifyAtomPicked(iatom, strMeasure, null);
1288   }
1289
1290   public abstract void notifyScriptTermination(String strStatus,
1291           int msWalltime);
1292
1293   /**
1294    * display a message echoed from the jmol viewer
1295    * 
1296    * @param strEcho
1297    */
1298   public abstract void sendConsoleEcho(String strEcho); /*
1299                                                          * { showConsole(true);
1300                                                          * 
1301                                                          * history.append("\n" +
1302                                                          * strEcho); }
1303                                                          */
1304
1305   // /End JmolStatusListener
1306   // /////////////////////////////
1307
1308   /**
1309    * @param strStatus
1310    *          status message - usually the response received after a script
1311    *          executed
1312    */
1313   public abstract void sendConsoleMessage(String strStatus);
1314
1315   public void setCallbackFunction(String callbackType,
1316           String callbackFunction)
1317   {
1318     System.err.println("Ignoring set-callback request to associate "
1319             + callbackType + " with function " + callbackFunction);
1320
1321   }
1322
1323   public void setJalviewColourScheme(ColourSchemeI cs)
1324   {
1325     colourBySequence = false;
1326
1327     if (cs == null)
1328     {
1329       return;
1330     }
1331
1332     jmolHistory(false);
1333     StringBuilder command = new StringBuilder(128);
1334     command.append("select *;color white;");
1335     List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
1336             false);
1337     for (String res : residueSet)
1338     {
1339       Color col = cs.findColour(res.charAt(0));
1340       command.append("select " + res + ";color[" + col.getRed() + ","
1341               + col.getGreen() + "," + col.getBlue() + "];");
1342     }
1343
1344     evalStateCommand(command.toString());
1345     jmolHistory(true);
1346   }
1347
1348   public void showHelp()
1349   {
1350     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1351   }
1352
1353   /**
1354    * open the URL somehow
1355    * 
1356    * @param target
1357    */
1358   public abstract void showUrl(String url, String target);
1359
1360   /**
1361    * called when the binding thinks the UI needs to be refreshed after a Jmol
1362    * state change. this could be because structures were loaded, or because an
1363    * error has occured.
1364    */
1365   public abstract void refreshGUI();
1366
1367   /**
1368    * called to show or hide the associated console window container.
1369    * 
1370    * @param show
1371    */
1372   public abstract void showConsole(boolean show);
1373
1374   /**
1375    * @param renderPanel
1376    * @param jmolfileio
1377    *          - when true will initialise jmol's file IO system (should be false
1378    *          in applet context)
1379    * @param htmlName
1380    * @param documentBase
1381    * @param codeBase
1382    * @param commandOptions
1383    */
1384   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1385           String htmlName, URL documentBase, URL codeBase,
1386           String commandOptions)
1387   {
1388     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
1389             codeBase, commandOptions, null, null);
1390   }
1391
1392   /**
1393    * 
1394    * @param renderPanel
1395    * @param jmolfileio
1396    *          - when true will initialise jmol's file IO system (should be false
1397    *          in applet context)
1398    * @param htmlName
1399    * @param documentBase
1400    * @param codeBase
1401    * @param commandOptions
1402    * @param consolePanel
1403    *          - panel to contain Jmol console
1404    * @param buttonsToShow
1405    *          - buttons to show on the console, in ordr
1406    */
1407   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1408           String htmlName, URL documentBase, URL codeBase,
1409           String commandOptions, final Container consolePanel,
1410           String buttonsToShow)
1411   {
1412     if (commandOptions == null)
1413     {
1414       commandOptions = "";
1415     }
1416     viewer = JmolViewer.allocateViewer(renderPanel,
1417             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1418                     + ((Object) this).toString(), documentBase, codeBase,
1419             commandOptions, this);
1420
1421     console = createJmolConsole(viewer, consolePanel, buttonsToShow);
1422     if (consolePanel != null)
1423     {
1424       consolePanel.addComponentListener(this);
1425
1426     }
1427
1428   }
1429
1430   protected abstract JmolAppConsoleInterface createJmolConsole(
1431           JmolViewer viewer2, Container consolePanel, String buttonsToShow);
1432
1433   protected org.jmol.api.JmolAppConsoleInterface console = null;
1434
1435   public void setBackgroundColour(java.awt.Color col)
1436   {
1437     jmolHistory(false);
1438     viewer.evalStringQuiet("background [" + col.getRed() + ","
1439             + col.getGreen() + "," + col.getBlue() + "];");
1440     jmolHistory(true);
1441   }
1442
1443   @Override
1444   public void resizeInnerPanel(String data)
1445   {
1446     // Jalview doesn't honour resize panel requests
1447
1448   }
1449
1450   public boolean isFinishedInit()
1451   {
1452     return finishedInit;
1453   }
1454
1455   public void setFinishedInit(boolean finishedInit)
1456   {
1457     this.finishedInit = finishedInit;
1458   }
1459
1460   /**
1461    * 
1462    */
1463   protected void closeConsole()
1464   {
1465     if (console != null)
1466     {
1467       try
1468       {
1469         console.setVisible(false);
1470       } catch (Error e)
1471       {
1472       } catch (Exception x)
1473       {
1474       }
1475       ;
1476       console = null;
1477     }
1478   }
1479
1480   /**
1481    * ComponentListener method
1482    */
1483   @Override
1484   public void componentMoved(ComponentEvent e)
1485   {
1486   }
1487
1488   /**
1489    * ComponentListener method
1490    */
1491   @Override
1492   public void componentResized(ComponentEvent e)
1493   {
1494   }
1495
1496   /**
1497    * ComponentListener method
1498    */
1499   @Override
1500   public void componentShown(ComponentEvent e)
1501   {
1502     showConsole(true);
1503   }
1504
1505   /**
1506    * ComponentListener method
1507    */
1508   @Override
1509   public void componentHidden(ComponentEvent e)
1510   {
1511     showConsole(false);
1512   }
1513 }