revised sequence/structure binding so one structure associated with x-seuqenceI's...
[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)
165     {
166       return ("Jalview Jmol Window");
167     }
168     StringBuffer title = new StringBuffer(sequence[0][0].getName() + ":"
169             + pdbentry[0].getId());
170
171     if (pdbentry[0].getProperty() != null)
172     {
173       if (pdbentry[0].getProperty().get("method") != null)
174       {
175         title.append(" Method: ");
176         title.append(pdbentry[0].getProperty().get("method"));
177       }
178       if (pdbentry[0].getProperty().get("chains") != null)
179       {
180         title.append(" Chain:");
181         title.append(pdbentry[0].getProperty().get("chains"));
182       }
183     }
184     return title.toString();
185   }
186
187   /**
188    * prepare the view for a given set of models/chains. chainList contains
189    * strings of the form 'pdbfilename:Chaincode'
190    * 
191    * @param chainList
192    *          list of chains to make visible
193    */
194   public void centerViewer(Vector chainList)
195   {
196     StringBuffer cmd = new StringBuffer();
197     String lbl;
198     int mlength, p;
199     for (int i = 0, iSize = chainList.size(); i < iSize; i++)
200     {
201       mlength = 0;
202       lbl = (String) chainList.elementAt(i);
203       do
204       {
205         p = mlength;
206         mlength = lbl.indexOf(":", p);
207       } while (p < mlength && mlength < (lbl.length() - 2));
208       cmd.append(":" + lbl.substring(mlength + 1) + " /"
209               + getModelNum(lbl.substring(0, mlength)) + " or ");
210     }
211     if (cmd.length() > 0)
212       cmd.setLength(cmd.length() - 4);
213     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
214   }
215
216   public void closeViewer()
217   {
218     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
219     // remove listeners for all structures in viewer
220     StructureSelectionManager.getStructureSelectionManager()
221             .removeStructureViewerListener(this, this.getPdbFile());
222     // and shut down jmol
223     viewer.evalStringQuiet("zap");
224     viewer.setJmolStatusListener(null);
225     lastCommand = null;
226     viewer = null;
227   }
228
229   public void colourByChain()
230   {
231     colourBySequence = false;
232     evalStateCommand("select *;color chain");
233   }
234
235   public void colourByCharge()
236   {
237     colourBySequence = false;
238     evalStateCommand("select *;color white;select ASP,GLU;color red;"
239             + "select LYS,ARG;color blue;select CYS;color yellow");
240   }
241
242   /**
243    * superpose the structures associated with sequences in the alignment
244    * according to their corresponding positions.
245    */
246   public void superposeStructures(AlignmentI alignment)
247   {
248     superposeStructures(alignment, -1, null);
249   }
250
251   /**
252    * superpose the structures associated with sequences in the alignment
253    * according to their corresponding positions. ded)
254    * 
255    * @param refStructure
256    *          - select which pdb file to use as reference (default is -1 - the
257    *          first structure in the alignment)
258    */
259   public void superposeStructures(AlignmentI alignment, int refStructure)
260   {
261     superposeStructures(alignment, refStructure, null);
262   }
263
264   /**
265    * superpose the structures associated with sequences in the alignment
266    * according to their corresponding positions. ded)
267    * @param refStructure
268    *          - select which pdb file to use as reference (default is -1 - the
269    *          first structure in the alignment)
270    * @param hiddenCols TODO
271    */
272   public void superposeStructures(AlignmentI alignment, int refStructure, ColumnSelection hiddenCols)
273   {
274     String[] files = getPdbFile();
275     if (refStructure>=files.length)
276     {
277       System.err.println("Invalid reference structure value "+refStructure);
278       refStructure= -1;
279     }
280     if (refStructure<-1)
281     {
282       refStructure=-1;
283     }
284     StringBuffer command = new StringBuffer(), selectioncom = new StringBuffer();
285     
286     boolean matched[] = new boolean[alignment.getWidth()];
287     for (int m = 0; m < matched.length; m++)
288     {
289       
290       matched[m] = (hiddenCols!=null) ? hiddenCols.isVisible(m) : true;
291     }
292     
293     int commonrpositions[][] = new int[files.length][alignment.getWidth()];
294     String isel[] = new String[files.length];
295     // reference structure - all others are superposed in it
296     String[] targetC = new String[files.length];
297     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
298     {
299       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
300
301       if (mapping == null || mapping.length < 1)
302         continue;
303
304       int lastPos = -1;
305       for (int s = 0; s < sequence[pdbfnum].length; s++)
306       {
307         for (int sp, m = 0; m < mapping.length; m++)
308         {
309           if (mapping[m].getSequence() == sequence[pdbfnum][s]
310                   && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
311           {
312             if (refStructure == -1)
313             {
314               refStructure = pdbfnum;
315             }
316             SequenceI asp = alignment.getSequenceAt(sp);
317             for (int r = 0; r < matched.length; r++)
318             {
319               if (!matched[r])
320               {
321                 continue;
322               }
323               matched[r] = false; // assume this is not a good site
324               if (r >= asp.getLength())
325               {
326                 continue;
327               }
328
329               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
330               {
331                 // no mapping to gaps in sequence
332                 continue;
333               }
334               int t = asp.findPosition(r); // sequence position
335               int apos = mapping[m].getAtomNum(t);
336               int pos = mapping[m].getPDBResNum(t);
337
338               if (pos < 1 || pos == lastPos)
339               {
340                 // can't align unmapped sequence
341                 continue;
342               }
343               matched[r] = true; // this is a good ite
344               lastPos = pos;
345               // just record this residue position
346               commonrpositions[pdbfnum][r] = pos;
347             }
348             // create model selection suffix
349             isel[pdbfnum] = "/" + (pdbfnum + 1) + ".1";
350             if (mapping[m].getChain() == null
351                     || mapping[m].getChain().trim().length() == 0)
352             {
353               targetC[pdbfnum] = "";
354             }
355             else
356             {
357               targetC[pdbfnum] = ":" + mapping[m].getChain();
358             }
359             // move on to next pdb file
360             s = sequence[pdbfnum].length;
361             break;
362           }
363         }
364       }
365     }
366     StringBuffer selcom[] = new StringBuffer[files.length];
367     // generate select statements to select regions to superimpose structures
368     {
369       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
370       {
371         String chainCd = targetC[pdbfnum];
372         int lpos = -1;
373         boolean run = false;
374         StringBuffer molsel = (selcom[pdbfnum] = new StringBuffer());
375         molsel.append("{");
376         for (int r = 0; r < matched.length; r++)
377         {
378           if (matched[r])
379           {
380
381             if (lpos != commonrpositions[pdbfnum][r] - 1)
382             {
383               // discontinuity
384               if (lpos != -1)
385               {
386                 molsel.append(lpos);
387                 molsel.append(chainCd);
388                 // molsel.append("} {");
389                 molsel.append("|");
390               }
391             }
392             else
393             {
394               // continuous run - and lpos >-1
395               if (!run)
396               {
397                 // at the beginning, so add dash
398                 molsel.append(lpos);
399                 molsel.append("-");
400               }
401               run = true;
402             }
403             lpos = commonrpositions[pdbfnum][r];
404             // molsel.append(lpos);
405           }
406         }
407         // add final selection phrase
408         if (lpos != -1)
409         {
410           molsel.append(lpos);
411           molsel.append(chainCd);
412           molsel.append("}");
413         }
414         selectioncom.append("((");
415         selectioncom.append(molsel.subSequence(1, molsel.length()-1));
416         selectioncom.append(" )& ");
417         selectioncom.append(pdbfnum+1);
418         selectioncom.append(".1)");
419         if (pdbfnum<files.length-1)
420         {
421           selectioncom.append("|");
422         }
423       }
424     }
425
426     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
427     {
428       if (pdbfnum == refStructure)
429       {
430         continue;
431       }
432       command.append("compare ");
433       command.append("{");
434       command.append(1 + pdbfnum);
435       command.append(".1} {");
436       command.append(1 + refStructure);
437       command.append(".1} SUBSET {*.CA | *.P} ATOMS ");
438
439       // form the matched pair strings
440       String sep = "";
441       for (int s = 0; s < 2; s++)
442       {
443         command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
444       }
445       command.append(" ROTATE TRANSLATE;\n");
446     }
447     System.out.println("Select regions:\n" + selectioncom.toString());
448     evalStateCommand("select *; cartoons off; backbone; select ("+selectioncom.toString()+"); cartoons; ");
449     // selcom.append("; ribbons; ");
450     System.out.println("Superimpose command(s):\n" + command.toString());
451
452     evalStateCommand(command.toString());
453     
454     // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
455   }
456
457   public void evalStateCommand(String command)
458   {
459     jmolHistory(false);
460     if (lastCommand == null || !lastCommand.equals(command))
461     {
462       viewer.evalStringQuiet(command + "\n");
463     }
464     jmolHistory(true);
465     lastCommand = command;
466   }
467
468   /**
469    * colour any structures associated with sequences in the given alignment
470    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
471    * if colourBySequence is enabled.
472    */
473   public void colourBySequence(boolean showFeatures, AlignmentI alignment)
474   {
475     if (!colourBySequence)
476       return;
477     if (ssm == null)
478     {
479       return;
480     }
481     String[] files = getPdbFile();
482     SequenceRenderer sr = getSequenceRenderer();
483
484     FeatureRenderer fr = null;
485     if (showFeatures)
486     {
487       fr = getFeatureRenderer();
488     }
489
490     StringBuffer command = new StringBuffer();
491
492     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
493     {
494       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
495
496       if (mapping == null || mapping.length < 1)
497         continue;
498
499       int lastPos = -1;
500       for (int s = 0; s < sequence[pdbfnum].length; s++)
501       {
502         for (int sp, m = 0; m < mapping.length; m++)
503         {
504           if (mapping[m].getSequence() == sequence[pdbfnum][s]
505                   && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
506           {
507             SequenceI asp = alignment.getSequenceAt(sp);
508             for (int r = 0; r < asp.getLength(); r++)
509             {
510               // no mapping to gaps in sequence
511               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
512               {
513                 continue;
514               }
515               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
516
517               if (pos < 1 || pos == lastPos)
518                 continue;
519
520               lastPos = pos;
521
522               Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
523
524               if (showFeatures)
525                 col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
526               String newSelcom = (mapping[m].getChain() != " " ? ":"
527                       + mapping[m].getChain() : "")
528                       + "/"
529                       + (pdbfnum + 1)
530                       + ".1"
531                       + ";color["
532                       + col.getRed()
533                       + ","
534                       + col.getGreen()
535                       + ","
536                       + col.getBlue() + "]";
537               if (command.toString().endsWith(newSelcom))
538               {
539                 command = condenseCommand(command.toString(), pos);
540                 continue;
541               }
542               // TODO: deal with case when buffer is too large for Jmol to parse
543               // - execute command and flush
544
545               command.append(";select " + pos);
546               command.append(newSelcom);
547             }
548             break;
549           }
550         }
551       }
552     }
553     evalStateCommand(command.toString());
554   }
555
556   public boolean isColourBySequence()
557   {
558     return colourBySequence;
559   }
560
561   public void setColourBySequence(boolean colourBySequence)
562   {
563     this.colourBySequence = colourBySequence;
564   }
565
566   StringBuffer condenseCommand(String command, int pos)
567   {
568
569     StringBuffer sb = new StringBuffer(command.substring(0,
570             command.lastIndexOf("select") + 7));
571
572     command = command.substring(sb.length());
573
574     String start;
575
576     if (command.indexOf("-") > -1)
577     {
578       start = command.substring(0, command.indexOf("-"));
579     }
580     else
581     {
582       start = command.substring(0, command.indexOf(":"));
583     }
584
585     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
586
587     return sb;
588   }
589
590   public void createImage(String file, String type, int quality)
591   {
592     System.out.println("JMOL CREATE IMAGE");
593   }
594
595   public String createImage(String fileName, String type,
596           Object textOrBytes, int quality)
597   {
598     System.out.println("JMOL CREATE IMAGE");
599     return null;
600   }
601
602   public String eval(String strEval)
603   {
604     // System.out.println(strEval);
605     // "# 'eval' is implemented only for the applet.";
606     return null;
607   }
608
609   // End StructureListener
610   // //////////////////////////
611
612   public float[][] functionXY(String functionName, int x, int y)
613   {
614     return null;
615   }
616
617   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
618   {
619     // TODO Auto-generated method stub
620     return null;
621   }
622
623   public Color getColour(int atomIndex, int pdbResNum, String chain,
624           String pdbfile)
625   {
626     if (getModelNum(pdbfile) < 0)
627       return null;
628     // TODO: verify atomIndex is selecting correct model.
629     return new Color(viewer.getAtomArgb(atomIndex));
630   }
631
632   /**
633    * returns the current featureRenderer that should be used to colour the
634    * structures
635    * 
636    * @return
637    */
638   public abstract FeatureRenderer getFeatureRenderer();
639
640   /**
641    * instruct the Jalview binding to update the pdbentries vector if necessary
642    * prior to matching the jmol view's contents to the list of structure files
643    * Jalview knows about.
644    */
645   public abstract void refreshPdbEntries();
646
647   private int getModelNum(String modelFileName)
648   {
649     String[] mfn = getPdbFile();
650     if (mfn == null)
651     {
652       return -1;
653     }
654     for (int i = 0; i < mfn.length; i++)
655     {
656       if (mfn[i].equalsIgnoreCase(modelFileName))
657         return i;
658     }
659     return -1;
660   }
661
662   // ////////////////////////////////
663   // /StructureListener
664   public String[] getPdbFile()
665   {
666     if (modelFileNames == null)
667     {
668       String mset[] = new String[viewer.getModelCount()];
669       for (int i = 0; i < mset.length; i++)
670       {
671         mset[i] = viewer.getModelFileName(i);
672       }
673       modelFileNames = mset;
674     }
675     return modelFileNames;
676   }
677
678   /**
679    * map from string to applet
680    */
681   public Map getRegistryInfo()
682   {
683     // TODO Auto-generated method stub
684     return null;
685   }
686
687   /**
688    * returns the current sequenceRenderer that should be used to colour the
689    * structures
690    * 
691    * @return
692    */
693   public abstract SequenceRenderer getSequenceRenderer();
694
695   // ///////////////////////////////
696   // JmolStatusListener
697
698   public void handlePopupMenu(int x, int y)
699   {
700     jmolpopup.show(x, y);
701   }
702
703   // jmol/ssm only
704   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
705           String pdbfile)
706   {
707     if (modelFileNames == null)
708     {
709       return;
710     }
711
712     // look up file model number for this pdbfile
713     int mdlNum = 0;
714     String fn;
715     // may need to adjust for URLencoding here - we don't worry about that yet.
716     while (mdlNum < modelFileNames.length
717             && !pdbfile.equals(modelFileNames[mdlNum]))
718     {
719       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
720       mdlNum++;
721     }
722     if (mdlNum == modelFileNames.length)
723     {
724       return;
725     }
726
727     jmolHistory(false);
728     // if (!pdbfile.equals(pdbentry.getFile()))
729     // return;
730     if (resetLastRes.length() > 0)
731     {
732       viewer.evalStringQuiet(resetLastRes.toString());
733     }
734
735     eval.setLength(0);
736     eval.append("select " + pdbResNum); // +modelNum
737
738     resetLastRes.setLength(0);
739     resetLastRes.append("select " + pdbResNum); // +modelNum
740
741     eval.append(":");
742     resetLastRes.append(":");
743     if (!chain.equals(" "))
744     {
745       eval.append(chain);
746       resetLastRes.append(chain);
747     }
748     {
749       eval.append(" /" + (mdlNum + 1));
750       resetLastRes.append("/" + (mdlNum + 1));
751     }
752     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
753
754     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
755             + " and not hetero; spacefill 0;");
756
757     eval.append("spacefill 200;select none");
758
759     viewer.evalStringQuiet(eval.toString());
760     jmolHistory(true);
761
762   }
763
764   boolean debug = true;
765
766   private void jmolHistory(boolean enable)
767   {
768     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
769   }
770
771   public void loadInline(String string)
772   {
773     loadedInline = true;
774     viewer.openStringInline(string);
775   }
776
777   public void mouseOverStructure(int atomIndex, String strInfo)
778   {
779     int pdbResNum;
780     int mdlSep = strInfo.indexOf("/");
781     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
782
783     if (chainSeparator == -1)
784     {
785       chainSeparator = strInfo.indexOf(".");
786       if (mdlSep > -1 && mdlSep < chainSeparator)
787       {
788         chainSeparator1 = chainSeparator;
789         chainSeparator = mdlSep;
790       }
791     }
792     pdbResNum = Integer.parseInt(strInfo.substring(
793             strInfo.indexOf("]") + 1, chainSeparator));
794
795     String chainId;
796
797     if (strInfo.indexOf(":") > -1)
798       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
799               strInfo.indexOf("."));
800     else
801     {
802       chainId = " ";
803     }
804
805     String pdbfilename = modelFileNames[frameNo]; // default is first or current
806     // model
807     if (mdlSep > -1)
808     {
809       if (chainSeparator1 == -1)
810       {
811         chainSeparator1 = strInfo.indexOf(".", mdlSep);
812       }
813       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
814               chainSeparator1) : strInfo.substring(mdlSep + 1);
815       try
816       {
817         // recover PDB filename for the model hovered over.
818         pdbfilename = viewer
819                 .getModelFileName(new Integer(mdlId).intValue() - 1);
820       } catch (Exception e)
821       {
822       }
823       ;
824     }
825     if (lastMessage == null || !lastMessage.equals(strInfo))
826       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
827
828     lastMessage = strInfo;
829   }
830
831   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
832   {
833     if (data != null)
834     {
835       System.err.println("Ignoring additional hover info: " + data
836               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
837     }
838     mouseOverStructure(atomIndex, strInfo);
839   }
840
841   /*
842    * { if (history != null && strStatus != null &&
843    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
844    * } }
845    */
846
847   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
848   {
849     /**
850      * this implements the toggle label behaviour copied from the original
851      * structure viewer, MCView
852      */
853     if (strData != null)
854     {
855       System.err.println("Ignoring additional pick data string " + strData);
856     }
857     int chainSeparator = strInfo.indexOf(":");
858     int p = 0;
859     if (chainSeparator == -1)
860       chainSeparator = strInfo.indexOf(".");
861
862     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
863             chainSeparator);
864     String mdlString = "";
865     if ((p = strInfo.indexOf(":")) > -1)
866       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
867
868     if ((p = strInfo.indexOf("/")) > -1)
869     {
870       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
871     }
872     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
873             + mdlString + "))";
874     jmolHistory(false);
875
876     if (!atomsPicked.contains(picked))
877     {
878       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
879       atomsPicked.addElement(picked);
880     }
881     else
882     {
883       viewer.evalString("select " + picked + ";label off");
884       atomsPicked.removeElement(picked);
885     }
886     jmolHistory(true);
887     // TODO: in application this happens
888     //
889     // if (scriptWindow != null)
890     // {
891     // scriptWindow.sendConsoleMessage(strInfo);
892     // scriptWindow.sendConsoleMessage("\n");
893     // }
894
895   }
896
897   public void notifyCallback(int type, Object[] data)
898   {
899     try
900     {
901       switch (type)
902       {
903       case JmolConstants.CALLBACK_LOADSTRUCT:
904         notifyFileLoaded((String) data[1], (String) data[2],
905                 (String) data[3], (String) data[4],
906                 ((Integer) data[5]).intValue());
907
908         break;
909       case JmolConstants.CALLBACK_PICK:
910         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
911                 (String) data[0]);
912         // also highlight in alignment
913       case JmolConstants.CALLBACK_HOVER:
914         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
915                 (String) data[0]);
916         break;
917       case JmolConstants.CALLBACK_SCRIPT:
918         notifyScriptTermination((String) data[2],
919                 ((Integer) data[3]).intValue());
920         break;
921       case JmolConstants.CALLBACK_ECHO:
922         sendConsoleEcho((String) data[1]);
923         break;
924       case JmolConstants.CALLBACK_MESSAGE:
925         sendConsoleMessage((data == null) ? ((String) null)
926                 : (String) data[1]);
927         break;
928       case JmolConstants.CALLBACK_ERROR:
929         // System.err.println("Ignoring error callback.");
930         break;
931       case JmolConstants.CALLBACK_SYNC:
932       case JmolConstants.CALLBACK_RESIZE:
933         refreshGUI();
934         break;
935       case JmolConstants.CALLBACK_MEASURE:
936
937       case JmolConstants.CALLBACK_CLICK:
938
939       default:
940         System.err.println("Unhandled callback " + type + " "
941                 + data[1].toString());
942         break;
943       }
944     } catch (Exception e)
945     {
946       System.err.println("Squashed Jmol callback handler error:");
947       e.printStackTrace();
948     }
949   }
950
951   public boolean notifyEnabled(int callbackPick)
952   {
953     switch (callbackPick)
954     {
955     case JmolConstants.CALLBACK_ECHO:
956     case JmolConstants.CALLBACK_LOADSTRUCT:
957     case JmolConstants.CALLBACK_MEASURE:
958     case JmolConstants.CALLBACK_MESSAGE:
959     case JmolConstants.CALLBACK_PICK:
960     case JmolConstants.CALLBACK_SCRIPT:
961     case JmolConstants.CALLBACK_HOVER:
962     case JmolConstants.CALLBACK_ERROR:
963       return true;
964     case JmolConstants.CALLBACK_RESIZE:
965     case JmolConstants.CALLBACK_SYNC:
966     case JmolConstants.CALLBACK_CLICK:
967     case JmolConstants.CALLBACK_ANIMFRAME:
968     case JmolConstants.CALLBACK_MINIMIZATION:
969     }
970     return false;
971   }
972
973   public void notifyFileLoaded(String fullPathName, String fileName2,
974           String modelName, String errorMsg, int modelParts)
975   {
976     if (errorMsg != null)
977     {
978       fileLoadingError = errorMsg;
979       refreshGUI();
980       return;
981     }
982     // the rest of this routine ignores the arguments, and simply interrogates
983     // the Jmol view to find out what structures it contains, and adds them to
984     // the structure selection manager.
985     fileLoadingError = null;
986     String[] oldmodels = modelFileNames;
987     modelFileNames = null;
988     chainNames = new Vector();
989     boolean notifyLoaded = false;
990     String[] modelfilenames = getPdbFile();
991     ssm = StructureSelectionManager.getStructureSelectionManager();
992     // first check if we've lost any structures
993     if (oldmodels != null && oldmodels.length > 0)
994     {
995       int oldm = 0;
996       for (int i = 0; i < oldmodels.length; i++)
997       {
998         for (int n = 0; n < modelfilenames.length; n++)
999         {
1000           if (modelfilenames[n] == oldmodels[i])
1001           {
1002             oldmodels[i] = null;
1003             break;
1004           }
1005         }
1006         if (oldmodels[i] != null)
1007         {
1008           oldm++;
1009         }
1010       }
1011       if (oldm > 0)
1012       {
1013         String[] oldmfn = new String[oldm];
1014         oldm = 0;
1015         for (int i = 0; i < oldmodels.length; i++)
1016         {
1017           if (oldmodels[i] != null)
1018           {
1019             oldmfn[oldm++] = oldmodels[i];
1020           }
1021         }
1022         // deregister the Jmol instance for these structures - we'll add
1023         // ourselves again at the end for the current structure set.
1024         ssm.removeStructureViewerListener(this, oldmfn);
1025       }
1026     }
1027     refreshPdbEntries();
1028     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1029     {
1030       String fileName = modelfilenames[modelnum];
1031       if (fileName != null)
1032       {
1033         boolean foundEntry = false;
1034         // search pdbentries and sequences to find correct pdbentry and
1035         // sequence[] pair for this filename
1036         if (pdbentry != null)
1037         {
1038           for (int pe = 0; pe < pdbentry.length; pe++)
1039           {
1040             if (pdbentry[pe].getFile().equals(fileName))
1041             {
1042               foundEntry = true;
1043               MCview.PDBfile pdb;
1044               if (loadedInline)
1045               {
1046                 // TODO: replace with getData ?
1047                 pdb = ssm.setMapping(sequence[pe], chains[pe],
1048                         pdbentry[pe].getFile(), AppletFormatAdapter.PASTE);
1049                 pdbentry[pe].setFile("INLINE" + pdb.id);
1050               }
1051               else
1052               {
1053                 // TODO: Jmol can in principle retrieve from CLASSLOADER but
1054                 // this
1055                 // needs
1056                 // to be tested. See mantis bug
1057                 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1058                 String protocol = AppletFormatAdapter.URL;
1059                 try
1060                 {
1061                   File fl = new java.io.File(pdbentry[pe].getFile());
1062                   if (fl.exists())
1063                   {
1064                     protocol = AppletFormatAdapter.FILE;
1065                   }
1066                 } catch (Exception e)
1067                 {
1068                 } catch (Error e)
1069                 {
1070                 }
1071                 ;
1072                 pdb = ssm.setMapping(sequence[pe], chains[pe],
1073                         pdbentry[pe].getFile(), protocol);
1074
1075               }
1076
1077               pdbentry[pe].setId(pdb.id);
1078
1079               for (int i = 0; i < pdb.chains.size(); i++)
1080               {
1081                 chainNames.addElement(new String(pdb.id + ":"
1082                         + ((MCview.PDBChain) pdb.chains.elementAt(i)).id));
1083               }
1084               notifyLoaded = true;
1085             }
1086           }
1087         }
1088         if (!foundEntry && associateNewStructs)
1089         {
1090           // this is a foreign pdb file that jalview doesn't know about - add
1091           // it to the dataset and try to find a home - either on a matching
1092           // sequence or as a new sequence.
1093           String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1094                   "PDB");
1095           // parse pdb file into a chain, etc.
1096           // locate best match for pdb in associated views and add mapping to
1097           // ssm
1098           // if properly registered then
1099           notifyLoaded = true;
1100
1101         }
1102       }
1103     }
1104     // FILE LOADED OK
1105     // so finally, update the jmol bits and pieces
1106     if (jmolpopup != null)
1107     {
1108       jmolpopup.updateComputedMenus();
1109     }
1110     if (!isLoadingFromArchive())
1111     {
1112       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
1113     }
1114     setLoadingFromArchive(false);
1115     // register ourselves as a listener and notify the gui that it needs to
1116     // update itself.
1117     ssm.addStructureViewerListener(this);
1118     if (notifyLoaded)
1119     {
1120       FeatureRenderer fr = getFeatureRenderer();
1121       if (fr != null)
1122       {
1123         fr.featuresAdded();
1124       }
1125       refreshGUI();
1126     }
1127   }
1128
1129   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1130   {
1131     notifyAtomPicked(iatom, strMeasure, null);
1132   }
1133
1134   public abstract void notifyScriptTermination(String strStatus,
1135           int msWalltime);
1136
1137   /**
1138    * display a message echoed from the jmol viewer
1139    * 
1140    * @param strEcho
1141    */
1142   public abstract void sendConsoleEcho(String strEcho); /*
1143                                                          * { showConsole(true);
1144                                                          * 
1145                                                          * history.append("\n" +
1146                                                          * strEcho); }
1147                                                          */
1148
1149   // /End JmolStatusListener
1150   // /////////////////////////////
1151
1152   /**
1153    * @param strStatus
1154    *          status message - usually the response received after a script
1155    *          executed
1156    */
1157   public abstract void sendConsoleMessage(String strStatus);
1158
1159   public void setCallbackFunction(String callbackType,
1160           String callbackFunction)
1161   {
1162     System.err.println("Ignoring set-callback request to associate "
1163             + callbackType + " with function " + callbackFunction);
1164
1165   }
1166
1167   public void setJalviewColourScheme(ColourSchemeI cs)
1168   {
1169     colourBySequence = false;
1170
1171     if (cs == null)
1172       return;
1173
1174     String res;
1175     int index;
1176     Color col;
1177     jmolHistory(false);
1178     // TODO: Switch between nucleotide or aa selection expressions
1179     Enumeration en = ResidueProperties.aa3Hash.keys();
1180     StringBuffer command = new StringBuffer("select *;color white;");
1181     while (en.hasMoreElements())
1182     {
1183       res = en.nextElement().toString();
1184       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
1185       if (index > 20)
1186         continue;
1187
1188       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
1189
1190       command.append("select " + res + ";color[" + col.getRed() + ","
1191               + col.getGreen() + "," + col.getBlue() + "];");
1192     }
1193
1194     evalStateCommand(command.toString());
1195     jmolHistory(true);
1196   }
1197
1198   public void showHelp()
1199   {
1200     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1201   }
1202
1203   /**
1204    * open the URL somehow
1205    * 
1206    * @param target
1207    */
1208   public abstract void showUrl(String url, String target);
1209
1210   /**
1211    * called when the binding thinks the UI needs to be refreshed after a Jmol
1212    * state change. this could be because structures were loaded, or because an
1213    * error has occured.
1214    */
1215   public abstract void refreshGUI();
1216
1217   /**
1218    * @param renderPanel
1219    * @param jmolfileio
1220    *          - when true will initialise jmol's file IO system (should be false
1221    *          in applet context)
1222    * @param htmlName
1223    * @param documentBase
1224    * @param codeBase
1225    * @param commandOptions
1226    */
1227   public void allocateViewer(Component renderPanel, boolean jmolfileio,
1228           String htmlName, URL documentBase, URL codeBase,
1229           String commandOptions)
1230   {
1231     viewer = JmolViewer.allocateViewer(renderPanel,
1232             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1233                     + ((Object) this).toString(), documentBase, codeBase,
1234             commandOptions, this);
1235   }
1236
1237   public void setLoadingFromArchive(boolean loadingFromArchive)
1238   {
1239     this.loadingFromArchive = loadingFromArchive;
1240   }
1241
1242   public boolean isLoadingFromArchive()
1243   {
1244     return loadingFromArchive;
1245   }
1246
1247   public void setBackgroundColour(java.awt.Color col)
1248   {
1249     jmolHistory(false);
1250     viewer.evalStringQuiet("background [" + col.getRed() + ","
1251             + col.getGreen() + "," + col.getBlue() + "];");
1252     jmolHistory(true);
1253   }
1254 }