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