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