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