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