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