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