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