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