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