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