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