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