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