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