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