929411f0b7c7d8f97f496941fc88eb24caccb0db
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ext.jmol;
19
20 import java.io.File;
21 import java.net.URL;
22 import java.util.*;
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import jalview.api.FeatureRenderer;
27 import jalview.api.SequenceRenderer;
28 import jalview.api.SequenceStructureBinding;
29 import jalview.datamodel.*;
30 import jalview.structure.*;
31 import jalview.io.*;
32
33 import org.jmol.api.*;
34 import org.jmol.adapter.smarter.SmarterJmolAdapter;
35
36 import org.jmol.popup.*;
37 import org.jmol.viewer.JmolConstants;
38
39 import jalview.schemes.*;
40
41 public abstract class JalviewJmolBinding implements StructureListener,
42         JmolStatusListener, SequenceStructureBinding, JmolSelectionListener
43
44 {
45   /**
46    * set if Jmol state is being restored from some source - instructs binding
47    * not to apply default display style when structure set is updated for first
48    * time.
49    */
50   private boolean loadingFromArchive = false;
51
52   /**
53    * state flag used to check if the Jmol viewer's paint method can be called
54    */
55   private boolean finishedInit = false;
56
57   public boolean isFinishedInit()
58   {
59     return finishedInit;
60   }
61
62   public void setFinishedInit(boolean finishedInit)
63   {
64     this.finishedInit = finishedInit;
65   }
66
67   boolean allChainsSelected = false;
68
69   /**
70    * when true, try to search the associated datamodel for sequences that are
71    * associated with any unknown structures in the Jmol view.
72    */
73   private boolean associateNewStructs = false;
74
75   Vector atomsPicked = new Vector();
76
77   public Vector chainNames;
78
79   String[] chains;
80
81   boolean colourBySequence = true;
82
83   StringBuffer eval = new StringBuffer();
84
85   public String fileLoadingError;
86
87   /**
88    * the default or current model displayed if the model cannot be identified
89    * from the selection message
90    */
91   int frameNo = 0;
92
93   protected JmolPopup jmolpopup;
94
95   String lastCommand;
96
97   String lastMessage;
98
99   boolean loadedInline;
100
101   /**
102    * current set of model filenames loaded in the Jmol instance
103    */
104   String[] modelFileNames = null;
105
106   public PDBEntry[] pdbentry;
107
108   /**
109    * datasource protocol for access to PDBEntry
110    */
111   String protocol = null;
112
113   StringBuffer resetLastRes = new StringBuffer();
114
115   public SequenceI[] sequence;
116
117   StructureSelectionManager ssm;
118
119   public JmolViewer viewer;
120
121   public JalviewJmolBinding(PDBEntry[] pdbentry, SequenceI[] seq,
122           String[] chains, String protocol)
123   {
124     this.sequence = seq;
125     this.chains = chains;
126     this.pdbentry = pdbentry;
127     this.protocol = protocol;
128
129     /*
130      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
131      * "jalviewJmol", ap.av.applet .getDocumentBase(),
132      * ap.av.applet.getCodeBase(), "", this);
133      * 
134      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
135      */
136   }
137
138   public JalviewJmolBinding(JmolViewer viewer2)
139   {
140     viewer = viewer2;
141     viewer.setJmolStatusListener(this);
142     viewer.addSelectionListener(this);
143   }
144
145   /**
146    * construct a title string for the viewer window based on the data jalview
147    * knows about
148    * 
149    * @return
150    */
151   public String getViewerTitle()
152   {
153     if (sequence == null || pdbentry == null || sequence.length < 1
154             || pdbentry.length < 1)
155     {
156       return ("Jalview Jmol Window");
157     }
158     StringBuffer title = new StringBuffer(sequence[0].getName() + ":"
159             + pdbentry[0].getId());
160
161     if (pdbentry[0].getProperty() != null)
162     {
163       if (pdbentry[0].getProperty().get("method") != null)
164       {
165         title.append(" Method: ");
166         title.append(pdbentry[0].getProperty().get("method"));
167       }
168       if (pdbentry[0].getProperty().get("chains") != null)
169       {
170         title.append(" Chain:");
171         title.append(pdbentry[0].getProperty().get("chains"));
172       }
173     }
174     return title.toString();
175   }
176
177   /**
178    * prepare the view for a given set of models/chains. chainList contains
179    * strings of the form 'pdbfilename:Chaincode'
180    * 
181    * @param chainList
182    *          list of chains to make visible
183    */
184   public void centerViewer(Vector chainList)
185   {
186     StringBuffer cmd = new StringBuffer();
187     String lbl;
188     int mlength, p;
189     for (int i = 0, iSize = chainList.size(); i < iSize; i++)
190     {
191       mlength = 0;
192       lbl = (String) chainList.elementAt(i);
193       do
194       {
195         p = mlength;
196         mlength = lbl.indexOf(":", p);
197       } while (p < mlength && mlength < (lbl.length() - 2));
198       cmd.append(":" + lbl.substring(mlength + 1) + " /"
199               + getModelNum(lbl.substring(0, mlength)) + " or ");
200     }
201     if (cmd.length() > 0)
202       cmd.setLength(cmd.length() - 4);
203     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
204   }
205
206   public void closeViewer()
207   {
208     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
209     // remove listeners for all structures in viewer
210     StructureSelectionManager.getStructureSelectionManager()
211             .removeStructureViewerListener(this, this.getPdbFile());
212     // and shut down jmol
213     viewer.evalStringQuiet("zap");
214     viewer.setJmolStatusListener(null);
215     lastCommand = null;
216     viewer = null;
217   }
218
219   public void colourByChain()
220   {
221     colourBySequence = false;
222     evalStateCommand("select *;color chain");
223   }
224
225   public void colourByCharge()
226   {
227     colourBySequence = false;
228     evalStateCommand("select *;color white;select ASP,GLU;color red;"
229             + "select LYS,ARG;color blue;select CYS;color yellow");
230   }
231
232   /**
233    * superpose the structures associated with sequences in the alignment
234    * according to their corresponding positions.
235    */
236   public void superposeStructures(AlignmentI alignment)
237   {
238     String[] files = getPdbFile();
239
240     StringBuffer command = new StringBuffer();
241     boolean matched[] = new boolean[alignment.getWidth()];
242     String commonpositions[][] = new String[files.length][alignment
243             .getWidth()];
244     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
245     {
246       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
247
248       if (mapping == null || mapping.length < 1)
249         continue;
250
251       int lastPos = -1;
252       for (int s = 0; s < sequence.length; s++)
253       {
254         for (int sp, m = 0; m < mapping.length; m++)
255         {
256           if (mapping[m].getSequence() == sequence[s]
257                   && (sp = alignment.findIndex(sequence[s])) > -1)
258           {
259             SequenceI asp = alignment.getSequenceAt(sp);
260             for (int r = 0; r < asp.getLength(); r++)
261             {
262               // no mapping to gaps in sequence
263               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
264               {
265                 matched[r] = false; // exclude from common set
266                 continue;
267               }
268               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
269
270               if (pos < 1 || pos == lastPos)
271                 continue;
272
273               lastPos = pos;
274
275               commonpositions[m][r] = (mapping[m].getChain() != " " ? ":"
276                       + mapping[m].getChain() : "")
277                       + "/" + (pdbfnum + 1) + ".1";
278             }
279             break;
280           }
281         }
282       }
283     }
284     command.append("select ");
285     // form the matched pair selection strings
286     String sep = "";
287     for (int r = 0; r < matched.length; r++)
288     {
289       if (matched[r])
290       {
291         command.append(sep);
292         command.append("(");
293         for (int s = 0; s < commonpositions.length; s++)
294         {
295           if (s > 0)
296           {
297             command.append(" | ");
298           }
299           command.append(commonpositions[s][r]);
300         }
301         command.append(")");
302         sep = " | ";
303       }
304     }
305     evalStateCommand(command.toString());
306   }
307
308   public void evalStateCommand(String command)
309   {
310     jmolHistory(false);
311     if (lastCommand == null || !lastCommand.equals(command))
312     {
313       viewer.evalStringQuiet(command + "\n");
314     }
315     jmolHistory(true);
316     lastCommand = command;
317   }
318
319   /**
320    * colour any structures associated with sequences in the given alignment
321    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
322    * if colourBySequence is enabled.
323    */
324   public void colourBySequence(boolean showFeatures, AlignmentI alignment)
325   {
326     if (!colourBySequence)
327       return;
328     if (ssm == null)
329     {
330       return;
331     }
332     String[] files = getPdbFile();
333     SequenceRenderer sr = getSequenceRenderer();
334
335     FeatureRenderer fr = null;
336     if (showFeatures)
337     {
338       fr = getFeatureRenderer();
339     }
340
341     StringBuffer command = new StringBuffer();
342
343     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
344     {
345       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
346
347       if (mapping == null || mapping.length < 1)
348         continue;
349
350       int lastPos = -1;
351       for (int s = 0; s < sequence.length; s++)
352       {
353         for (int sp, m = 0; m < mapping.length; m++)
354         {
355           if (mapping[m].getSequence() == sequence[s]
356                   && (sp = alignment.findIndex(sequence[s])) > -1)
357           {
358             SequenceI asp = alignment.getSequenceAt(sp);
359             for (int r = 0; r < asp.getLength(); r++)
360             {
361               // no mapping to gaps in sequence
362               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
363               {
364                 continue;
365               }
366               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
367
368               if (pos < 1 || pos == lastPos)
369                 continue;
370
371               lastPos = pos;
372
373               Color col = sr.getResidueBoxColour(sequence[s], r);
374
375               if (showFeatures)
376                 col = fr.findFeatureColour(col, sequence[s], r);
377               String newSelcom = (mapping[m].getChain() != " " ? ":"
378                       + mapping[m].getChain() : "")
379                       + "/"
380                       + (pdbfnum + 1)
381                       + ".1"
382                       + ";color["
383                       + col.getRed()
384                       + ","
385                       + col.getGreen()
386                       + ","
387                       + col.getBlue() + "]";
388               if (command.toString().endsWith(newSelcom))
389               {
390                 command = condenseCommand(command.toString(), pos);
391                 continue;
392               }
393               // TODO: deal with case when buffer is too large for Jmol to parse
394               // - execute command and flush
395
396               command.append(";select " + pos);
397               command.append(newSelcom);
398             }
399             break;
400           }
401         }
402       }
403     }
404     evalStateCommand(command.toString());
405   }
406
407   public boolean isColourBySequence()
408   {
409     return colourBySequence;
410   }
411
412   public void setColourBySequence(boolean colourBySequence)
413   {
414     this.colourBySequence = colourBySequence;
415   }
416
417   StringBuffer condenseCommand(String command, int pos)
418   {
419
420     StringBuffer sb = new StringBuffer(command.substring(0,
421             command.lastIndexOf("select") + 7));
422
423     command = command.substring(sb.length());
424
425     String start;
426
427     if (command.indexOf("-") > -1)
428     {
429       start = command.substring(0, command.indexOf("-"));
430     }
431     else
432     {
433       start = command.substring(0, command.indexOf(":"));
434     }
435
436     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
437
438     return sb;
439   }
440
441   public void createImage(String file, String type, int quality)
442   {
443     System.out.println("JMOL CREATE IMAGE");
444   }
445
446   public String createImage(String fileName, String type,
447           Object textOrBytes, int quality)
448   {
449     System.out.println("JMOL CREATE IMAGE");
450     return null;
451   }
452
453   public String eval(String strEval)
454   {
455     // System.out.println(strEval);
456     // "# 'eval' is implemented only for the applet.";
457     return null;
458   }
459
460   // End StructureListener
461   // //////////////////////////
462
463   public float[][] functionXY(String functionName, int x, int y)
464   {
465     return null;
466   }
467
468   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
469   {
470     // TODO Auto-generated method stub
471     return null;
472   }
473
474   public Color getColour(int atomIndex, int pdbResNum, String chain,
475           String pdbfile)
476   {
477     if (getModelNum(pdbfile) < 0)
478       return null;
479     // TODO: verify atomIndex is selecting correct model.
480     return new Color(viewer.getAtomArgb(atomIndex));
481   }
482
483   /**
484    * returns the current featureRenderer that should be used to colour the
485    * structures
486    * 
487    * @return
488    */
489   public abstract FeatureRenderer getFeatureRenderer();
490
491   /**
492    * instruct the Jalview binding to update the pdbentries vector if necessary
493    * prior to matching the jmol view's contents to the list of structure files
494    * Jalview knows about.
495    */
496   public abstract void refreshPdbEntries();
497
498   private int getModelNum(String modelFileName)
499   {
500     String[] mfn = getPdbFile();
501     if (mfn == null)
502     {
503       return -1;
504     }
505     for (int i = 0; i < mfn.length; i++)
506     {
507       if (mfn[i].equalsIgnoreCase(modelFileName))
508         return i;
509     }
510     return -1;
511   }
512
513   // ////////////////////////////////
514   // /StructureListener
515   public String[] getPdbFile()
516   {
517     if (modelFileNames == null)
518     {
519       String mset[] = new String[viewer.getModelCount()];
520       for (int i = 0; i < mset.length; i++)
521       {
522         mset[i] = viewer.getModelFileName(i);
523       }
524       modelFileNames = mset;
525     }
526     return modelFileNames;
527   }
528
529   public Hashtable getRegistryInfo()
530   {
531     // TODO Auto-generated method stub
532     return null;
533   }
534
535   /**
536    * returns the current sequenceRenderer that should be used to colour the
537    * structures
538    * 
539    * @return
540    */
541   public abstract SequenceRenderer getSequenceRenderer();
542
543   // ///////////////////////////////
544   // JmolStatusListener
545
546   public void handlePopupMenu(int x, int y)
547   {
548     jmolpopup.show(x, y);
549   }
550
551   // jmol/ssm only
552   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
553           String pdbfile)
554   {
555     if (modelFileNames == null)
556     {
557       return;
558     }
559
560     // look up file model number for this pdbfile
561     int mdlNum = 0;
562     String fn;
563     // may need to adjust for URLencoding here - we don't worry about that yet.
564     while (mdlNum < modelFileNames.length
565             && !pdbfile.equals(modelFileNames[mdlNum]))
566     {
567       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
568       mdlNum++;
569     }
570     if (mdlNum == modelFileNames.length)
571     {
572       return;
573     }
574
575     jmolHistory(false);
576     // if (!pdbfile.equals(pdbentry.getFile()))
577     // return;
578     if (resetLastRes.length() > 0)
579     {
580       viewer.evalStringQuiet(resetLastRes.toString());
581     }
582
583     eval.setLength(0);
584     eval.append("select " + pdbResNum); // +modelNum
585
586     resetLastRes.setLength(0);
587     resetLastRes.append("select " + pdbResNum); // +modelNum
588
589     eval.append(":");
590     resetLastRes.append(":");
591     if (!chain.equals(" "))
592     {
593       eval.append(chain);
594       resetLastRes.append(chain);
595     }
596     {
597       eval.append(" /" + (mdlNum + 1));
598       resetLastRes.append("/" + (mdlNum + 1));
599     }
600     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
601
602     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
603             + " and not hetero; spacefill 0;");
604
605     eval.append("spacefill 200;select none");
606
607     viewer.evalStringQuiet(eval.toString());
608     jmolHistory(true);
609
610   }
611
612   boolean debug = true;
613
614   private void jmolHistory(boolean enable)
615   {
616     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
617   }
618
619   public void loadInline(String string)
620   {
621     loadedInline = true;
622     viewer.openStringInline(string);
623   }
624
625   public void mouseOverStructure(int atomIndex, String strInfo)
626   {
627     int pdbResNum;
628     int mdlSep = strInfo.indexOf("/");
629     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
630
631     if (chainSeparator == -1)
632     {
633       chainSeparator = strInfo.indexOf(".");
634       if (mdlSep > -1 && mdlSep < chainSeparator)
635       {
636         chainSeparator1 = chainSeparator;
637         chainSeparator = mdlSep;
638       }
639     }
640     pdbResNum = Integer.parseInt(strInfo.substring(
641             strInfo.indexOf("]") + 1, chainSeparator));
642
643     String chainId;
644
645     if (strInfo.indexOf(":") > -1)
646       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
647               strInfo.indexOf("."));
648     else
649     {
650       chainId = " ";
651     }
652
653     String pdbfilename = modelFileNames[frameNo]; // default is first or current
654     // model
655     if (mdlSep > -1)
656     {
657       if (chainSeparator1 == -1)
658       {
659         chainSeparator1 = strInfo.indexOf(".", mdlSep);
660       }
661       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
662               chainSeparator1) : strInfo.substring(mdlSep + 1);
663       try
664       {
665         // recover PDB filename for the model hovered over.
666         pdbfilename = viewer
667                 .getModelFileName(new Integer(mdlId).intValue() - 1);
668       } catch (Exception e)
669       {
670       }
671       ;
672     }
673     if (lastMessage == null || !lastMessage.equals(strInfo))
674       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
675
676     lastMessage = strInfo;
677   }
678
679   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
680   {
681     if (data != null)
682     {
683       System.err.println("Ignoring additional hover info: " + data
684               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
685     }
686     mouseOverStructure(atomIndex, strInfo);
687   }
688
689   /*
690    * { if (history != null && strStatus != null &&
691    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
692    * } }
693    */
694
695   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
696   {
697     /**
698      * this implements the toggle label behaviour copied from the original
699      * structure viewer, MCView
700      */
701     if (strData != null)
702     {
703       System.err.println("Ignoring additional pick data string " + strData);
704     }
705     int chainSeparator = strInfo.indexOf(":");
706     int p = 0;
707     if (chainSeparator == -1)
708       chainSeparator = strInfo.indexOf(".");
709
710     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
711             chainSeparator);
712     String mdlString = "";
713     if ((p = strInfo.indexOf(":")) > -1)
714       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
715
716     if ((p = strInfo.indexOf("/")) > -1)
717     {
718       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
719     }
720     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
721             + mdlString + "))";
722     jmolHistory(false);
723
724     if (!atomsPicked.contains(picked))
725     {
726       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
727       atomsPicked.addElement(picked);
728     }
729     else
730     {
731       viewer.evalString("select " + picked + ";label off");
732       atomsPicked.removeElement(picked);
733     }
734     jmolHistory(true);
735     // TODO: in application this happens
736     //
737     // if (scriptWindow != null)
738     // {
739     // scriptWindow.sendConsoleMessage(strInfo);
740     // scriptWindow.sendConsoleMessage("\n");
741     // }
742
743   }
744
745   public void notifyCallback(int type, Object[] data)
746   {
747     try
748     {
749       switch (type)
750       {
751       case JmolConstants.CALLBACK_LOADSTRUCT:
752         notifyFileLoaded((String) data[1], (String) data[2],
753                 (String) data[3], (String) data[4],
754                 ((Integer) data[5]).intValue());
755
756         break;
757       case JmolConstants.CALLBACK_PICK:
758         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
759                 (String) data[0]);
760         // also highlight in alignment
761       case JmolConstants.CALLBACK_HOVER:
762         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
763                 (String) data[0]);
764         break;
765       case JmolConstants.CALLBACK_SCRIPT:
766         notifyScriptTermination((String) data[2],
767                 ((Integer) data[3]).intValue());
768         break;
769       case JmolConstants.CALLBACK_ECHO:
770         sendConsoleEcho((String) data[1]);
771         break;
772       case JmolConstants.CALLBACK_MESSAGE:
773         sendConsoleMessage((data == null) ? ((String) null)
774                 : (String) data[1]);
775         break;
776       case JmolConstants.CALLBACK_ERROR:
777         // System.err.println("Ignoring error callback.");
778         break;
779       case JmolConstants.CALLBACK_SYNC:
780       case JmolConstants.CALLBACK_RESIZE:
781         updateUI();
782         break;
783       case JmolConstants.CALLBACK_MEASURE:
784
785       case JmolConstants.CALLBACK_CLICK:
786
787       default:
788         System.err.println("Unhandled callback " + type + " "
789                 + data[1].toString());
790         break;
791       }
792     } catch (Exception e)
793     {
794       System.err.println("Squashed Jmol callback handler error:");
795       e.printStackTrace();
796     }
797   }
798
799   public boolean notifyEnabled(int callbackPick)
800   {
801     switch (callbackPick)
802     {
803     case JmolConstants.CALLBACK_ECHO:
804     case JmolConstants.CALLBACK_LOADSTRUCT:
805     case JmolConstants.CALLBACK_MEASURE:
806     case JmolConstants.CALLBACK_MESSAGE:
807     case JmolConstants.CALLBACK_PICK:
808     case JmolConstants.CALLBACK_SCRIPT:
809     case JmolConstants.CALLBACK_HOVER:
810     case JmolConstants.CALLBACK_ERROR:
811       return true;
812     case JmolConstants.CALLBACK_RESIZE:
813     case JmolConstants.CALLBACK_SYNC:
814     case JmolConstants.CALLBACK_CLICK:
815     case JmolConstants.CALLBACK_ANIMFRAME:
816     case JmolConstants.CALLBACK_MINIMIZATION:
817     }
818     return false;
819   }
820
821   public void notifyFileLoaded(String fullPathName, String fileName2,
822           String modelName, String errorMsg, int modelParts)
823   {
824     if (errorMsg != null)
825     {
826       fileLoadingError = errorMsg;
827       updateUI();
828       return;
829     }
830     // the rest of this routine ignores the arguments, and simply interrogates
831     // the Jmol view to find out what structures it contains, and adds them to
832     // the structure selection manager.
833     fileLoadingError = null;
834     String[] oldmodels = modelFileNames;
835     modelFileNames = null;
836     chainNames = new Vector();
837     boolean notifyLoaded = false;
838     String[] modelfilenames = getPdbFile();
839     ssm = StructureSelectionManager.getStructureSelectionManager();
840     // first check if we've lost any structures
841     if (oldmodels != null && oldmodels.length > 0)
842     {
843       int oldm = 0;
844       for (int i = 0; i < oldmodels.length; i++)
845       {
846         for (int n = 0; n < modelfilenames.length; n++)
847         {
848           if (modelfilenames[n] == oldmodels[i])
849           {
850             oldmodels[i] = null;
851             break;
852           }
853         }
854         if (oldmodels[i] != null)
855         {
856           oldm++;
857         }
858       }
859       if (oldm > 0)
860       {
861         String[] oldmfn = new String[oldm];
862         oldm = 0;
863         for (int i = 0; i < oldmodels.length; i++)
864         {
865           if (oldmodels[i] != null)
866           {
867             oldmfn[oldm++] = oldmodels[i];
868           }
869         }
870         // deregister the Jmol instance for these structures - we'll add
871         // ourselves again at the end for the current structure set.
872         ssm.removeStructureViewerListener(this, oldmfn);
873       }
874     }
875     refreshPdbEntries();
876     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
877     {
878       String fileName = modelfilenames[modelnum];
879       if (fileName != null)
880       {
881         boolean foundEntry = false;
882         // search pdbentries and sequences to find correct pdbentry and
883         // sequence[] pair for this filename
884         if (pdbentry != null)
885         {
886           for (int pe = 0; pe < pdbentry.length; pe++)
887           {
888             if (pdbentry[pe].getFile().equals(fileName))
889             {
890               foundEntry = true;
891               MCview.PDBfile pdb;
892               if (loadedInline)
893               {
894                 // TODO: replace with getData ?
895                 pdb = ssm.setMapping(sequence, chains,
896                         pdbentry[pe].getFile(), AppletFormatAdapter.PASTE);
897                 pdbentry[pe].setFile("INLINE" + pdb.id);
898               }
899               else
900               {
901                 // TODO: Jmol can in principle retrieve from CLASSLOADER but
902                 // this
903                 // needs
904                 // to be tested. See mantis bug
905                 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
906                 String protocol = AppletFormatAdapter.URL;
907                 try
908                 {
909                   File fl = new java.io.File(pdbentry[pe].getFile());
910                   if (fl.exists())
911                   {
912                     protocol = AppletFormatAdapter.FILE;
913                   }
914                 } catch (Exception e)
915                 {
916                 } catch (Error e)
917                 {
918                 }
919                 ;
920                 pdb = ssm.setMapping(sequence, chains,
921                         pdbentry[pe].getFile(), protocol);
922
923               }
924
925               pdbentry[pe].setId(pdb.id);
926
927               for (int i = 0; i < pdb.chains.size(); i++)
928               {
929                 chainNames.addElement(new String(pdb.id + ":"
930                         + ((MCview.PDBChain) pdb.chains.elementAt(i)).id));
931               }
932               notifyLoaded = true;
933             }
934           }
935         }
936         if (!foundEntry && associateNewStructs)
937         {
938           // this is a foreign pdb file that jalview doesn't know about - add
939           // it to the dataset and try to find a home - either on a matching
940           // sequence or as a new sequence.
941           String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
942                   "PDB");
943           // parse pdb file into a chain, etc.
944           // locate best match for pdb in associated views and add mapping to
945           // ssm
946           // if properly registered then
947           notifyLoaded = true;
948
949         }
950       }
951     }
952     // FILE LOADED OK
953     // so finally, update the jmol bits and pieces
954     if (jmolpopup != null)
955     {
956       jmolpopup.updateComputedMenus();
957     }
958     if (!isLoadingFromArchive())
959     {
960       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
961     }
962     setLoadingFromArchive(false);
963     // register ourselves as a listener and notify the gui that it needs to
964     // update itself.
965     ssm.addStructureViewerListener(this);
966     if (notifyLoaded)
967     {
968       FeatureRenderer fr = getFeatureRenderer();
969       if (fr != null)
970       {
971         fr.featuresAdded();
972       }
973       updateUI();
974     }
975   }
976
977   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
978   {
979     notifyAtomPicked(iatom, strMeasure, null);
980   }
981
982   public abstract void notifyScriptTermination(String strStatus,
983           int msWalltime);
984
985   /**
986    * display a message echoed from the jmol viewer
987    * 
988    * @param strEcho
989    */
990   public abstract void sendConsoleEcho(String strEcho); /*
991                                                          * { showConsole(true);
992                                                          * 
993                                                          * history.append("\n" +
994                                                          * strEcho); }
995                                                          */
996
997   // /End JmolStatusListener
998   // /////////////////////////////
999
1000   /**
1001    * @param strStatus
1002    *          status message - usually the response received after a script
1003    *          executed
1004    */
1005   public abstract void sendConsoleMessage(String strStatus);
1006
1007   public void setCallbackFunction(String callbackType,
1008           String callbackFunction)
1009   {
1010     System.err.println("Ignoring set-callback request to associate "
1011             + callbackType + " with function " + callbackFunction);
1012
1013   }
1014
1015   public void setJalviewColourScheme(ColourSchemeI cs)
1016   {
1017     colourBySequence = false;
1018
1019     if (cs == null)
1020       return;
1021
1022     String res;
1023     int index;
1024     Color col;
1025     jmolHistory(false);
1026     // TODO: Switch between nucleotide or aa selection expressions
1027     Enumeration en = ResidueProperties.aa3Hash.keys();
1028     StringBuffer command = new StringBuffer("select *;color white;");
1029     while (en.hasMoreElements())
1030     {
1031       res = en.nextElement().toString();
1032       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
1033       if (index > 20)
1034         continue;
1035
1036       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
1037
1038       command.append("select " + res + ";color[" + col.getRed() + ","
1039               + col.getGreen() + "," + col.getBlue() + "];");
1040     }
1041
1042     evalStateCommand(command.toString());
1043     jmolHistory(true);
1044   }
1045
1046   public void showHelp()
1047   {
1048     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1049   }
1050
1051   /**
1052    * open the URL somehow
1053    * 
1054    * @param target
1055    */
1056   public abstract void showUrl(String url, String target);
1057
1058   /**
1059    * called when the binding thinks the UI needs to be refreshed after a Jmol
1060    * state change. this could be because structures were loaded, or because an
1061    * error has occured.
1062    */
1063   public abstract void updateUI();
1064
1065   public void allocateViewer(Component renderPanel, String htmlName,
1066           URL documentBase, URL codeBase, String commandOptions)
1067   {
1068     viewer = JmolViewer.allocateViewer(renderPanel,
1069             new SmarterJmolAdapter(),
1070             htmlName + ((Object) this).toString(), documentBase, codeBase,
1071             commandOptions, this);
1072   }
1073
1074   public void setLoadingFromArchive(boolean loadingFromArchive)
1075   {
1076     this.loadingFromArchive = loadingFromArchive;
1077   }
1078
1079   public boolean isLoadingFromArchive()
1080   {
1081     return loadingFromArchive;
1082   }
1083
1084   public void setBackgroundColour(java.awt.Color col)
1085   {
1086     jmolHistory(false);
1087     viewer.evalStringQuiet("background [" + col.getRed() + ","
1088             + col.getGreen() + "," + col.getBlue() + "];");
1089     jmolHistory(true);
1090   }
1091 }