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