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