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