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