Merge branch 'develop' into features/JAL-2360colourSchemeApplicability
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ext.jmol;
22
23 import jalview.api.FeatureRenderer;
24 import jalview.api.SequenceRenderer;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.ColumnSelection;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.SequenceI;
29 import jalview.io.DataSourceType;
30 import jalview.io.StructureFile;
31 import jalview.schemes.ColourSchemeI;
32 import jalview.schemes.ResidueProperties;
33 import jalview.structure.AtomSpec;
34 import jalview.structure.StructureMappingcommandSet;
35 import jalview.structure.StructureSelectionManager;
36 import jalview.structures.models.AAStructureBindingModel;
37
38 import java.awt.Color;
39 import java.awt.Container;
40 import java.awt.event.ComponentEvent;
41 import java.awt.event.ComponentListener;
42 import java.io.File;
43 import java.net.URL;
44 import java.security.AccessControlException;
45 import java.util.ArrayList;
46 import java.util.Hashtable;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Vector;
50
51 import org.jmol.adapter.smarter.SmarterJmolAdapter;
52 import org.jmol.api.JmolAppConsoleInterface;
53 import org.jmol.api.JmolSelectionListener;
54 import org.jmol.api.JmolStatusListener;
55 import org.jmol.api.JmolViewer;
56 import org.jmol.c.CBK;
57 import org.jmol.script.T;
58 import org.jmol.viewer.Viewer;
59
60 public abstract class JalviewJmolBinding extends AAStructureBindingModel
61         implements JmolStatusListener, JmolSelectionListener,
62         ComponentListener
63 {
64   boolean allChainsSelected = false;
65
66   /*
67    * when true, try to search the associated datamodel for sequences that are
68    * associated with any unknown structures in the Jmol view.
69    */
70   private boolean associateNewStructs = false;
71
72   Vector<String> atomsPicked = new Vector<String>();
73
74   private List<String> chainNames;
75
76   Hashtable<String, String> chainFile;
77
78   /*
79    * the default or current model displayed if the model cannot be identified
80    * from the selection message
81    */
82   int frameNo = 0;
83
84   // protected JmolGenericPopup jmolpopup; // not used - remove?
85
86   String lastCommand;
87
88   String lastMessage;
89
90   boolean loadedInline;
91
92   StringBuffer resetLastRes = new StringBuffer();
93
94   public Viewer viewer;
95
96   public JalviewJmolBinding(StructureSelectionManager ssm,
97           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
98           DataSourceType protocol)
99   {
100     super(ssm, pdbentry, sequenceIs, protocol);
101     /*
102      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
103      * "jalviewJmol", ap.av.applet .getDocumentBase(),
104      * ap.av.applet.getCodeBase(), "", this);
105      * 
106      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
107      */
108   }
109
110   public JalviewJmolBinding(StructureSelectionManager ssm,
111           SequenceI[][] seqs, Viewer theViewer)
112   {
113     super(ssm, seqs);
114
115     viewer = theViewer;
116     viewer.setJmolStatusListener(this);
117     viewer.addSelectionListener(this);
118   }
119
120   /**
121    * construct a title string for the viewer window based on the data jalview
122    * knows about
123    * 
124    * @return
125    */
126   public String getViewerTitle()
127   {
128     return getViewerTitle("Jmol", true);
129   }
130
131   /**
132    * prepare the view for a given set of models/chains. chainList contains
133    * strings of the form 'pdbfilename:Chaincode'
134    * 
135    * @param chainList
136    *          list of chains to make visible
137    */
138   public void centerViewer(Vector<String> chainList)
139   {
140     StringBuilder cmd = new StringBuilder(128);
141     int mlength, p;
142     for (String lbl : chainList)
143     {
144       mlength = 0;
145       do
146       {
147         p = mlength;
148         mlength = lbl.indexOf(":", p);
149       } while (p < mlength && mlength < (lbl.length() - 2));
150       // TODO: lookup each pdb id and recover proper model number for it.
151       cmd.append(":" + lbl.substring(mlength + 1) + " /"
152               + (1 + getModelNum(chainFile.get(lbl))) + " or ");
153     }
154     if (cmd.length() > 0)
155     {
156       cmd.setLength(cmd.length() - 4);
157     }
158     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
159   }
160
161   public void closeViewer()
162   {
163     // remove listeners for all structures in viewer
164     getSsm().removeStructureViewerListener(this, this.getPdbFile());
165     viewer.dispose();
166     lastCommand = null;
167     viewer = null;
168     releaseUIResources();
169   }
170
171   public void colourByChain()
172   {
173     colourBySequence = false;
174     // TODO: colour by chain should colour each chain distinctly across all
175     // visible models
176     // TODO: http://issues.jalview.org/browse/JAL-628
177     evalStateCommand("select *;color chain");
178   }
179
180   public void colourByCharge()
181   {
182     colourBySequence = false;
183     evalStateCommand("select *;color white;select ASP,GLU;color red;"
184             + "select LYS,ARG;color blue;select CYS;color yellow");
185   }
186
187   /**
188    * superpose the structures associated with sequences in the alignment
189    * according to their corresponding positions.
190    */
191   public void superposeStructures(AlignmentI alignment)
192   {
193     superposeStructures(alignment, -1, null);
194   }
195
196   /**
197    * superpose the structures associated with sequences in the alignment
198    * according to their corresponding positions. ded)
199    * 
200    * @param refStructure
201    *          - select which pdb file to use as reference (default is -1 - the
202    *          first structure in the alignment)
203    */
204   public void superposeStructures(AlignmentI alignment, int refStructure)
205   {
206     superposeStructures(alignment, refStructure, null);
207   }
208
209   /**
210    * superpose the structures associated with sequences in the alignment
211    * according to their corresponding positions. ded)
212    * 
213    * @param refStructure
214    *          - select which pdb file to use as reference (default is -1 - the
215    *          first structure in the alignment)
216    * @param hiddenCols
217    *          TODO
218    */
219   public void superposeStructures(AlignmentI alignment, int refStructure,
220           ColumnSelection hiddenCols)
221   {
222     superposeStructures(new AlignmentI[] { alignment },
223             new int[] { refStructure },
224             new ColumnSelection[] { hiddenCols });
225   }
226
227   /**
228    * Construct and send a command to align structures against a reference
229    * structure, based on one or more sequence alignments
230    * 
231    * @param _alignment
232    *          an array of alignments to process
233    * @param _refStructure
234    *          an array of corresponding reference structures (index into pdb
235    *          file array); if a negative value is passed, the first PDB file
236    *          mapped to an alignment sequence is used as the reference for
237    *          superposition
238    * @param _hiddenCols
239    *          an array of corresponding hidden columns for each alignment
240    */
241   @Override
242   public void superposeStructures(AlignmentI[] _alignment,
243           int[] _refStructure, ColumnSelection[] _hiddenCols)
244   {
245     while (viewer.isScriptExecuting())
246     {
247       try
248       {
249         Thread.sleep(10);
250       } catch (InterruptedException i)
251       {
252       }
253     }
254
255     /*
256      * get the distinct structure files modelled
257      * (a file with multiple chains may map to multiple sequences)
258      */
259     String[] files = getPdbFile();
260     if (!waitForFileLoad(files))
261     {
262       return;
263     }
264
265     StringBuilder selectioncom = new StringBuilder(256);
266     // In principle - nSeconds specifies the speed of animation for each
267     // superposition - but is seems to behave weirdly, so we don't specify it.
268     String nSeconds = " ";
269     if (files.length > 10)
270     {
271       nSeconds = " 0.005 ";
272     }
273     else
274     {
275       nSeconds = " " + (2.0 / files.length) + " ";
276       // if (nSeconds).substring(0,5)+" ";
277     }
278     // see JAL-1345 - should really automatically turn off the animation for
279     // large numbers of structures, but Jmol doesn't seem to allow that.
280     // nSeconds = " ";
281     // union of all aligned positions are collected together.
282     for (int a = 0; a < _alignment.length; a++)
283     {
284       int refStructure = _refStructure[a];
285       AlignmentI alignment = _alignment[a];
286       ColumnSelection hiddenCols = _hiddenCols[a];
287       if (a > 0
288               && selectioncom.length() > 0
289               && !selectioncom.substring(selectioncom.length() - 1).equals(
290                       "|"))
291       {
292         selectioncom.append("|");
293       }
294       // process this alignment
295       if (refStructure >= files.length)
296       {
297         System.err.println("Invalid reference structure value "
298                 + refStructure);
299         refStructure = -1;
300       }
301
302       /*
303        * 'matched' array will hold 'true' for visible alignment columns where
304        * all sequences have a residue with a mapping to the PDB structure
305        */
306       // TODO could use a BitSet for matched
307       boolean matched[] = new boolean[alignment.getWidth()];
308       for (int m = 0; m < matched.length; m++)
309       {
310         matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
311       }
312
313       SuperposeData[] structures = new SuperposeData[files.length];
314       for (int f = 0; f < files.length; f++)
315       {
316         structures[f] = new SuperposeData(alignment.getWidth());
317       }
318
319       /*
320        * Calculate the superposable alignment columns ('matched'), and the
321        * corresponding structure residue positions (structures.pdbResNo)
322        */
323       int candidateRefStructure = findSuperposableResidues(alignment,
324               matched, structures);
325       if (refStructure < 0)
326       {
327         /*
328          * If no reference structure was specified, pick the first one that has
329          * a mapping in the alignment
330          */
331         refStructure = candidateRefStructure;
332       }
333
334       String[] selcom = new String[files.length];
335       int nmatched = 0;
336       for (boolean b : matched)
337       {
338         if (b)
339         {
340           nmatched++;
341         }
342       }
343       if (nmatched < 4)
344       {
345         // TODO: bail out here because superposition illdefined?
346       }
347
348       /*
349        * generate select statements to select regions to superimpose structures
350        */
351       {
352         // TODO extract method to construct selection statements
353         for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
354         {
355           String chainCd = ":" + structures[pdbfnum].chain;
356           int lpos = -1;
357           boolean run = false;
358           StringBuilder molsel = new StringBuilder();
359           molsel.append("{");
360           for (int r = 0; r < matched.length; r++)
361           {
362             if (matched[r])
363             {
364               int pdbResNo = structures[pdbfnum].pdbResNo[r];
365               if (lpos != pdbResNo - 1)
366               {
367                 // discontinuity
368                 if (lpos != -1)
369                 {
370                   molsel.append(lpos);
371                   molsel.append(chainCd);
372                   molsel.append("|");
373                 }
374                 run = false;
375               }
376               else
377               {
378                 // continuous run - and lpos >-1
379                 if (!run)
380                 {
381                   // at the beginning, so add dash
382                   molsel.append(lpos);
383                   molsel.append("-");
384                 }
385                 run = true;
386               }
387               lpos = pdbResNo;
388             }
389           }
390           /*
391            * add final selection phrase
392            */
393           if (lpos != -1)
394           {
395             molsel.append(lpos);
396             molsel.append(chainCd);
397             molsel.append("}");
398           }
399           if (molsel.length() > 1)
400           {
401             selcom[pdbfnum] = molsel.toString();
402             selectioncom.append("((");
403             selectioncom.append(selcom[pdbfnum].substring(1,
404                     selcom[pdbfnum].length() - 1));
405             selectioncom.append(" )& ");
406             selectioncom.append(pdbfnum + 1);
407             selectioncom.append(".1)");
408             if (pdbfnum < files.length - 1)
409             {
410               selectioncom.append("|");
411             }
412           }
413           else
414           {
415             selcom[pdbfnum] = null;
416           }
417         }
418       }
419       StringBuilder command = new StringBuilder(256);
420       // command.append("set spinFps 10;\n");
421
422       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
423       {
424         if (pdbfnum == refStructure || selcom[pdbfnum] == null
425                 || selcom[refStructure] == null)
426         {
427           continue;
428         }
429         command.append("echo ");
430         command.append("\"Superposing (");
431         command.append(structures[pdbfnum].pdbId);
432         command.append(") against reference (");
433         command.append(structures[refStructure].pdbId);
434         command.append(")\";\ncompare " + nSeconds);
435         command.append("{");
436         command.append(Integer.toString(1 + pdbfnum));
437         command.append(".1} {");
438         command.append(Integer.toString(1 + refStructure));
439         // conformation=1 excludes alternate locations for CA (JAL-1757)
440         command.append(".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
441
442         // for (int s = 0; s < 2; s++)
443         // {
444         // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
445         // }
446         command.append(selcom[pdbfnum]);
447         command.append(selcom[refStructure]);
448         command.append(" ROTATE TRANSLATE;\n");
449       }
450       if (selectioncom.length() > 0)
451       {
452         // TODO is performing selectioncom redundant here? is done later on
453         // System.out.println("Select regions:\n" + selectioncom.toString());
454         evalStateCommand("select *; cartoons off; backbone; select ("
455                 + selectioncom.toString() + "); cartoons; ");
456         // selcom.append("; ribbons; ");
457         String cmdString = command.toString();
458         // System.out.println("Superimpose command(s):\n" + cmdString);
459
460         evalStateCommand(cmdString);
461       }
462     }
463     if (selectioncom.length() > 0)
464     {// finally, mark all regions that were superposed.
465       if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
466       {
467         selectioncom.setLength(selectioncom.length() - 1);
468       }
469       // System.out.println("Select regions:\n" + selectioncom.toString());
470       evalStateCommand("select *; cartoons off; backbone; select ("
471               + selectioncom.toString() + "); cartoons; ");
472       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
473     }
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    * Sends a set of colour commands to the structure viewer
489    * 
490    * @param colourBySequenceCommands
491    */
492   @Override
493   protected void colourBySequence(
494           StructureMappingcommandSet[] colourBySequenceCommands)
495   {
496     for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
497     {
498       for (String cbyseq : cpdbbyseq.commands)
499       {
500         executeWhenReady(cbyseq);
501       }
502     }
503   }
504
505   /**
506    * @param files
507    * @param sr
508    * @param fr
509    * @param alignment
510    * @return
511    */
512   @Override
513   protected StructureMappingcommandSet[] getColourBySequenceCommands(
514           String[] files, SequenceRenderer sr, FeatureRenderer fr,
515           AlignmentI alignment)
516   {
517     return JmolCommands.getColourBySequenceCommand(getSsm(), files,
518             getSequence(), sr, fr, alignment);
519   }
520
521   /**
522    * @param command
523    */
524   protected void executeWhenReady(String command)
525   {
526     evalStateCommand(command);
527   }
528
529   public void createImage(String file, String type, int quality)
530   {
531     System.out.println("JMOL CREATE IMAGE");
532   }
533
534   @Override
535   public String createImage(String fileName, String type,
536           Object textOrBytes, int quality)
537   {
538     System.out.println("JMOL CREATE IMAGE");
539     return null;
540   }
541
542   @Override
543   public String eval(String strEval)
544   {
545     // System.out.println(strEval);
546     // "# 'eval' is implemented only for the applet.";
547     return null;
548   }
549
550   // End StructureListener
551   // //////////////////////////
552
553   @Override
554   public float[][] functionXY(String functionName, int x, int y)
555   {
556     return null;
557   }
558
559   @Override
560   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
561   {
562     // TODO Auto-generated method stub
563     return null;
564   }
565
566   public Color getColour(int atomIndex, int pdbResNum, String chain,
567           String pdbfile)
568   {
569     if (getModelNum(pdbfile) < 0)
570     {
571       return null;
572     }
573     // TODO: verify atomIndex is selecting correct model.
574     // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4
575     int colour = viewer.ms.at[atomIndex].atomPropertyInt(T.color);
576     return new Color(colour);
577   }
578
579   /**
580    * instruct the Jalview binding to update the pdbentries vector if necessary
581    * prior to matching the jmol view's contents to the list of structure files
582    * Jalview knows about.
583    */
584   public abstract void refreshPdbEntries();
585
586   private int getModelNum(String modelFileName)
587   {
588     String[] mfn = getPdbFile();
589     if (mfn == null)
590     {
591       return -1;
592     }
593     for (int i = 0; i < mfn.length; i++)
594     {
595       if (mfn[i].equalsIgnoreCase(modelFileName))
596       {
597         return i;
598       }
599     }
600     return -1;
601   }
602
603   /**
604    * map between index of model filename returned from getPdbFile and the first
605    * index of models from this file in the viewer. Note - this is not trimmed -
606    * use getPdbFile to get number of unique models.
607    */
608   private int _modelFileNameMap[];
609
610   // ////////////////////////////////
611   // /StructureListener
612   @Override
613   public synchronized String[] getPdbFile()
614   {
615     if (viewer == null)
616     {
617       return new String[0];
618     }
619     if (modelFileNames == null)
620     {
621       List<String> mset = new ArrayList<String>();
622       _modelFileNameMap = new int[viewer.ms.mc];
623       String m = viewer.ms.getModelFileName(0);
624       if (m != null)
625       {
626         String filePath = m;
627         try
628         {
629           filePath = new File(m).getAbsolutePath();
630         } catch (AccessControlException x)
631         {
632           // usually not allowed to do this in applet
633           System.err
634                   .println("jmolBinding: Using local file string from Jmol: "
635                           + m);
636         }
637         if (filePath.indexOf("/file:") != -1)
638         {
639           // applet path with docroot - discard as format won't match pdbfile
640           filePath = m;
641         }
642         mset.add(filePath);
643         _modelFileNameMap[0] = 0; // filename index for first model is always 0.
644       }
645       int j = 1;
646       for (int i = 1; i < viewer.ms.mc; i++)
647       {
648         m = viewer.ms.getModelFileName(i);
649         String filePath = m;
650         if (m != null)
651         {
652           try
653           {
654             filePath = new File(m).getAbsolutePath();
655           } catch (AccessControlException x)
656           {
657             // usually not allowed to do this in applet, so keep raw handle
658             // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
659           }
660         }
661
662         /*
663          * add this model unless it is read from a structure file we have
664          * already seen (example: 2MJW is an NMR structure with 10 models)
665          */
666         if (!mset.contains(filePath))
667         {
668           mset.add(filePath);
669           _modelFileNameMap[j] = i; // record the model index for the filename
670           j++;
671         }
672       }
673       modelFileNames = mset.toArray(new String[mset.size()]);
674     }
675     return modelFileNames;
676   }
677
678   /**
679    * map from string to applet
680    */
681   @Override
682   public Map<String, Object> getRegistryInfo()
683   {
684     // TODO Auto-generated method stub
685     return null;
686   }
687
688   
689
690   // ///////////////////////////////
691   // JmolStatusListener
692
693   public void handlePopupMenu(int x, int y)
694   {
695     // jmolpopup.show(x, y);
696     // jmolpopup.jpiShow(x, y);
697   }
698
699   /**
700    * Highlight zero, one or more atoms on the structure
701    */
702   @Override
703   public void highlightAtoms(List<AtomSpec> atoms)
704   {
705     if (atoms != null)
706     {
707       if (resetLastRes.length() > 0)
708       {
709         viewer.evalStringQuiet(resetLastRes.toString());
710         resetLastRes.setLength(0);
711       }
712       for (AtomSpec atom : atoms)
713       {
714         highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
715                 atom.getChain(), atom.getPdbFile());
716       }
717     }
718   }
719
720   // jmol/ssm only
721   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
722           String pdbfile)
723   {
724     if (modelFileNames == null)
725     {
726       return;
727     }
728
729     // look up file model number for this pdbfile
730     int mdlNum = 0;
731     // may need to adjust for URLencoding here - we don't worry about that yet.
732     while (mdlNum < modelFileNames.length
733             && !pdbfile.equals(modelFileNames[mdlNum]))
734     {
735       mdlNum++;
736     }
737     if (mdlNum == modelFileNames.length)
738     {
739       return;
740     }
741
742     jmolHistory(false);
743
744     StringBuilder cmd = new StringBuilder(64);
745     cmd.append("select " + pdbResNum); // +modelNum
746
747     resetLastRes.append("select " + pdbResNum); // +modelNum
748
749     cmd.append(":");
750     resetLastRes.append(":");
751     if (!chain.equals(" "))
752     {
753       cmd.append(chain);
754       resetLastRes.append(chain);
755     }
756     {
757       cmd.append(" /" + (mdlNum + 1));
758       resetLastRes.append("/" + (mdlNum + 1));
759     }
760     cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;");
761
762     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
763             + " and not hetero; spacefill 0;");
764
765     cmd.append("spacefill 200;select none");
766
767     viewer.evalStringQuiet(cmd.toString());
768     jmolHistory(true);
769
770   }
771
772   boolean debug = true;
773
774   private void jmolHistory(boolean enable)
775   {
776     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
777   }
778
779   public void loadInline(String string)
780   {
781     loadedInline = true;
782     // TODO: re JAL-623
783     // viewer.loadInline(strModel, isAppend);
784     // could do this:
785     // construct fake fullPathName and fileName so we can identify the file
786     // later.
787     // Then, construct pass a reader for the string to Jmol.
788     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
789     // fileName, null, reader, false, null, null, 0);
790     viewer.openStringInline(string);
791   }
792
793   public void mouseOverStructure(int atomIndex, String strInfo)
794   {
795     int pdbResNum;
796     int alocsep = strInfo.indexOf("^");
797     int mdlSep = strInfo.indexOf("/");
798     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
799
800     if (chainSeparator == -1)
801     {
802       chainSeparator = strInfo.indexOf(".");
803       if (mdlSep > -1 && mdlSep < chainSeparator)
804       {
805         chainSeparator1 = chainSeparator;
806         chainSeparator = mdlSep;
807       }
808     }
809     // handle insertion codes
810     if (alocsep != -1)
811     {
812       pdbResNum = Integer.parseInt(strInfo.substring(
813               strInfo.indexOf("]") + 1, alocsep));
814
815     }
816     else
817     {
818       pdbResNum = Integer.parseInt(strInfo.substring(
819               strInfo.indexOf("]") + 1, chainSeparator));
820     }
821     String chainId;
822
823     if (strInfo.indexOf(":") > -1)
824     {
825       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
826               strInfo.indexOf("."));
827     }
828     else
829     {
830       chainId = " ";
831     }
832
833     String pdbfilename = modelFileNames[frameNo]; // default is first or current
834     // model
835     if (mdlSep > -1)
836     {
837       if (chainSeparator1 == -1)
838       {
839         chainSeparator1 = strInfo.indexOf(".", mdlSep);
840       }
841       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
842               chainSeparator1) : strInfo.substring(mdlSep + 1);
843       try
844       {
845         // recover PDB filename for the model hovered over.
846         int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
847                 .intValue() - 1;
848         while (mnumber < _modelFileNameMap[_mp])
849         {
850           _mp--;
851         }
852         pdbfilename = modelFileNames[_mp];
853         if (pdbfilename == null)
854         {
855           pdbfilename = new File(viewer.ms.getModelFileName(mnumber))
856                   .getAbsolutePath();
857         }
858
859       } catch (Exception e)
860       {
861       }
862       ;
863     }
864     if (lastMessage == null || !lastMessage.equals(strInfo))
865     {
866       getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
867     }
868
869     lastMessage = strInfo;
870   }
871
872   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
873   {
874     if (data != null)
875     {
876       System.err.println("Ignoring additional hover info: " + data
877               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
878     }
879     mouseOverStructure(atomIndex, strInfo);
880   }
881
882   /*
883    * { if (history != null && strStatus != null &&
884    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
885    * } }
886    */
887
888   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
889   {
890     /**
891      * this implements the toggle label behaviour copied from the original
892      * structure viewer, MCView
893      */
894     if (strData != null)
895     {
896       System.err.println("Ignoring additional pick data string " + strData);
897     }
898     int chainSeparator = strInfo.indexOf(":");
899     int p = 0;
900     if (chainSeparator == -1)
901     {
902       chainSeparator = strInfo.indexOf(".");
903     }
904
905     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
906             chainSeparator);
907     String mdlString = "";
908     if ((p = strInfo.indexOf(":")) > -1)
909     {
910       picked += strInfo.substring(p, strInfo.indexOf("."));
911     }
912
913     if ((p = strInfo.indexOf("/")) > -1)
914     {
915       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
916     }
917     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
918             + mdlString + "))";
919     jmolHistory(false);
920
921     if (!atomsPicked.contains(picked))
922     {
923       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
924       atomsPicked.addElement(picked);
925     }
926     else
927     {
928       viewer.evalString("select " + picked + ";label off");
929       atomsPicked.removeElement(picked);
930     }
931     jmolHistory(true);
932     // TODO: in application this happens
933     //
934     // if (scriptWindow != null)
935     // {
936     // scriptWindow.sendConsoleMessage(strInfo);
937     // scriptWindow.sendConsoleMessage("\n");
938     // }
939
940   }
941
942   @Override
943   public void notifyCallback(CBK type, Object[] data)
944   {
945     try
946     {
947       switch (type)
948       {
949       case LOADSTRUCT:
950         notifyFileLoaded((String) data[1], (String) data[2],
951                 (String) data[3], (String) data[4],
952                 ((Integer) data[5]).intValue());
953
954         break;
955       case PICK:
956         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
957                 (String) data[0]);
958         // also highlight in alignment
959       case HOVER:
960         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
961                 (String) data[0]);
962         break;
963       case SCRIPT:
964         notifyScriptTermination((String) data[2],
965                 ((Integer) data[3]).intValue());
966         break;
967       case ECHO:
968         sendConsoleEcho((String) data[1]);
969         break;
970       case MESSAGE:
971         sendConsoleMessage((data == null) ? ((String) null)
972                 : (String) data[1]);
973         break;
974       case ERROR:
975         // System.err.println("Ignoring error callback.");
976         break;
977       case SYNC:
978       case RESIZE:
979         refreshGUI();
980         break;
981       case MEASURE:
982
983       case CLICK:
984       default:
985         System.err.println("Unhandled callback " + type + " "
986                 + data[1].toString());
987         break;
988       }
989     } catch (Exception e)
990     {
991       System.err.println("Squashed Jmol callback handler error:");
992       e.printStackTrace();
993     }
994   }
995
996   @Override
997   public boolean notifyEnabled(CBK callbackPick)
998   {
999     switch (callbackPick)
1000     {
1001     case ECHO:
1002     case LOADSTRUCT:
1003     case MEASURE:
1004     case MESSAGE:
1005     case PICK:
1006     case SCRIPT:
1007     case HOVER:
1008     case ERROR:
1009       return true;
1010     default:
1011       return false;
1012     }
1013   }
1014
1015   // incremented every time a load notification is successfully handled -
1016   // lightweight mechanism for other threads to detect when they can start
1017   // referrring to new structures.
1018   private long loadNotifiesHandled = 0;
1019
1020   public long getLoadNotifiesHandled()
1021   {
1022     return loadNotifiesHandled;
1023   }
1024
1025   public void notifyFileLoaded(String fullPathName, String fileName2,
1026           String modelName, String errorMsg, int modelParts)
1027   {
1028     if (errorMsg != null)
1029     {
1030       fileLoadingError = errorMsg;
1031       refreshGUI();
1032       return;
1033     }
1034     // TODO: deal sensibly with models loaded inLine:
1035     // modelName will be null, as will fullPathName.
1036
1037     // the rest of this routine ignores the arguments, and simply interrogates
1038     // the Jmol view to find out what structures it contains, and adds them to
1039     // the structure selection manager.
1040     fileLoadingError = null;
1041     String[] oldmodels = modelFileNames;
1042     modelFileNames = null;
1043     chainNames = new ArrayList<String>();
1044     chainFile = new Hashtable<String, String>();
1045     boolean notifyLoaded = false;
1046     String[] modelfilenames = getPdbFile();
1047     // first check if we've lost any structures
1048     if (oldmodels != null && oldmodels.length > 0)
1049     {
1050       int oldm = 0;
1051       for (int i = 0; i < oldmodels.length; i++)
1052       {
1053         for (int n = 0; n < modelfilenames.length; n++)
1054         {
1055           if (modelfilenames[n] == oldmodels[i])
1056           {
1057             oldmodels[i] = null;
1058             break;
1059           }
1060         }
1061         if (oldmodels[i] != null)
1062         {
1063           oldm++;
1064         }
1065       }
1066       if (oldm > 0)
1067       {
1068         String[] oldmfn = new String[oldm];
1069         oldm = 0;
1070         for (int i = 0; i < oldmodels.length; i++)
1071         {
1072           if (oldmodels[i] != null)
1073           {
1074             oldmfn[oldm++] = oldmodels[i];
1075           }
1076         }
1077         // deregister the Jmol instance for these structures - we'll add
1078         // ourselves again at the end for the current structure set.
1079         getSsm().removeStructureViewerListener(this, oldmfn);
1080       }
1081     }
1082     refreshPdbEntries();
1083     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1084     {
1085       String fileName = modelfilenames[modelnum];
1086       boolean foundEntry = false;
1087       StructureFile pdb = null;
1088       String pdbfile = null;
1089       // model was probably loaded inline - so check the pdb file hashcode
1090       if (loadedInline)
1091       {
1092         // calculate essential attributes for the pdb data imported inline.
1093         // prolly need to resolve modelnumber properly - for now just use our
1094         // 'best guess'
1095         pdbfile = viewer.getData("" + (1 + _modelFileNameMap[modelnum])
1096                 + ".0", "PDB");
1097       }
1098       // search pdbentries and sequences to find correct pdbentry for this
1099       // model
1100       for (int pe = 0; pe < getPdbCount(); pe++)
1101       {
1102         boolean matches = false;
1103         addSequence(pe, getSequence()[pe]);
1104         if (fileName == null)
1105         {
1106           if (false)
1107           // see JAL-623 - need method of matching pasted data up
1108           {
1109             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
1110                     pdbfile, DataSourceType.PASTE);
1111             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
1112             matches = true;
1113             foundEntry = true;
1114           }
1115         }
1116         else
1117         {
1118           File fl = new File(getPdbEntry(pe).getFile());
1119           matches = fl.equals(new File(fileName));
1120           if (matches)
1121           {
1122             foundEntry = true;
1123             // TODO: Jmol can in principle retrieve from CLASSLOADER but
1124             // this
1125             // needs
1126             // to be tested. See mantis bug
1127             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1128             DataSourceType protocol = DataSourceType.URL;
1129             try
1130             {
1131               if (fl.exists())
1132               {
1133                 protocol = DataSourceType.FILE;
1134               }
1135             } catch (Exception e)
1136             {
1137             } catch (Error e)
1138             {
1139             }
1140             // Explicitly map to the filename used by Jmol ;
1141             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
1142                     fileName, protocol);
1143             // pdbentry[pe].getFile(), protocol);
1144
1145           }
1146         }
1147         if (matches)
1148         {
1149           // add an entry for every chain in the model
1150           for (int i = 0; i < pdb.getChains().size(); i++)
1151           {
1152             String chid = new String(pdb.getId() + ":"
1153                     + pdb.getChains().elementAt(i).id);
1154             chainFile.put(chid, fileName);
1155             chainNames.add(chid);
1156           }
1157           notifyLoaded = true;
1158         }
1159       }
1160
1161       if (!foundEntry && associateNewStructs)
1162       {
1163         // this is a foreign pdb file that jalview doesn't know about - add
1164         // it to the dataset and try to find a home - either on a matching
1165         // sequence or as a new sequence.
1166         String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1167                 "PDB");
1168         // parse pdb file into a chain, etc.
1169         // locate best match for pdb in associated views and add mapping to
1170         // ssm
1171         // if properly registered then
1172         notifyLoaded = true;
1173
1174       }
1175     }
1176     // FILE LOADED OK
1177     // so finally, update the jmol bits and pieces
1178     // if (jmolpopup != null)
1179     // {
1180     // // potential for deadlock here:
1181     // // jmolpopup.updateComputedMenus();
1182     // }
1183     if (!isLoadingFromArchive())
1184     {
1185       viewer.evalStringQuiet("model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
1186     }
1187     // register ourselves as a listener and notify the gui that it needs to
1188     // update itself.
1189     getSsm().addStructureViewerListener(this);
1190     if (notifyLoaded)
1191     {
1192       FeatureRenderer fr = getFeatureRenderer(null);
1193       if (fr != null)
1194       {
1195         fr.featuresAdded();
1196       }
1197       refreshGUI();
1198       loadNotifiesHandled++;
1199     }
1200     setLoadingFromArchive(false);
1201   }
1202
1203   @Override
1204   public List<String> getChainNames()
1205   {
1206     return chainNames;
1207   }
1208
1209   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1210   {
1211     notifyAtomPicked(iatom, strMeasure, null);
1212   }
1213
1214   public abstract void notifyScriptTermination(String strStatus,
1215           int msWalltime);
1216
1217   /**
1218    * display a message echoed from the jmol viewer
1219    * 
1220    * @param strEcho
1221    */
1222   public abstract void sendConsoleEcho(String strEcho); /*
1223                                                          * { showConsole(true);
1224                                                          * 
1225                                                          * history.append("\n" +
1226                                                          * strEcho); }
1227                                                          */
1228
1229   // /End JmolStatusListener
1230   // /////////////////////////////
1231
1232   /**
1233    * @param strStatus
1234    *          status message - usually the response received after a script
1235    *          executed
1236    */
1237   public abstract void sendConsoleMessage(String strStatus);
1238
1239   @Override
1240   public void setCallbackFunction(String callbackType,
1241           String callbackFunction)
1242   {
1243     System.err.println("Ignoring set-callback request to associate "
1244             + callbackType + " with function " + callbackFunction);
1245
1246   }
1247
1248   @Override
1249   public void setJalviewColourScheme(ColourSchemeI cs)
1250   {
1251     colourBySequence = false;
1252
1253     if (cs == null)
1254     {
1255       return;
1256     }
1257
1258     jmolHistory(false);
1259     StringBuilder command = new StringBuilder(128);
1260     command.append("select *;color white;");
1261     List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
1262             false);
1263     for (String resName : residueSet)
1264     {
1265       char res = resName.length() == 3 ? ResidueProperties
1266               .getSingleCharacterCode(resName) : resName.charAt(0);
1267       Color col = cs.findColour(res);
1268       command.append("select " + resName + ";color[" + col.getRed() + ","
1269               + col.getGreen() + "," + col.getBlue() + "];");
1270     }
1271
1272     evalStateCommand(command.toString());
1273     jmolHistory(true);
1274   }
1275
1276   public void showHelp()
1277   {
1278     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1279   }
1280
1281   /**
1282    * open the URL somehow
1283    * 
1284    * @param target
1285    */
1286   public abstract void showUrl(String url, String target);
1287
1288   /**
1289    * called when the binding thinks the UI needs to be refreshed after a Jmol
1290    * state change. this could be because structures were loaded, or because an
1291    * error has occured.
1292    */
1293   public abstract void refreshGUI();
1294
1295   /**
1296    * called to show or hide the associated console window container.
1297    * 
1298    * @param show
1299    */
1300   public abstract void showConsole(boolean show);
1301
1302   /**
1303    * @param renderPanel
1304    * @param jmolfileio
1305    *          - when true will initialise jmol's file IO system (should be false
1306    *          in applet context)
1307    * @param htmlName
1308    * @param documentBase
1309    * @param codeBase
1310    * @param commandOptions
1311    */
1312   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1313           String htmlName, URL documentBase, URL codeBase,
1314           String commandOptions)
1315   {
1316     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
1317             codeBase, commandOptions, null, null);
1318   }
1319
1320   /**
1321    * 
1322    * @param renderPanel
1323    * @param jmolfileio
1324    *          - when true will initialise jmol's file IO system (should be false
1325    *          in applet context)
1326    * @param htmlName
1327    * @param documentBase
1328    * @param codeBase
1329    * @param commandOptions
1330    * @param consolePanel
1331    *          - panel to contain Jmol console
1332    * @param buttonsToShow
1333    *          - buttons to show on the console, in ordr
1334    */
1335   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1336           String htmlName, URL documentBase, URL codeBase,
1337           String commandOptions, final Container consolePanel,
1338           String buttonsToShow)
1339   {
1340     if (commandOptions == null)
1341     {
1342       commandOptions = "";
1343     }
1344     viewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
1345             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1346                     + ((Object) this).toString(), documentBase, codeBase,
1347             commandOptions, this);
1348
1349     viewer.setJmolStatusListener(this); // extends JmolCallbackListener
1350
1351     console = createJmolConsole(consolePanel, buttonsToShow);
1352     if (consolePanel != null)
1353     {
1354       consolePanel.addComponentListener(this);
1355
1356     }
1357
1358   }
1359
1360   protected abstract JmolAppConsoleInterface createJmolConsole(
1361           Container consolePanel, String buttonsToShow);
1362
1363   protected org.jmol.api.JmolAppConsoleInterface console = null;
1364
1365   @Override
1366   public void setBackgroundColour(java.awt.Color col)
1367   {
1368     jmolHistory(false);
1369     viewer.evalStringQuiet("background [" + col.getRed() + ","
1370             + col.getGreen() + "," + col.getBlue() + "];");
1371     jmolHistory(true);
1372   }
1373
1374   @Override
1375   public int[] resizeInnerPanel(String data)
1376   {
1377     // Jalview doesn't honour resize panel requests
1378     return null;
1379   }
1380
1381   /**
1382    * 
1383    */
1384   protected void closeConsole()
1385   {
1386     if (console != null)
1387     {
1388       try
1389       {
1390         console.setVisible(false);
1391       } catch (Error e)
1392       {
1393       } catch (Exception x)
1394       {
1395       }
1396       ;
1397       console = null;
1398     }
1399   }
1400
1401   /**
1402    * ComponentListener method
1403    */
1404   @Override
1405   public void componentMoved(ComponentEvent e)
1406   {
1407   }
1408
1409   /**
1410    * ComponentListener method
1411    */
1412   @Override
1413   public void componentResized(ComponentEvent e)
1414   {
1415   }
1416
1417   /**
1418    * ComponentListener method
1419    */
1420   @Override
1421   public void componentShown(ComponentEvent e)
1422   {
1423     showConsole(true);
1424   }
1425
1426   /**
1427    * ComponentListener method
1428    */
1429   @Override
1430   public void componentHidden(ComponentEvent e)
1431   {
1432     showConsole(false);
1433   }
1434 }