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