JAL-3551 copy Jalview features to Pymol 'p' (with pull refactoring)
[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     // first check if we've lost any structures
602     if (oldmodels != null && oldmodels.length > 0)
603     {
604       int oldm = 0;
605       for (int i = 0; i < oldmodels.length; i++)
606       {
607         for (int n = 0; n < modelfilenames.length; n++)
608         {
609           if (modelfilenames[n] == oldmodels[i])
610           {
611             oldmodels[i] = null;
612             break;
613           }
614         }
615         if (oldmodels[i] != null)
616         {
617           oldm++;
618         }
619       }
620       if (oldm > 0)
621       {
622         String[] oldmfn = new String[oldm];
623         oldm = 0;
624         for (int i = 0; i < oldmodels.length; i++)
625         {
626           if (oldmodels[i] != null)
627           {
628             oldmfn[oldm++] = oldmodels[i];
629           }
630         }
631         // deregister the Jmol instance for these structures - we'll add
632         // ourselves again at the end for the current structure set.
633         getSsm().removeStructureViewerListener(this, oldmfn);
634       }
635     }
636     refreshPdbEntries();
637     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
638     {
639       String fileName = modelfilenames[modelnum];
640       boolean foundEntry = false;
641       StructureFile pdb = null;
642       String pdbfile = null;
643       // model was probably loaded inline - so check the pdb file hashcode
644       if (loadedInline)
645       {
646         // calculate essential attributes for the pdb data imported inline.
647         // prolly need to resolve modelnumber properly - for now just use our
648         // 'best guess'
649         pdbfile = jmolViewer.getData(
650                 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
651       }
652       // search pdbentries and sequences to find correct pdbentry for this
653       // model
654       for (int pe = 0; pe < getPdbCount(); pe++)
655       {
656         boolean matches = false;
657         addSequence(pe, getSequence()[pe]);
658         if (fileName == null)
659         {
660           if (false)
661           // see JAL-623 - need method of matching pasted data up
662           {
663             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
664                     pdbfile, DataSourceType.PASTE, getIProgressIndicator());
665             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
666             matches = true;
667             foundEntry = true;
668           }
669         }
670         else
671         {
672           File fl = new File(getPdbEntry(pe).getFile());
673           matches = fl.equals(new File(fileName));
674           if (matches)
675           {
676             foundEntry = true;
677             // TODO: Jmol can in principle retrieve from CLASSLOADER but
678             // this
679             // needs
680             // to be tested. See mantis bug
681             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
682             DataSourceType protocol = DataSourceType.URL;
683             try
684             {
685               if (fl.exists())
686               {
687                 protocol = DataSourceType.FILE;
688               }
689             } catch (Exception e)
690             {
691             } catch (Error e)
692             {
693             }
694             // Explicitly map to the filename used by Jmol ;
695             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
696                     fileName, protocol, getIProgressIndicator());
697             // pdbentry[pe].getFile(), protocol);
698
699           }
700         }
701         if (matches)
702         {
703           stashFoundChains(pdb, fileName);
704           notifyLoaded = true;
705         }
706       }
707
708       if (!foundEntry && associateNewStructs)
709       {
710         // this is a foreign pdb file that jalview doesn't know about - add
711         // it to the dataset and try to find a home - either on a matching
712         // sequence or as a new sequence.
713         String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
714                 "PDB");
715         // parse pdb file into a chain, etc.
716         // locate best match for pdb in associated views and add mapping to
717         // ssm
718         // if properly registered then
719         notifyLoaded = true;
720
721       }
722     }
723     // FILE LOADED OK
724     // so finally, update the jmol bits and pieces
725     // if (jmolpopup != null)
726     // {
727     // // potential for deadlock here:
728     // // jmolpopup.updateComputedMenus();
729     // }
730     if (!isLoadingFromArchive())
731     {
732       jmolScript(
733               "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
734     }
735     // register ourselves as a listener and notify the gui that it needs to
736     // update itself.
737     getSsm().addStructureViewerListener(this);
738     if (notifyLoaded)
739     {
740       FeatureRenderer fr = getFeatureRenderer(null);
741       if (fr != null)
742       {
743         fr.featuresAdded();
744       }
745       refreshGUI();
746       loadNotifiesHandled++;
747     }
748     setLoadingFromArchive(false);
749   }
750
751   protected IProgressIndicator getIProgressIndicator()
752   {
753     return null;
754   }
755
756   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
757   {
758     notifyAtomPicked(iatom, strMeasure, null);
759   }
760
761   public abstract void notifyScriptTermination(String strStatus,
762           int msWalltime);
763
764   /**
765    * display a message echoed from the jmol viewer
766    * 
767    * @param strEcho
768    */
769   public abstract void sendConsoleEcho(String strEcho); /*
770                                                          * { showConsole(true);
771                                                          * 
772                                                          * history.append("\n" +
773                                                          * strEcho); }
774                                                          */
775
776   // /End JmolStatusListener
777   // /////////////////////////////
778
779   /**
780    * @param strStatus
781    *          status message - usually the response received after a script
782    *          executed
783    */
784   public abstract void sendConsoleMessage(String strStatus);
785
786   @Override
787   public void setCallbackFunction(String callbackType,
788           String callbackFunction)
789   {
790     System.err.println("Ignoring set-callback request to associate "
791             + callbackType + " with function " + callbackFunction);
792
793   }
794
795   public void showHelp()
796   {
797     showUrl("http://wiki.jmol.org"
798     // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
799             , "jmolHelp");
800   }
801
802   /**
803    * open the URL somehow
804    * 
805    * @param target
806    */
807   public abstract void showUrl(String url, String target);
808
809   /**
810    * called to show or hide the associated console window container.
811    * 
812    * @param show
813    */
814   public abstract void showConsole(boolean show);
815
816   public static Viewer getJmolData(JmolParser jmolParser)
817   {
818     return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
819             "-x -o -n", jmolParser);
820   }
821
822   /**
823    * 
824    * 
825    * 
826    * @param renderPanel
827    * @param jmolfileio
828    *          - when true will initialise jmol's file IO system (should be false
829    *          in applet context)
830    * @param htmlName
831    * @param documentBase
832    * @param codeBase
833    * @param commandOptions
834    */
835   public void allocateViewer(Container renderPanel, boolean jmolfileio,
836           String htmlName, URL documentBase, URL codeBase,
837           String commandOptions)
838   {
839     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
840             codeBase, commandOptions, null, null);
841   }
842
843   /**
844    * 
845    * @param renderPanel
846    * @param jmolfileio
847    *          - when true will initialise jmol's file IO system (should be false
848    *          in applet context)
849    * @param htmlName
850    * @param documentBase
851    * @param codeBase
852    * @param commandOptions
853    * @param consolePanel
854    *          - panel to contain Jmol console
855    * @param buttonsToShow
856    *          - buttons to show on the console, in order
857    */
858   public void allocateViewer(Container renderPanel, boolean jmolfileio,
859           String htmlName, URL documentBase, URL codeBase,
860           String commandOptions, final Container consolePanel,
861           String buttonsToShow)
862   {
863
864     System.err.println("Allocating Jmol Viewer: " + commandOptions);
865
866     if (commandOptions == null)
867     {
868       commandOptions = "";
869     }
870     jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
871             (jmolfileio ? new SmarterJmolAdapter() : null),
872             htmlName + ((Object) this).toString(), documentBase, codeBase,
873             commandOptions, this);
874
875     jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
876
877     try
878     {
879       console = createJmolConsole(consolePanel, buttonsToShow);
880     } catch (Throwable e)
881     {
882       System.err.println("Could not create Jmol application console. "
883               + e.getMessage());
884       e.printStackTrace();
885     }
886     if (consolePanel != null)
887     {
888       consolePanel.addComponentListener(this);
889
890     }
891
892   }
893
894   protected abstract JmolAppConsoleInterface createJmolConsole(
895           Container consolePanel, String buttonsToShow);
896
897   // BH 2018 -- Jmol console is not working due to problems with styled
898   // documents.
899
900   protected org.jmol.api.JmolAppConsoleInterface console = null;
901
902   @Override
903   public int[] resizeInnerPanel(String data)
904   {
905     // Jalview doesn't honour resize panel requests
906     return null;
907   }
908
909   /**
910    * 
911    */
912   protected void closeConsole()
913   {
914     if (console != null)
915     {
916       try
917       {
918         console.setVisible(false);
919       } catch (Error e)
920       {
921       } catch (Exception x)
922       {
923       }
924       ;
925       console = null;
926     }
927   }
928
929   /**
930    * ComponentListener method
931    */
932   @Override
933   public void componentMoved(ComponentEvent e)
934   {
935   }
936
937   /**
938    * ComponentListener method
939    */
940   @Override
941   public void componentResized(ComponentEvent e)
942   {
943   }
944
945   /**
946    * ComponentListener method
947    */
948   @Override
949   public void componentShown(ComponentEvent e)
950   {
951     showConsole(true);
952   }
953
954   /**
955    * ComponentListener method
956    */
957   @Override
958   public void componentHidden(ComponentEvent e)
959   {
960     showConsole(false);
961   }
962
963   @Override
964   protected String getModelIdForFile(String pdbFile)
965   {
966     if (modelFileNames == null)
967     {
968       return "";
969     }
970     for (int i = 0; i < modelFileNames.length; i++)
971     {
972       if (modelFileNames[i].equalsIgnoreCase(pdbFile))
973       {
974         return String.valueOf(i + 1);
975       }
976     }
977     return "";
978   }
979
980   @Override
981   protected ViewerType getViewerType()
982   {
983     return ViewerType.JMOL;
984   }
985
986   @Override
987   protected String getModelId(int pdbfnum, String file)
988   {
989     return String.valueOf(pdbfnum + 1);
990   }
991
992   /**
993    * Returns ".spt" - the Jmol session file extension
994    * 
995    * @return
996    * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
997    */
998   @Override
999   public String getSessionFileExtension()
1000   {
1001     return ".spt";
1002   }
1003
1004   @Override
1005   public void selectionChanged(BS arg0)
1006   {
1007     // TODO Auto-generated method stub
1008
1009   }
1010
1011   @Override
1012   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1013   {
1014     return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1015   }
1016
1017   @Override
1018   public String getHelpURL()
1019   {
1020     return "http://wiki.jmol.org"; // BH 2018
1021   }
1022 }