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