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