fixed various null pointer exceptions that occur when PDB files imported into Jmol...
[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     // generate select statements to select regions to superimpose structures
377     {
378       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
379       {
380         String chainCd = targetC[pdbfnum];
381         int lpos = -1;
382         boolean run = false;
383         StringBuffer molsel = new StringBuffer();
384         molsel.append("{");
385         for (int r = 0; r < matched.length; r++)
386         {
387           if (matched[r])
388           {
389
390             if (lpos != commonrpositions[pdbfnum][r] - 1)
391             {
392               // discontinuity
393               if (lpos != -1)
394               {
395                 molsel.append(lpos);
396                 molsel.append(chainCd);
397                 // molsel.append("} {");
398                 molsel.append("|");
399               }
400             }
401             else
402             {
403               // continuous run - and lpos >-1
404               if (!run)
405               {
406                 // at the beginning, so add dash
407                 molsel.append(lpos);
408                 molsel.append("-");
409               }
410               run = true;
411             }
412             lpos = commonrpositions[pdbfnum][r];
413             // molsel.append(lpos);
414           }
415         }
416         // add final selection phrase
417         if (lpos != -1)
418         {
419           molsel.append(lpos);
420           molsel.append(chainCd);
421           molsel.append("}");
422         }
423         selcom[pdbfnum] = molsel.toString();
424         selectioncom.append("((");
425         selectioncom.append(selcom[pdbfnum].substring(1,
426                 selcom[pdbfnum].length() - 1));
427         selectioncom.append(" )& ");
428         selectioncom.append(pdbfnum + 1);
429         selectioncom.append(".1)");
430         if (pdbfnum < files.length - 1)
431         {
432           selectioncom.append("|");
433         }
434       }
435     }
436
437     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
438     {
439       if (pdbfnum == refStructure)
440       {
441         continue;
442       }
443       command.append("compare ");
444       command.append("{");
445       command.append(1 + pdbfnum);
446       command.append(".1} {");
447       command.append(1 + refStructure);
448       command.append(".1} SUBSET {*.CA | *.P} ATOMS ");
449
450       // form the matched pair strings
451       String sep = "";
452       for (int s = 0; s < 2; s++)
453       {
454         command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
455       }
456       command.append(" ROTATE TRANSLATE;\n");
457     }
458     System.out.println("Select regions:\n" + selectioncom.toString());
459     evalStateCommand("select *; cartoons off; backbone; select ("
460             + selectioncom.toString() + "); cartoons; ");
461     // selcom.append("; ribbons; ");
462     System.out.println("Superimpose command(s):\n" + command.toString());
463
464     evalStateCommand(command.toString());
465
466     // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
467   }
468
469   public void evalStateCommand(String command)
470   {
471     jmolHistory(false);
472     if (lastCommand == null || !lastCommand.equals(command))
473     {
474       viewer.evalStringQuiet(command + "\n");
475     }
476     jmolHistory(true);
477     lastCommand = command;
478   }
479
480   /**
481    * colour any structures associated with sequences in the given alignment
482    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
483    * if colourBySequence is enabled.
484    */
485   public void colourBySequence(boolean showFeatures, AlignmentI alignment)
486   {
487     if (!colourBySequence)
488       return;
489     if (ssm == null)
490     {
491       return;
492     }
493     String[] files = getPdbFile();
494     SequenceRenderer sr = getSequenceRenderer();
495
496     FeatureRenderer fr = null;
497     if (showFeatures)
498     {
499       fr = getFeatureRenderer();
500     }
501
502     StringBuffer command = new StringBuffer();
503
504     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
505     {
506       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
507
508       if (mapping == null || mapping.length < 1)
509         continue;
510
511       int lastPos = -1;
512       for (int s = 0; s < sequence[pdbfnum].length; s++)
513       {
514         for (int sp, m = 0; m < mapping.length; m++)
515         {
516           if (mapping[m].getSequence() == sequence[pdbfnum][s]
517                   && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
518           {
519             SequenceI asp = alignment.getSequenceAt(sp);
520             for (int r = 0; r < asp.getLength(); r++)
521             {
522               // no mapping to gaps in sequence
523               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
524               {
525                 continue;
526               }
527               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
528
529               if (pos < 1 || pos == lastPos)
530                 continue;
531
532               lastPos = pos;
533
534               Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
535
536               if (showFeatures)
537                 col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
538               String newSelcom = (mapping[m].getChain() != " " ? ":"
539                       + mapping[m].getChain() : "")
540                       + "/"
541                       + (pdbfnum + 1)
542                       + ".1"
543                       + ";color["
544                       + col.getRed()
545                       + ","
546                       + col.getGreen()
547                       + ","
548                       + col.getBlue() + "]";
549               if (command.toString().endsWith(newSelcom))
550               {
551                 command = condenseCommand(command.toString(), pos);
552                 continue;
553               }
554               // TODO: deal with case when buffer is too large for Jmol to parse
555               // - execute command and flush
556
557               command.append(";select " + pos);
558               command.append(newSelcom);
559             }
560             break;
561           }
562         }
563       }
564     }
565     evalStateCommand(command.toString());
566   }
567
568   public boolean isColourBySequence()
569   {
570     return colourBySequence;
571   }
572
573   public void setColourBySequence(boolean colourBySequence)
574   {
575     this.colourBySequence = colourBySequence;
576   }
577
578   StringBuffer condenseCommand(String command, int pos)
579   {
580
581     StringBuffer sb = new StringBuffer(command.substring(0,
582             command.lastIndexOf("select") + 7));
583
584     command = command.substring(sb.length());
585
586     String start;
587
588     if (command.indexOf("-") > -1)
589     {
590       start = command.substring(0, command.indexOf("-"));
591     }
592     else
593     {
594       start = command.substring(0, command.indexOf(":"));
595     }
596
597     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
598
599     return sb;
600   }
601
602   public void createImage(String file, String type, int quality)
603   {
604     System.out.println("JMOL CREATE IMAGE");
605   }
606
607   public String createImage(String fileName, String type,
608           Object textOrBytes, int quality)
609   {
610     System.out.println("JMOL CREATE IMAGE");
611     return null;
612   }
613
614   public String eval(String strEval)
615   {
616     // System.out.println(strEval);
617     // "# 'eval' is implemented only for the applet.";
618     return null;
619   }
620
621   // End StructureListener
622   // //////////////////////////
623
624   public float[][] functionXY(String functionName, int x, int y)
625   {
626     return null;
627   }
628
629   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
630   {
631     // TODO Auto-generated method stub
632     return null;
633   }
634
635   public Color getColour(int atomIndex, int pdbResNum, String chain,
636           String pdbfile)
637   {
638     if (getModelNum(pdbfile) < 0)
639       return null;
640     // TODO: verify atomIndex is selecting correct model.
641     return new Color(viewer.getAtomArgb(atomIndex));
642   }
643
644   /**
645    * returns the current featureRenderer that should be used to colour the
646    * structures
647    * 
648    * @return
649    */
650   public abstract FeatureRenderer getFeatureRenderer();
651
652   /**
653    * instruct the Jalview binding to update the pdbentries vector if necessary
654    * prior to matching the jmol view's contents to the list of structure files
655    * Jalview knows about.
656    */
657   public abstract void refreshPdbEntries();
658
659   private int getModelNum(String modelFileName)
660   {
661     String[] mfn = getPdbFile();
662     if (mfn == null)
663     {
664       return -1;
665     }
666     for (int i = 0; i < mfn.length; i++)
667     {
668       if (mfn[i].equalsIgnoreCase(modelFileName))
669         return i;
670     }
671     return -1;
672   }
673
674   /**
675    * map between index of model filename returned from getPdbFile and the first
676    * index of models from this file in the viewer. Note - this is not trimmed -
677    * use getPdbFile to get number of unique models.
678    */
679   private int _modelFileNameMap[];
680
681   // ////////////////////////////////
682   // /StructureListener
683   public synchronized String[] getPdbFile()
684   {
685     if (modelFileNames == null)
686     {
687
688       String mset[] = new String[viewer.getModelCount()];
689       _modelFileNameMap = new int[mset.length];
690       int j = 1;
691       mset[0] = viewer.getModelFileName(0);
692       for (int i = 1; i < mset.length; i++)
693       {
694         mset[j] = viewer.getModelFileName(i);
695         _modelFileNameMap[j] = i; // record the model index for the filename
696         // skip any additional models in the same file (NMR structures)
697         if ((mset[j] == null ? mset[j] != mset[j - 1] : mset[j - 1] == null
698                 || mset[j].equals(mset[j - 1])))
699         {
700           j++;
701         }
702       }
703       modelFileNames = new String[j];
704       System.arraycopy(mset, 0, modelFileNames, 0, j);
705     }
706     return modelFileNames;
707   }
708
709   /**
710    * map from string to applet
711    */
712   public Map getRegistryInfo()
713   {
714     // TODO Auto-generated method stub
715     return null;
716   }
717
718   /**
719    * returns the current sequenceRenderer that should be used to colour the
720    * structures
721    * 
722    * @return
723    */
724   public abstract SequenceRenderer getSequenceRenderer();
725
726   // ///////////////////////////////
727   // JmolStatusListener
728
729   public void handlePopupMenu(int x, int y)
730   {
731     jmolpopup.show(x, y);
732   }
733
734   // jmol/ssm only
735   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
736           String pdbfile)
737   {
738     if (modelFileNames == null)
739     {
740       return;
741     }
742
743     // look up file model number for this pdbfile
744     int mdlNum = 0;
745     String fn;
746     // may need to adjust for URLencoding here - we don't worry about that yet.
747     while (mdlNum < modelFileNames.length
748             && !pdbfile.equals(modelFileNames[mdlNum]))
749     {
750       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
751       mdlNum++;
752     }
753     if (mdlNum == modelFileNames.length)
754     {
755       return;
756     }
757
758     jmolHistory(false);
759     // if (!pdbfile.equals(pdbentry.getFile()))
760     // return;
761     if (resetLastRes.length() > 0)
762     {
763       viewer.evalStringQuiet(resetLastRes.toString());
764     }
765
766     eval.setLength(0);
767     eval.append("select " + pdbResNum); // +modelNum
768
769     resetLastRes.setLength(0);
770     resetLastRes.append("select " + pdbResNum); // +modelNum
771
772     eval.append(":");
773     resetLastRes.append(":");
774     if (!chain.equals(" "))
775     {
776       eval.append(chain);
777       resetLastRes.append(chain);
778     }
779     {
780       eval.append(" /" + (mdlNum + 1));
781       resetLastRes.append("/" + (mdlNum + 1));
782     }
783     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
784
785     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
786             + " and not hetero; spacefill 0;");
787
788     eval.append("spacefill 200;select none");
789
790     viewer.evalStringQuiet(eval.toString());
791     jmolHistory(true);
792
793   }
794
795   boolean debug = true;
796
797   private void jmolHistory(boolean enable)
798   {
799     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
800   }
801
802   public void loadInline(String string)
803   {
804     loadedInline = true;
805     // TODO: re JAL-623
806     // viewer.loadInline(strModel, isAppend);
807     // could do this:
808     // construct fake fullPathName and fileName so we can identify the file
809     // later.
810     // Then, construct pass a reader for the string to Jmol.
811     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
812     // fileName, null, reader, false, null, null, 0);
813     viewer.openStringInline(string);
814   }
815
816   public void mouseOverStructure(int atomIndex, String strInfo)
817   {
818     int pdbResNum;
819     int mdlSep = strInfo.indexOf("/");
820     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
821
822     if (chainSeparator == -1)
823     {
824       chainSeparator = strInfo.indexOf(".");
825       if (mdlSep > -1 && mdlSep < chainSeparator)
826       {
827         chainSeparator1 = chainSeparator;
828         chainSeparator = mdlSep;
829       }
830     }
831     pdbResNum = Integer.parseInt(strInfo.substring(
832             strInfo.indexOf("]") + 1, chainSeparator));
833
834     String chainId;
835
836     if (strInfo.indexOf(":") > -1)
837       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
838               strInfo.indexOf("."));
839     else
840     {
841       chainId = " ";
842     }
843
844     String pdbfilename = modelFileNames[frameNo]; // default is first or current
845     // model
846     if (mdlSep > -1)
847     {
848       if (chainSeparator1 == -1)
849       {
850         chainSeparator1 = strInfo.indexOf(".", mdlSep);
851       }
852       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
853               chainSeparator1) : strInfo.substring(mdlSep + 1);
854       try
855       {
856         // recover PDB filename for the model hovered over.
857         pdbfilename = viewer
858                 .getModelFileName(new Integer(mdlId).intValue() - 1);
859       } catch (Exception e)
860       {
861       }
862       ;
863     }
864     if (lastMessage == null || !lastMessage.equals(strInfo))
865       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
866
867     lastMessage = strInfo;
868   }
869
870   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
871   {
872     if (data != null)
873     {
874       System.err.println("Ignoring additional hover info: " + data
875               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
876     }
877     mouseOverStructure(atomIndex, strInfo);
878   }
879
880   /*
881    * { if (history != null && strStatus != null &&
882    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
883    * } }
884    */
885
886   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
887   {
888     /**
889      * this implements the toggle label behaviour copied from the original
890      * structure viewer, MCView
891      */
892     if (strData != null)
893     {
894       System.err.println("Ignoring additional pick data string " + strData);
895     }
896     int chainSeparator = strInfo.indexOf(":");
897     int p = 0;
898     if (chainSeparator == -1)
899       chainSeparator = strInfo.indexOf(".");
900
901     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
902             chainSeparator);
903     String mdlString = "";
904     if ((p = strInfo.indexOf(":")) > -1)
905       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
906
907     if ((p = strInfo.indexOf("/")) > -1)
908     {
909       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
910     }
911     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
912             + mdlString + "))";
913     jmolHistory(false);
914
915     if (!atomsPicked.contains(picked))
916     {
917       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
918       atomsPicked.addElement(picked);
919     }
920     else
921     {
922       viewer.evalString("select " + picked + ";label off");
923       atomsPicked.removeElement(picked);
924     }
925     jmolHistory(true);
926     // TODO: in application this happens
927     //
928     // if (scriptWindow != null)
929     // {
930     // scriptWindow.sendConsoleMessage(strInfo);
931     // scriptWindow.sendConsoleMessage("\n");
932     // }
933
934   }
935
936   public void notifyCallback(int type, Object[] data)
937   {
938     try
939     {
940       switch (type)
941       {
942       case JmolConstants.CALLBACK_LOADSTRUCT:
943         notifyFileLoaded((String) data[1], (String) data[2],
944                 (String) data[3], (String) data[4],
945                 ((Integer) data[5]).intValue());
946
947         break;
948       case JmolConstants.CALLBACK_PICK:
949         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
950                 (String) data[0]);
951         // also highlight in alignment
952       case JmolConstants.CALLBACK_HOVER:
953         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
954                 (String) data[0]);
955         break;
956       case JmolConstants.CALLBACK_SCRIPT:
957         notifyScriptTermination((String) data[2],
958                 ((Integer) data[3]).intValue());
959         break;
960       case JmolConstants.CALLBACK_ECHO:
961         sendConsoleEcho((String) data[1]);
962         break;
963       case JmolConstants.CALLBACK_MESSAGE:
964         sendConsoleMessage((data == null) ? ((String) null)
965                 : (String) data[1]);
966         break;
967       case JmolConstants.CALLBACK_ERROR:
968         // System.err.println("Ignoring error callback.");
969         break;
970       case JmolConstants.CALLBACK_SYNC:
971       case JmolConstants.CALLBACK_RESIZE:
972         refreshGUI();
973         break;
974       case JmolConstants.CALLBACK_MEASURE:
975
976       case JmolConstants.CALLBACK_CLICK:
977
978       default:
979         System.err.println("Unhandled callback " + type + " "
980                 + data[1].toString());
981         break;
982       }
983     } catch (Exception e)
984     {
985       System.err.println("Squashed Jmol callback handler error:");
986       e.printStackTrace();
987     }
988   }
989
990   public boolean notifyEnabled(int callbackPick)
991   {
992     switch (callbackPick)
993     {
994     case JmolConstants.CALLBACK_ECHO:
995     case JmolConstants.CALLBACK_LOADSTRUCT:
996     case JmolConstants.CALLBACK_MEASURE:
997     case JmolConstants.CALLBACK_MESSAGE:
998     case JmolConstants.CALLBACK_PICK:
999     case JmolConstants.CALLBACK_SCRIPT:
1000     case JmolConstants.CALLBACK_HOVER:
1001     case JmolConstants.CALLBACK_ERROR:
1002       return true;
1003     case JmolConstants.CALLBACK_RESIZE:
1004     case JmolConstants.CALLBACK_SYNC:
1005     case JmolConstants.CALLBACK_CLICK:
1006     case JmolConstants.CALLBACK_ANIMFRAME:
1007     case JmolConstants.CALLBACK_MINIMIZATION:
1008     }
1009     return false;
1010   }
1011
1012   public void notifyFileLoaded(String fullPathName, String fileName2,
1013           String modelName, String errorMsg, int modelParts)
1014   {
1015     if (errorMsg != null)
1016     {
1017       fileLoadingError = errorMsg;
1018       refreshGUI();
1019       return;
1020     }
1021     // TODO: deal sensibly with models loaded inLine:
1022     // modelName will be null, as will fullPathName.
1023
1024     // the rest of this routine ignores the arguments, and simply interrogates
1025     // the Jmol view to find out what structures it contains, and adds them to
1026     // the structure selection manager.
1027     fileLoadingError = null;
1028     String[] oldmodels = modelFileNames;
1029     modelFileNames = null;
1030     chainNames = new Vector();
1031     boolean notifyLoaded = false;
1032     String[] modelfilenames = getPdbFile();
1033     ssm = StructureSelectionManager.getStructureSelectionManager();
1034     // first check if we've lost any structures
1035     if (oldmodels != null && oldmodels.length > 0)
1036     {
1037       int oldm = 0;
1038       for (int i = 0; i < oldmodels.length; i++)
1039       {
1040         for (int n = 0; n < modelfilenames.length; n++)
1041         {
1042           if (modelfilenames[n] == oldmodels[i])
1043           {
1044             oldmodels[i] = null;
1045             break;
1046           }
1047         }
1048         if (oldmodels[i] != null)
1049         {
1050           oldm++;
1051         }
1052       }
1053       if (oldm > 0)
1054       {
1055         String[] oldmfn = new String[oldm];
1056         oldm = 0;
1057         for (int i = 0; i < oldmodels.length; i++)
1058         {
1059           if (oldmodels[i] != null)
1060           {
1061             oldmfn[oldm++] = oldmodels[i];
1062           }
1063         }
1064         // deregister the Jmol instance for these structures - we'll add
1065         // ourselves again at the end for the current structure set.
1066         ssm.removeStructureViewerListener(this, oldmfn);
1067       }
1068     }
1069     refreshPdbEntries();
1070     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1071     {
1072       String fileName = modelfilenames[modelnum];
1073       boolean foundEntry = false;
1074       MCview.PDBfile pdb = null;
1075       String pdbfile = null, pdbfhash = null;
1076       // model was probably loaded inline - so check the pdb file hashcode
1077       if (loadedInline)
1078       {
1079         // calculate essential attributes for the pdb data imported inline.
1080         // prolly need to resolve modelnumber properly - for now just use our 'best guess'
1081         pdbfile = viewer.getData(""+(1+_modelFileNameMap[modelnum])+".0",
1082                 "PDB");
1083         pdbfhash = "" + pdbfile.hashCode();
1084       }
1085       if (pdbentry != null)
1086       {
1087         // search pdbentries and sequences to find correct pdbentry for this
1088         // model
1089         for (int pe = 0; pe < pdbentry.length; pe++)
1090         {
1091           boolean matches = false;
1092           if (fileName == null)
1093           {
1094             if (false) 
1095               // see JAL-623 - need method of matching pasted data up
1096             {
1097               pdb = ssm.setMapping(sequence[pe], chains[pe], pdbfile,
1098                       AppletFormatAdapter.PASTE);
1099               pdbentry[modelnum].setFile("INLINE" + pdb.id);
1100               matches = true;
1101               foundEntry = true;
1102             }
1103           }
1104           else
1105           {
1106             if (matches = pdbentry[pe].getFile().equals(fileName))
1107             {
1108               foundEntry = true;
1109               // TODO: Jmol can in principle retrieve from CLASSLOADER but
1110               // this
1111               // needs
1112               // to be tested. See mantis bug
1113               // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1114               String protocol = AppletFormatAdapter.URL;
1115               try
1116               {
1117                 File fl = new java.io.File(pdbentry[pe].getFile());
1118                 if (fl.exists())
1119                 {
1120                   protocol = AppletFormatAdapter.FILE;
1121                 }
1122               } catch (Exception e)
1123               {
1124               } catch (Error e)
1125               {
1126               }
1127               ;
1128               pdb = ssm.setMapping(sequence[pe], chains[pe],
1129                       pdbentry[pe].getFile(), protocol);
1130
1131
1132             }
1133           }
1134           if (matches)
1135           {
1136             pdbentry[pe].setId(pdb.id);
1137             // add an entry for every chain in the model
1138             for (int i = 0; i < pdb.chains.size(); i++)
1139             {
1140               chainNames.addElement(new String(pdb.id + ":"
1141                       + ((MCview.PDBChain) pdb.chains.elementAt(i)).id));
1142             }
1143             notifyLoaded = true;
1144           }
1145         }
1146       }
1147       if (!foundEntry && associateNewStructs)
1148       {
1149           // this is a foreign pdb file that jalview doesn't know about - add
1150           // it to the dataset and try to find a home - either on a matching
1151           // sequence or as a new sequence.
1152           String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1153                   "PDB");
1154           // parse pdb file into a chain, etc.
1155           // locate best match for pdb in associated views and add mapping to
1156           // ssm
1157           // if properly registered then
1158           notifyLoaded = true;
1159
1160         }
1161       }
1162     // FILE LOADED OK
1163     // so finally, update the jmol bits and pieces
1164     if (jmolpopup != null)
1165     {
1166       // potential for deadlock here:
1167       // jmolpopup.updateComputedMenus();
1168     }
1169     if (!isLoadingFromArchive())
1170     {
1171       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
1172     }
1173     setLoadingFromArchive(false);
1174     // register ourselves as a listener and notify the gui that it needs to
1175     // update itself.
1176     ssm.addStructureViewerListener(this);
1177     if (notifyLoaded)
1178     {
1179       FeatureRenderer fr = getFeatureRenderer();
1180       if (fr != null)
1181       {
1182         fr.featuresAdded();
1183       }
1184       refreshGUI();
1185     }
1186   }
1187
1188   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1189   {
1190     notifyAtomPicked(iatom, strMeasure, null);
1191   }
1192
1193   public abstract void notifyScriptTermination(String strStatus,
1194           int msWalltime);
1195
1196   /**
1197    * display a message echoed from the jmol viewer
1198    * 
1199    * @param strEcho
1200    */
1201   public abstract void sendConsoleEcho(String strEcho); /*
1202                                                          * { showConsole(true);
1203                                                          * 
1204                                                          * history.append("\n" +
1205                                                          * strEcho); }
1206                                                          */
1207
1208   // /End JmolStatusListener
1209   // /////////////////////////////
1210
1211   /**
1212    * @param strStatus
1213    *          status message - usually the response received after a script
1214    *          executed
1215    */
1216   public abstract void sendConsoleMessage(String strStatus);
1217
1218   public void setCallbackFunction(String callbackType,
1219           String callbackFunction)
1220   {
1221     System.err.println("Ignoring set-callback request to associate "
1222             + callbackType + " with function " + callbackFunction);
1223
1224   }
1225
1226   public void setJalviewColourScheme(ColourSchemeI cs)
1227   {
1228     colourBySequence = false;
1229
1230     if (cs == null)
1231       return;
1232
1233     String res;
1234     int index;
1235     Color col;
1236     jmolHistory(false);
1237     // TODO: Switch between nucleotide or aa selection expressions
1238     Enumeration en = ResidueProperties.aa3Hash.keys();
1239     StringBuffer command = new StringBuffer("select *;color white;");
1240     while (en.hasMoreElements())
1241     {
1242       res = en.nextElement().toString();
1243       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
1244       if (index > 20)
1245         continue;
1246
1247       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
1248
1249       command.append("select " + res + ";color[" + col.getRed() + ","
1250               + col.getGreen() + "," + col.getBlue() + "];");
1251     }
1252
1253     evalStateCommand(command.toString());
1254     jmolHistory(true);
1255   }
1256
1257   public void showHelp()
1258   {
1259     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1260   }
1261
1262   /**
1263    * open the URL somehow
1264    * 
1265    * @param target
1266    */
1267   public abstract void showUrl(String url, String target);
1268
1269   /**
1270    * called when the binding thinks the UI needs to be refreshed after a Jmol
1271    * state change. this could be because structures were loaded, or because an
1272    * error has occured.
1273    */
1274   public abstract void refreshGUI();
1275
1276   /**
1277    * @param renderPanel
1278    * @param jmolfileio
1279    *          - when true will initialise jmol's file IO system (should be false
1280    *          in applet context)
1281    * @param htmlName
1282    * @param documentBase
1283    * @param codeBase
1284    * @param commandOptions
1285    */
1286   public void allocateViewer(Component renderPanel, boolean jmolfileio,
1287           String htmlName, URL documentBase, URL codeBase,
1288           String commandOptions)
1289   {
1290     viewer = JmolViewer.allocateViewer(renderPanel,
1291             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1292                     + ((Object) this).toString(), documentBase, codeBase,
1293             commandOptions, this);
1294   }
1295
1296   public void setLoadingFromArchive(boolean loadingFromArchive)
1297   {
1298     this.loadingFromArchive = loadingFromArchive;
1299   }
1300
1301   public boolean isLoadingFromArchive()
1302   {
1303     return loadingFromArchive;
1304   }
1305
1306   public void setBackgroundColour(java.awt.Color col)
1307   {
1308     jmolHistory(false);
1309     viewer.evalStringQuiet("background [" + col.getRed() + ","
1310             + col.getGreen() + "," + col.getBlue() + "];");
1311     jmolHistory(true);
1312   }
1313
1314   /**
1315    * add structures and any known sequence associations
1316    * 
1317    * @returns the pdb entries added to the current set.
1318    */
1319   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
1320           SequenceI[][] seq, String[][] chns)
1321   {
1322     int pe = -1;
1323     Vector v = new Vector();
1324     Vector rtn = new Vector();
1325     for (int i = 0; i < pdbentry.length; i++)
1326     {
1327       v.addElement(pdbentry[i]);
1328     }
1329     for (int i = 0; i < pdbe.length; i++)
1330     {
1331       int r = v.indexOf(pdbe[i]);
1332       if (r == -1 || r >= pdbentry.length)
1333       {
1334         rtn.addElement(new int[]
1335         { v.size(), i });
1336         v.addElement(pdbe[i]);
1337       }
1338       else
1339       {
1340         // just make sure the sequence/chain entries are all up to date
1341         addSequenceAndChain(r, seq[i], chns[i]);
1342       }
1343     }
1344     pdbe = new PDBEntry[v.size()];
1345     v.copyInto(pdbe);
1346     pdbentry = pdbe;
1347     if (rtn.size() > 0)
1348     {
1349       // expand the tied seuqence[] and string[] arrays
1350       SequenceI[][] sqs = new SequenceI[pdbentry.length][];
1351       String[][] sch = new String[pdbentry.length][];
1352       System.arraycopy(sequence, 0, sqs, 0, sequence.length);
1353       System.arraycopy(chains, 0, sch, 0, this.chains.length);
1354       sequence = sqs;
1355       chains = sch;
1356       pdbe = new PDBEntry[rtn.size()];
1357       for (int r = 0; r < pdbe.length; r++)
1358       {
1359         int[] stri = ((int[]) rtn.elementAt(r));
1360         // record the pdb file as a new addition
1361         pdbe[r] = pdbentry[stri[0]];
1362         // and add the new sequence/chain entries
1363         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
1364       }
1365     }
1366     else
1367     {
1368       pdbe = null;
1369     }
1370     return pdbe;
1371   }
1372
1373   public void addSequence(int pe, SequenceI[] seq)
1374   {
1375     // add sequences to the pe'th pdbentry's seuqence set.
1376     addSequenceAndChain(pe, seq, null);
1377   }
1378
1379   private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
1380   {
1381     if (pe < 0 || pe >= pdbentry.length)
1382     {
1383       throw new Error(
1384               "Implementation error - no corresponding pdbentry (for index "
1385                       + pe + ") to add sequences mappings to");
1386     }
1387     final String nullChain = "TheNullChain";
1388     Vector s = new Vector();
1389     Vector c = new Vector();
1390     if (chains == null)
1391     {
1392       chains = new String[pdbentry.length][];
1393     }
1394     if (sequence[pe] != null)
1395     {
1396       for (int i = 0; i < sequence[pe].length; i++)
1397       {
1398         s.addElement(sequence[pe][i]);
1399         if (chains[pe] != null)
1400         {
1401           if (i < chains[pe].length)
1402           {
1403             c.addElement(chains[pe][i]);
1404           }
1405           else
1406           {
1407             c.addElement(nullChain);
1408           }
1409         }
1410         else
1411         {
1412           if (tchain != null && tchain.length > 0)
1413           {
1414             c.addElement(nullChain);
1415           }
1416         }
1417       }
1418     }
1419     for (int i = 0; i < seq.length; i++)
1420     {
1421       if (!s.contains(seq[i]))
1422       {
1423         s.addElement(seq[i]);
1424         if (tchain != null && i < tchain.length)
1425         {
1426           c.addElement(tchain[i] == null ? nullChain : tchain[i]);
1427         }
1428       }
1429     }
1430     SequenceI[] tmp = new SequenceI[s.size()];
1431     s.copyInto(tmp);
1432     sequence[pe] = tmp;
1433     if (c.size() > 0)
1434     {
1435       String[] tch = new String[c.size()];
1436       c.copyInto(tch);
1437       for (int i = 0; i < tch.length; i++)
1438       {
1439         if (tch[i] == nullChain)
1440         {
1441           tch[i] = null;
1442         }
1443       }
1444       chains[pe] = tch;
1445     }
1446     else
1447     {
1448       chains[pe] = null;
1449     }
1450   }
1451 }