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