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