JAL-3818 process Jmol callbacks in AWT thread
[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 java.awt.Container;
24 import java.awt.event.ComponentEvent;
25 import java.awt.event.ComponentListener;
26 import java.io.File;
27 import java.net.URL;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.StringTokenizer;
32 import java.util.Vector;
33
34 import javax.swing.SwingUtilities;
35
36 import org.jmol.adapter.smarter.SmarterJmolAdapter;
37 import org.jmol.api.JmolAppConsoleInterface;
38 import org.jmol.api.JmolSelectionListener;
39 import org.jmol.api.JmolStatusListener;
40 import org.jmol.api.JmolViewer;
41 import org.jmol.c.CBK;
42 import org.jmol.viewer.Viewer;
43
44 import jalview.api.AlignmentViewPanel;
45 import jalview.api.FeatureRenderer;
46 import jalview.api.FeatureSettingsModelI;
47 import jalview.api.SequenceRenderer;
48 import jalview.bin.Cache;
49 import jalview.datamodel.PDBEntry;
50 import jalview.datamodel.SequenceI;
51 import jalview.gui.AppJmol;
52 import jalview.gui.IProgressIndicator;
53 import jalview.gui.StructureViewer.ViewerType;
54 import jalview.io.DataSourceType;
55 import jalview.io.StructureFile;
56 import jalview.structure.AtomSpec;
57 import jalview.structure.StructureCommand;
58 import jalview.structure.StructureCommandI;
59 import jalview.structure.StructureSelectionManager;
60 import jalview.structures.models.AAStructureBindingModel;
61 import jalview.ws.dbsources.Pdb;
62 import javajs.util.BS;
63
64 public abstract class JalviewJmolBinding extends AAStructureBindingModel
65     implements JmolStatusListener, JmolSelectionListener, ComponentListener {
66   private String lastMessage;
67
68   /*
69    * when true, try to search the associated datamodel for sequences that are
70    * associated with any unknown structures in the Jmol view.
71    */
72   private boolean associateNewStructs = false;
73
74   private Vector<String> atomsPicked = new Vector<>();
75
76   private String lastCommand;
77
78   private boolean loadedInline;
79
80   private StringBuffer resetLastRes = new StringBuffer();
81
82   public Viewer jmolViewer;
83
84   public JalviewJmolBinding(StructureSelectionManager ssm, PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
85       DataSourceType protocol) {
86     super(ssm, pdbentry, sequenceIs, protocol);
87     setStructureCommands(new JmolCommands());
88     /*
89      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
90      * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
91      * "", this);
92      * 
93      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
94      */
95   }
96
97   public JalviewJmolBinding(StructureSelectionManager ssm, SequenceI[][] seqs, Viewer theViewer) {
98     super(ssm, seqs);
99
100     jmolViewer = theViewer;
101     jmolViewer.setJmolStatusListener(this);
102     jmolViewer.addSelectionListener(this);
103     setStructureCommands(new JmolCommands());
104   }
105
106   /**
107    * construct a title string for the viewer window based on the data jalview
108    * knows about
109    * 
110    * @return
111    */
112   public String getViewerTitle() {
113     return getViewerTitle("Jmol", true);
114   }
115
116   private String jmolScript(String script) {
117     Cache.log.debug(">>Jmol>> " + script);
118     String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
119     Cache.log.debug("<<Jmol<< " + s);
120
121     return s;
122   }
123
124   @Override
125   public List<String> executeCommand(StructureCommandI command, boolean getReply) {
126     if (command == null) {
127       return null;
128     }
129     String cmd = command.getCommand();
130     jmolHistory(false);
131     if (lastCommand == null || !lastCommand.equals(cmd)) {
132       jmolScript(cmd + "\n");
133     }
134     jmolHistory(true);
135     lastCommand = cmd;
136     return null;
137   }
138
139   public void createImage(String file, String type, int quality) {
140     System.out.println("JMOL CREATE IMAGE");
141   }
142
143   @Override
144   public String createImage(String fileName, String type, Object textOrBytes, int quality) {
145     System.out.println("JMOL CREATE IMAGE");
146     return null;
147   }
148
149   @Override
150   public String eval(String strEval) {
151     // System.out.println(strEval);
152     // "# 'eval' is implemented only for the applet.";
153     return null;
154   }
155
156   // End StructureListener
157   // //////////////////////////
158
159   @Override
160   public float[][] functionXY(String functionName, int x, int y) {
161     return null;
162   }
163
164   @Override
165   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz) {
166     // TODO Auto-generated method stub
167     return null;
168   }
169
170   /**
171    * map between index of model filename returned from getPdbFile and the first
172    * index of models from this file in the viewer. Note - this is not trimmed -
173    * use getPdbFile to get number of unique models.
174    */
175   private int _modelFileNameMap[];
176
177   @Override
178   public synchronized String[] getStructureFiles() {
179     if (jmolViewer == null) {
180       return new String[0];
181     }
182
183     if (modelFileNames == null) {
184       int modelCount = jmolViewer.ms.mc;
185       String filePath = null;
186       List<String> mset = new ArrayList<>();
187       for (int i = 0; i < modelCount; ++i) {
188         /*
189          * defensive check for null as getModelFileName can return null even when model
190          * count ms.mc is > 0
191          */
192         filePath = jmolViewer.ms.getModelFileName(i);
193         if (filePath != null && !mset.contains(filePath)) {
194           mset.add(filePath);
195         }
196       }
197       if (!mset.isEmpty()) {
198         modelFileNames = mset.toArray(new String[mset.size()]);
199       }
200     }
201
202     return modelFileNames;
203   }
204
205   /**
206    * map from string to applet
207    */
208   @Override
209   public Map<String, Object> getRegistryInfo() {
210     // TODO Auto-generated method stub
211     return null;
212   }
213
214   // ///////////////////////////////
215   // JmolStatusListener
216
217   public void handlePopupMenu(int x, int y) {
218     // jmolpopup.show(x, y);
219     // jmolpopup.jpiShow(x, y);
220   }
221
222   /**
223    * Highlight zero, one or more atoms on the structure
224    */
225   @Override
226   public void highlightAtoms(List<AtomSpec> atoms) {
227     if (atoms != null) {
228       if (resetLastRes.length() > 0) {
229         jmolScript(resetLastRes.toString());
230         resetLastRes.setLength(0);
231       }
232       for (AtomSpec atom : atoms) {
233         highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(), atom.getChain(), atom.getPdbFile());
234       }
235     }
236   }
237
238   // jmol/ssm only
239   public void highlightAtom(int atomIndex, int pdbResNum, String chain, String pdbfile) {
240     String modelId = getModelIdForFile(pdbfile);
241     if (modelId.isEmpty()) {
242       return;
243     }
244
245     jmolHistory(false);
246
247     StringBuilder selection = new StringBuilder(32);
248     StringBuilder cmd = new StringBuilder(64);
249     selection.append("select ").append(String.valueOf(pdbResNum));
250     selection.append(":");
251     if (!chain.equals(" ")) {
252       selection.append(chain);
253     }
254     selection.append(" /").append(modelId);
255
256     cmd.append(selection).append(";wireframe 100;").append(selection).append(" and not hetero;")
257         .append("spacefill 200;select none");
258
259     resetLastRes.append(selection).append(";wireframe 0;").append(selection).append(" and not hetero; spacefill 0;");
260
261     jmolScript(cmd.toString());
262     jmolHistory(true);
263   }
264
265   private boolean debug = true;
266
267   private void jmolHistory(boolean enable) {
268     jmolScript("History " + ((debug || enable) ? "on" : "off"));
269   }
270
271   public void loadInline(String string) {
272     loadedInline = true;
273     // TODO: re JAL-623
274     // viewer.loadInline(strModel, isAppend);
275     // could do this:
276     // construct fake fullPathName and fileName so we can identify the file
277     // later.
278     // Then, construct pass a reader for the string to Jmol.
279     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
280     // fileName, null, reader, false, null, null, 0);
281     jmolViewer.openStringInline(string);
282   }
283
284   protected void mouseOverStructure(int atomIndex, final String strInfo) {
285     int pdbResNum;
286     int alocsep = strInfo.indexOf("^");
287     int mdlSep = strInfo.indexOf("/");
288     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
289
290     if (chainSeparator == -1) {
291       chainSeparator = strInfo.indexOf(".");
292       if (mdlSep > -1 && mdlSep < chainSeparator) {
293         chainSeparator1 = chainSeparator;
294         chainSeparator = mdlSep;
295       }
296     }
297     // handle insertion codes
298     if (alocsep != -1) {
299       pdbResNum = Integer.parseInt(strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
300
301     } else {
302       pdbResNum = Integer.parseInt(strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
303     }
304     String chainId;
305
306     if (strInfo.indexOf(":") > -1) {
307       chainId = strInfo.substring(strInfo.indexOf(":") + 1, strInfo.indexOf("."));
308     } else {
309       chainId = " ";
310     }
311
312     String pdbfilename = modelFileNames[0]; // default is first model
313     if (mdlSep > -1) {
314       if (chainSeparator1 == -1) {
315         chainSeparator1 = strInfo.indexOf(".", mdlSep);
316       }
317       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1, chainSeparator1)
318           : strInfo.substring(mdlSep + 1);
319       try {
320         // recover PDB filename for the model hovered over.
321         int mnumber = Integer.valueOf(mdlId).intValue() - 1;
322         if (_modelFileNameMap != null) {
323           int _mp = _modelFileNameMap.length - 1;
324
325           while (mnumber < _modelFileNameMap[_mp]) {
326             _mp--;
327           }
328           pdbfilename = modelFileNames[_mp];
329         } else {
330           if (mnumber >= 0 && mnumber < modelFileNames.length) {
331             pdbfilename = modelFileNames[mnumber];
332           }
333
334           if (pdbfilename == null) {
335             pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber)).getAbsolutePath();
336           }
337         }
338       } catch (Exception e) {
339       }
340     }
341
342     /*
343      * highlight position on alignment(s); if some text is returned, show this as a
344      * second line on the structure hover tooltip
345      */
346     String label = getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
347     if (label != null) {
348       // change comma to pipe separator (newline token for Jmol)
349       label = label.replace(',', '|');
350       StringTokenizer toks = new StringTokenizer(strInfo, " ");
351       StringBuilder sb = new StringBuilder();
352       sb.append("select ").append(String.valueOf(pdbResNum)).append(":").append(chainId).append("/1");
353       sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ").append(toks.nextToken());
354       sb.append("|").append(label).append("\"");
355       executeCommand(new StructureCommand(sb.toString()), false);
356     }
357   }
358
359   public void notifyAtomHovered(int atomIndex, String strInfo, String data) {
360     if (strInfo.equals(lastMessage)) {
361       return;
362     }
363     lastMessage = strInfo;
364     if (data != null) {
365       System.err.println(
366           "Ignoring additional hover info: " + data + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
367     }
368     mouseOverStructure(atomIndex, strInfo);
369   }
370
371   /*
372    * { if (history != null && strStatus != null &&
373    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
374    * }
375    */
376
377   public void notifyAtomPicked(int atomIndex, String strInfo, String strData) {
378     /**
379      * this implements the toggle label behaviour copied from the original structure
380      * viewer, mc_view
381      */
382     if (strData != null) {
383       System.err.println("Ignoring additional pick data string " + strData);
384     }
385     int chainSeparator = strInfo.indexOf(":");
386     int p = 0;
387     if (chainSeparator == -1) {
388       chainSeparator = strInfo.indexOf(".");
389     }
390
391     String picked = strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator);
392     String mdlString = "";
393     if ((p = strInfo.indexOf(":")) > -1) {
394       picked += strInfo.substring(p, strInfo.indexOf("."));
395     }
396
397     if ((p = strInfo.indexOf("/")) > -1) {
398       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
399     }
400     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P" + mdlString + "))";
401     jmolHistory(false);
402
403     if (!atomsPicked.contains(picked)) {
404       jmolScript("select " + picked + ";label %n %r:%c");
405       atomsPicked.addElement(picked);
406     } else {
407       jmolViewer.evalString("select " + picked + ";label off");
408       atomsPicked.removeElement(picked);
409     }
410     jmolHistory(true);
411     // TODO: in application this happens
412     //
413     // if (scriptWindow != null)
414     // {
415     // scriptWindow.sendConsoleMessage(strInfo);
416     // scriptWindow.sendConsoleMessage("\n");
417     // }
418
419   }
420
421   @Override
422   public void notifyCallback(CBK type, Object[] data) {
423     /*
424      * ensure processed in AWT thread to avoid risk of deadlocks
425      */
426     SwingUtilities.invokeLater(new Runnable() {
427
428       @Override
429       public void run() {
430         processCallback(type, data);
431       }
432     });
433   }
434
435   /**
436    * Processes one callback notification from Jmol
437    * 
438    * @param type
439    * @param data
440    */
441   protected void processCallback(CBK type, Object[] data) {
442     try {
443       switch (type) {
444       case LOADSTRUCT:
445         notifyFileLoaded((String) data[1], (String) data[2], (String) data[3], (String) data[4],
446             ((Integer) data[5]).intValue());
447
448         break;
449       case PICK:
450         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
451         // also highlight in alignment
452         // deliberate fall through
453       case HOVER:
454         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
455         break;
456       case SCRIPT:
457         notifyScriptTermination((String) data[2], ((Integer) data[3]).intValue());
458         break;
459       case ECHO:
460         sendConsoleEcho((String) data[1]);
461         break;
462       case MESSAGE:
463         sendConsoleMessage((data == null) ? ((String) null) : (String) data[1]);
464         break;
465       case ERROR:
466         // System.err.println("Ignoring error callback.");
467         break;
468       case SYNC:
469       case RESIZE:
470         refreshGUI();
471         break;
472       case MEASURE:
473
474       case CLICK:
475       default:
476         System.err.println("Unhandled callback " + type + " " + data[1].toString());
477         break;
478       }
479     } catch (Exception e) {
480       System.err.println("Squashed Jmol callback handler error:");
481       e.printStackTrace();
482     }
483   }
484
485   @Override
486   public boolean notifyEnabled(CBK callbackPick) {
487     switch (callbackPick) {
488     case ECHO:
489     case LOADSTRUCT:
490     case MEASURE:
491     case MESSAGE:
492     case PICK:
493     case SCRIPT:
494     case HOVER:
495     case ERROR:
496       return true;
497     default:
498       return false;
499     }
500   }
501
502   // incremented every time a load notification is successfully handled -
503   // lightweight mechanism for other threads to detect when they can start
504   // referrring to new structures.
505   private long loadNotifiesHandled = 0;
506
507   public long getLoadNotifiesHandled() {
508     return loadNotifiesHandled;
509   }
510
511   public void notifyFileLoaded(String fullPathName, String fileName2, String modelName, String errorMsg,
512       int modelParts) {
513     if (errorMsg != null) {
514       fileLoadingError = errorMsg;
515       refreshGUI();
516       return;
517     }
518     // TODO: deal sensibly with models loaded inLine:
519     // modelName will be null, as will fullPathName.
520
521     // the rest of this routine ignores the arguments, and simply interrogates
522     // the Jmol view to find out what structures it contains, and adds them to
523     // the structure selection manager.
524     fileLoadingError = null;
525     String[] oldmodels = modelFileNames;
526     modelFileNames = null;
527     boolean notifyLoaded = false;
528     String[] modelfilenames = getStructureFiles();
529     if (modelfilenames == null) {
530       // Jmol is still loading files!
531       return;
532     }
533     // first check if we've lost any structures
534     if (oldmodels != null && oldmodels.length > 0) {
535       int oldm = 0;
536       for (int i = 0; i < oldmodels.length; i++) {
537         for (int n = 0; n < modelfilenames.length; n++) {
538           if (modelfilenames[n] == oldmodels[i]) {
539             oldmodels[i] = null;
540             break;
541           }
542         }
543         if (oldmodels[i] != null) {
544           oldm++;
545         }
546       }
547       if (oldm > 0) {
548         String[] oldmfn = new String[oldm];
549         oldm = 0;
550         for (int i = 0; i < oldmodels.length; i++) {
551           if (oldmodels[i] != null) {
552             oldmfn[oldm++] = oldmodels[i];
553           }
554         }
555         // deregister the Jmol instance for these structures - we'll add
556         // ourselves again at the end for the current structure set.
557         getSsm().removeStructureViewerListener(this, oldmfn);
558       }
559     }
560     refreshPdbEntries();
561     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++) {
562       String fileName = modelfilenames[modelnum];
563       boolean foundEntry = false;
564       StructureFile pdb = null;
565       String pdbfile = null;
566       // model was probably loaded inline - so check the pdb file hashcode
567       if (loadedInline) {
568         // calculate essential attributes for the pdb data imported inline.
569         // prolly need to resolve modelnumber properly - for now just use our
570         // 'best guess'
571         pdbfile = jmolViewer.getData("" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
572       }
573       // search pdbentries and sequences to find correct pdbentry for this
574       // model
575       for (int pe = 0; pe < getPdbCount(); pe++) {
576         boolean matches = false;
577         addSequence(pe, getSequence()[pe]);
578         if (fileName == null) {
579           if (false)
580           // see JAL-623 - need method of matching pasted data up
581           {
582             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], pdbfile, DataSourceType.PASTE,
583                 getIProgressIndicator());
584             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
585             matches = true;
586             foundEntry = true;
587           }
588         } else {
589           File fl = new File(getPdbEntry(pe).getFile());
590           matches = fl.equals(new File(fileName));
591           if (matches) {
592             foundEntry = true;
593             // TODO: Jmol can in principle retrieve from CLASSLOADER but
594             // this
595             // needs
596             // to be tested. See mantis bug
597             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
598             DataSourceType protocol = DataSourceType.URL;
599             try {
600               if (fl.exists()) {
601                 protocol = DataSourceType.FILE;
602               }
603             } catch (Exception e) {
604             } catch (Error e) {
605             }
606             // Explicitly map to the filename used by Jmol ;
607             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], fileName, protocol, getIProgressIndicator());
608             // pdbentry[pe].getFile(), protocol);
609
610           }
611         }
612         if (matches) {
613           stashFoundChains(pdb, fileName);
614           notifyLoaded = true;
615         }
616       }
617
618       if (!foundEntry && associateNewStructs) {
619         // this is a foreign pdb file that jalview doesn't know about - add
620         // it to the dataset and try to find a home - either on a matching
621         // sequence or as a new sequence.
622         String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1", "PDB");
623         // parse pdb file into a chain, etc.
624         // locate best match for pdb in associated views and add mapping to
625         // ssm
626         // if properly registered then
627         notifyLoaded = true;
628
629       }
630     }
631     // FILE LOADED OK
632     // so finally, update the jmol bits and pieces
633     // if (jmolpopup != null)
634     // {
635     // // potential for deadlock here:
636     // // jmolpopup.updateComputedMenus();
637     // }
638     if (!isLoadingFromArchive()) {
639       jmolScript("model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
640     }
641     // register ourselves as a listener and notify the gui that it needs to
642     // update itself.
643     getSsm().addStructureViewerListener(this);
644     if (notifyLoaded) {
645       FeatureRenderer fr = getFeatureRenderer(null);
646       if (fr != null) {
647         FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
648         ((AppJmol) getViewer()).getAlignmentPanel().av.applyFeaturesStyle(colours);
649       }
650       refreshGUI();
651       loadNotifiesHandled++;
652     }
653     setLoadingFromArchive(false);
654   }
655
656   protected IProgressIndicator getIProgressIndicator() {
657     return null;
658   }
659
660   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure) {
661     notifyAtomPicked(iatom, strMeasure, null);
662   }
663
664   public abstract void notifyScriptTermination(String strStatus, int msWalltime);
665
666   /**
667    * display a message echoed from the jmol viewer
668    * 
669    * @param strEcho
670    */
671   public abstract void sendConsoleEcho(String strEcho); /*
672                                                          * { showConsole(true);
673                                                          * 
674                                                          * history.append("\n" + strEcho); }
675                                                          */
676
677   // /End JmolStatusListener
678   // /////////////////////////////
679
680   /**
681    * @param strStatus status message - usually the response received after a
682    *                  script executed
683    */
684   public abstract void sendConsoleMessage(String strStatus);
685
686   @Override
687   public void setCallbackFunction(String callbackType, String callbackFunction) {
688     System.err
689         .println("Ignoring set-callback request to associate " + callbackType + " with function " + callbackFunction);
690
691   }
692
693   public void showHelp() {
694     showUrl("http://wiki.jmol.org"
695     // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
696         , "jmolHelp");
697   }
698
699   /**
700    * open the URL somehow
701    * 
702    * @param target
703    */
704   public abstract void showUrl(String url, String target);
705
706   /**
707    * called to show or hide the associated console window container.
708    * 
709    * @param show
710    */
711   public abstract void showConsole(boolean show);
712
713   public static Viewer getJmolData(JmolParser jmolParser) {
714     return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null, "-x -o -n", jmolParser);
715   }
716
717   /**
718    * 
719    * 
720    * 
721    * @param renderPanel
722    * @param jmolfileio     - when true will initialise jmol's file IO system
723    *                       (should be false in applet context)
724    * @param htmlName
725    * @param documentBase
726    * @param codeBase
727    * @param commandOptions
728    */
729   public void allocateViewer(Container renderPanel, boolean jmolfileio, String htmlName, URL documentBase, URL codeBase,
730       String commandOptions) {
731     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase, codeBase, commandOptions, null, null);
732   }
733
734   /**
735    * 
736    * @param renderPanel
737    * @param jmolfileio     - when true will initialise jmol's file IO system
738    *                       (should be false in applet context)
739    * @param htmlName
740    * @param documentBase
741    * @param codeBase
742    * @param commandOptions
743    * @param consolePanel   - panel to contain Jmol console
744    * @param buttonsToShow  - buttons to show on the console, in order
745    */
746   public void allocateViewer(Container renderPanel, boolean jmolfileio, String htmlName, URL documentBase, URL codeBase,
747       String commandOptions, final Container consolePanel, String buttonsToShow) {
748
749     System.err.println("Allocating Jmol Viewer: " + commandOptions);
750
751     if (commandOptions == null) {
752       commandOptions = "";
753     }
754     jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel, (jmolfileio ? new SmarterJmolAdapter() : null),
755         htmlName + ((Object) this).toString(), documentBase, codeBase, commandOptions, this);
756
757     jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
758
759     try {
760       console = createJmolConsole(consolePanel, buttonsToShow);
761     } catch (Throwable e) {
762       System.err.println("Could not create Jmol application console. " + e.getMessage());
763       e.printStackTrace();
764     }
765     if (consolePanel != null) {
766       consolePanel.addComponentListener(this);
767
768     }
769
770   }
771
772   protected abstract JmolAppConsoleInterface createJmolConsole(Container consolePanel, String buttonsToShow);
773
774   // BH 2018 -- Jmol console is not working due to problems with styled
775   // documents.
776
777   protected org.jmol.api.JmolAppConsoleInterface console = null;
778
779   @Override
780   public int[] resizeInnerPanel(String data) {
781     // Jalview doesn't honour resize panel requests
782     return null;
783   }
784
785   /**
786    * 
787    */
788   protected void closeConsole() {
789     if (console != null) {
790       try {
791         console.setVisible(false);
792       } catch (Error e) {
793       } catch (Exception x) {
794       }
795       ;
796       console = null;
797     }
798   }
799
800   /**
801    * ComponentListener method
802    */
803   @Override
804   public void componentMoved(ComponentEvent e) {
805   }
806
807   /**
808    * ComponentListener method
809    */
810   @Override
811   public void componentResized(ComponentEvent e) {
812   }
813
814   /**
815    * ComponentListener method
816    */
817   @Override
818   public void componentShown(ComponentEvent e) {
819     showConsole(true);
820   }
821
822   /**
823    * ComponentListener method
824    */
825   @Override
826   public void componentHidden(ComponentEvent e) {
827     showConsole(false);
828   }
829
830   @Override
831   protected String getModelIdForFile(String pdbFile) {
832     if (modelFileNames == null) {
833       return "";
834     }
835     for (int i = 0; i < modelFileNames.length; i++) {
836       if (modelFileNames[i].equalsIgnoreCase(pdbFile)) {
837         return String.valueOf(i + 1);
838       }
839     }
840     return "";
841   }
842
843   @Override
844   protected ViewerType getViewerType() {
845     return ViewerType.JMOL;
846   }
847
848   @Override
849   protected String getModelId(int pdbfnum, String file) {
850     return String.valueOf(pdbfnum + 1);
851   }
852
853   /**
854    * Returns ".spt" - the Jmol session file extension
855    * 
856    * @return
857    * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
858    */
859   @Override
860   public String getSessionFileExtension() {
861     return ".spt";
862   }
863
864   @Override
865   public void selectionChanged(BS arg0) {
866     // TODO Auto-generated method stub
867
868   }
869
870   @Override
871   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp) {
872     return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
873   }
874
875   @Override
876   public String getHelpURL() {
877     return "http://wiki.jmol.org"; // BH 2018
878   }
879 }