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