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