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