2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.ext.jmol;
23 import java.awt.Container;
24 import java.awt.event.ComponentEvent;
25 import java.awt.event.ComponentListener;
28 import java.util.ArrayList;
29 import java.util.List;
31 import java.util.StringTokenizer;
32 import java.util.Vector;
34 import javax.swing.SwingUtilities;
36 import org.jmol.adapter.smarter.SmarterJmolAdapter;
37 import org.jmol.api.JmolAppConsoleInterface;
38 import org.jmol.api.JmolSelectionListener;
39 import org.jmol.api.JmolStatusListener;
40 import org.jmol.api.JmolViewer;
41 import org.jmol.c.CBK;
42 import org.jmol.viewer.Viewer;
44 import jalview.api.AlignmentViewPanel;
45 import jalview.api.FeatureRenderer;
46 import jalview.api.FeatureSettingsModelI;
47 import jalview.api.SequenceRenderer;
48 import jalview.bin.Console;
49 import jalview.datamodel.PDBEntry;
50 import jalview.datamodel.SequenceI;
51 import jalview.gui.AppJmol;
52 import jalview.gui.IProgressIndicator;
53 import jalview.gui.StructureViewer.ViewerType;
54 import jalview.io.DataSourceType;
55 import jalview.io.StructureFile;
56 import jalview.structure.AtomSpec;
57 import jalview.structure.StructureCommand;
58 import jalview.structure.StructureCommandI;
59 import jalview.structure.StructureSelectionManager;
60 import jalview.structures.models.AAStructureBindingModel;
61 import jalview.ws.dbsources.Pdb;
62 import javajs.util.BS;
64 public abstract class JalviewJmolBinding extends AAStructureBindingModel
65 implements JmolStatusListener, JmolSelectionListener,
68 private String lastMessage;
71 * when true, try to search the associated datamodel for sequences that are
72 * associated with any unknown structures in the Jmol view.
74 private boolean associateNewStructs = false;
76 private Vector<String> atomsPicked = new Vector<>();
78 private String lastCommand;
80 private boolean loadedInline;
82 private StringBuffer resetLastRes = new StringBuffer();
84 public Viewer jmolViewer;
86 private boolean globalUseScriptWait = false;
88 public JalviewJmolBinding(StructureSelectionManager ssm,
89 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
90 DataSourceType protocol)
92 super(ssm, pdbentry, sequenceIs, protocol);
93 setStructureCommands(new JmolCommands());
95 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
96 * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
99 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
103 public JalviewJmolBinding(StructureSelectionManager ssm,
104 SequenceI[][] seqs, Viewer theViewer)
108 jmolViewer = theViewer;
109 jmolViewer.setJmolStatusListener(this);
110 jmolViewer.addSelectionListener(this);
111 setStructureCommands(new JmolCommands());
115 * construct a title string for the viewer window based on the data jalview
120 public String getViewerTitle()
122 return getViewerTitle("Jmol", true);
125 private String jmolScript(String script)
127 return jmolScript(script, false);
130 private String jmolScript(String script, boolean useScriptWait)
132 Console.debug(">>Jmol>> " + script);
134 if (globalUseScriptWait || useScriptWait)
136 s = jmolViewer.scriptWait(script);
140 s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
142 Console.debug("<<Jmol<< " + s);
148 public List<String> executeCommand(StructureCommandI command,
155 String cmd = command.getCommand();
157 if (lastCommand == null || !lastCommand.equals(cmd))
159 jmolScript(cmd + "\n");
166 public void createImage(String file, String type, int quality)
168 System.out.println("JMOL CREATE IMAGE");
172 public String createImage(String fileName, String type,
173 Object textOrBytes, int quality)
175 System.out.println("JMOL CREATE IMAGE");
180 public String eval(String strEval)
182 // System.out.println(strEval);
183 // "# 'eval' is implemented only for the applet.";
187 // End StructureListener
188 // //////////////////////////
191 public float[][] functionXY(String functionName, int x, int y)
197 public float[][][] functionXYZ(String functionName, int nx, int ny,
200 // TODO Auto-generated method stub
205 * map between index of model filename returned from getPdbFile and the first
206 * index of models from this file in the viewer. Note - this is not trimmed -
207 * use getPdbFile to get number of unique models.
209 private int _modelFileNameMap[];
212 public synchronized String[] getStructureFiles()
214 if (jmolViewer == null)
216 return new String[0];
219 if (modelFileNames == null)
221 int modelCount = jmolViewer.ms.mc;
222 String filePath = null;
223 List<String> mset = new ArrayList<>();
224 for (int i = 0; i < modelCount; ++i)
227 * defensive check for null as getModelFileName can return null even when model
230 filePath = jmolViewer.ms.getModelFileName(i);
231 if (filePath != null && !mset.contains(filePath))
238 modelFileNames = mset.toArray(new String[mset.size()]);
242 return modelFileNames;
246 * map from string to applet
249 public Map<String, Object> getRegistryInfo()
251 // TODO Auto-generated method stub
255 // ///////////////////////////////
256 // JmolStatusListener
258 public void handlePopupMenu(int x, int y)
260 // jmolpopup.show(x, y);
261 // jmolpopup.jpiShow(x, y);
265 * Highlight zero, one or more atoms on the structure
268 public void highlightAtoms(List<AtomSpec> atoms)
272 // we set the globalUseScriptWait (and reset to previous value) only if
273 // we're going to use it here
274 boolean useScriptWait = atoms.size() > 1;
275 boolean prevUseScriptWait = false;
277 prevUseScriptWait = setGlobalUseScriptWait(useScriptWait);
278 if (resetLastRes.length() > 0)
280 jmolScript(resetLastRes.toString(), useScriptWait);
281 resetLastRes.setLength(0);
283 for (AtomSpec atom : atoms)
285 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
286 atom.getChain(), atom.getPdbFile(), useScriptWait);
289 setGlobalUseScriptWait(prevUseScriptWait);
294 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
295 String pdbfile, boolean useScriptWait)
297 String modelId = getModelIdForFile(pdbfile);
298 if (modelId.isEmpty())
305 StringBuilder selection = new StringBuilder(32);
306 StringBuilder cmd = new StringBuilder(64);
307 selection.append("select ").append(String.valueOf(pdbResNum));
308 selection.append(":");
309 if (!chain.equals(" "))
311 selection.append(chain);
313 selection.append(" /").append(modelId);
315 cmd.append(selection).append(";wireframe 100;").append(selection)
316 .append(" and not hetero;").append("spacefill 200;select none");
318 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
319 .append(" and not hetero; spacefill 0;");
321 jmolScript(cmd.toString(), useScriptWait);
325 private boolean debug = true;
327 private void jmolHistory(boolean enable)
329 jmolScript("History " + ((debug || enable) ? "on" : "off"));
332 public void loadInline(String string)
336 // viewer.loadInline(strModel, isAppend);
338 // construct fake fullPathName and fileName so we can identify the file
340 // Then, construct pass a reader for the string to Jmol.
341 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
342 // fileName, null, reader, false, null, null, 0);
343 jmolViewer.openStringInline(string);
346 protected void mouseOverStructure(int atomIndex, final String strInfo)
349 int alocsep = strInfo.indexOf("^");
350 int mdlSep = strInfo.indexOf("/");
351 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
353 if (chainSeparator == -1)
355 chainSeparator = strInfo.indexOf(".");
356 if (mdlSep > -1 && mdlSep < chainSeparator)
358 chainSeparator1 = chainSeparator;
359 chainSeparator = mdlSep;
362 // handle insertion codes
365 pdbResNum = Integer.parseInt(
366 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
371 pdbResNum = Integer.parseInt(
372 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
376 if (strInfo.indexOf(":") > -1)
378 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
379 strInfo.indexOf("."));
386 String pdbfilename = modelFileNames[0]; // default is first model
389 if (chainSeparator1 == -1)
391 chainSeparator1 = strInfo.indexOf(".", mdlSep);
393 String mdlId = (chainSeparator1 > -1)
394 ? strInfo.substring(mdlSep + 1, chainSeparator1)
395 : strInfo.substring(mdlSep + 1);
398 // recover PDB filename for the model hovered over.
399 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
400 if (_modelFileNameMap != null)
402 int _mp = _modelFileNameMap.length - 1;
404 while (mnumber < _modelFileNameMap[_mp])
408 pdbfilename = modelFileNames[_mp];
412 if (mnumber >= 0 && mnumber < modelFileNames.length)
414 pdbfilename = modelFileNames[mnumber];
417 if (pdbfilename == null)
419 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
423 } catch (Exception e)
429 * highlight position on alignment(s); if some text is returned, show this as a
430 * second line on the structure hover tooltip
432 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
436 // change comma to pipe separator (newline token for Jmol)
437 label = label.replace(',', '|');
438 StringTokenizer toks = new StringTokenizer(strInfo, " ");
439 StringBuilder sb = new StringBuilder();
440 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
441 .append(chainId).append("/1");
442 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
443 .append(toks.nextToken());
444 sb.append("|").append(label).append("\"");
445 executeCommand(new StructureCommand(sb.toString()), false);
449 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
451 if (strInfo.equals(lastMessage))
455 lastMessage = strInfo;
458 System.err.println("Ignoring additional hover info: " + data
459 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
461 mouseOverStructure(atomIndex, strInfo);
465 * { if (history != null && strStatus != null &&
466 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
470 public void notifyAtomPicked(int atomIndex, String strInfo,
474 * this implements the toggle label behaviour copied from the original
475 * structure viewer, mc_view
479 System.err.println("Ignoring additional pick data string " + strData);
481 int chainSeparator = strInfo.indexOf(":");
483 if (chainSeparator == -1)
485 chainSeparator = strInfo.indexOf(".");
488 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
490 String mdlString = "";
491 if ((p = strInfo.indexOf(":")) > -1)
493 picked += strInfo.substring(p, strInfo.indexOf("."));
496 if ((p = strInfo.indexOf("/")) > -1)
498 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
500 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
504 if (!atomsPicked.contains(picked))
506 jmolScript("select " + picked + ";label %n %r:%c");
507 atomsPicked.addElement(picked);
511 jmolViewer.evalString("select " + picked + ";label off");
512 atomsPicked.removeElement(picked);
515 // TODO: in application this happens
517 // if (scriptWindow != null)
519 // scriptWindow.sendConsoleMessage(strInfo);
520 // scriptWindow.sendConsoleMessage("\n");
526 public void notifyCallback(CBK type, Object[] data)
529 * ensure processed in AWT thread to avoid risk of deadlocks
531 SwingUtilities.invokeLater(new Runnable()
537 processCallback(type, data);
543 * Processes one callback notification from Jmol
548 protected void processCallback(CBK type, Object[] data)
555 notifyFileLoaded((String) data[1], (String) data[2],
556 (String) data[3], (String) data[4],
557 ((Integer) data[5]).intValue());
561 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
563 // also highlight in alignment
564 // deliberate fall through
566 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
570 notifyScriptTermination((String) data[2],
571 ((Integer) data[3]).intValue());
574 sendConsoleEcho((String) data[1]);
578 (data == null) ? ((String) null) : (String) data[1]);
581 // System.err.println("Ignoring error callback.");
592 "Unhandled callback " + type + " " + data[1].toString());
595 } catch (Exception e)
597 System.err.println("Squashed Jmol callback handler error:");
603 public boolean notifyEnabled(CBK callbackPick)
605 switch (callbackPick)
621 // incremented every time a load notification is successfully handled -
622 // lightweight mechanism for other threads to detect when they can start
623 // referrring to new structures.
624 private long loadNotifiesHandled = 0;
626 public long getLoadNotifiesHandled()
628 return loadNotifiesHandled;
631 public void notifyFileLoaded(String fullPathName, String fileName2,
632 String modelName, String errorMsg, int modelParts)
634 if (errorMsg != null)
636 fileLoadingError = errorMsg;
640 // TODO: deal sensibly with models loaded inLine:
641 // modelName will be null, as will fullPathName.
643 // the rest of this routine ignores the arguments, and simply interrogates
644 // the Jmol view to find out what structures it contains, and adds them to
645 // the structure selection manager.
646 fileLoadingError = null;
647 String[] oldmodels = modelFileNames;
648 modelFileNames = null;
649 boolean notifyLoaded = false;
650 String[] modelfilenames = getStructureFiles();
651 if (modelfilenames == null)
653 // Jmol is still loading files!
656 // first check if we've lost any structures
657 if (oldmodels != null && oldmodels.length > 0)
660 for (int i = 0; i < oldmodels.length; i++)
662 for (int n = 0; n < modelfilenames.length; n++)
664 if (modelfilenames[n] == oldmodels[i])
670 if (oldmodels[i] != null)
677 String[] oldmfn = new String[oldm];
679 for (int i = 0; i < oldmodels.length; i++)
681 if (oldmodels[i] != null)
683 oldmfn[oldm++] = oldmodels[i];
686 // deregister the Jmol instance for these structures - we'll add
687 // ourselves again at the end for the current structure set.
688 getSsm().removeStructureViewerListener(this, oldmfn);
692 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
694 String fileName = modelfilenames[modelnum];
695 boolean foundEntry = false;
696 StructureFile pdb = null;
697 String pdbfile = null;
698 // model was probably loaded inline - so check the pdb file hashcode
701 // calculate essential attributes for the pdb data imported inline.
702 // prolly need to resolve modelnumber properly - for now just use our
704 pdbfile = jmolViewer.getData(
705 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
707 // search pdbentries and sequences to find correct pdbentry for this
709 for (int pe = 0; pe < getPdbCount(); pe++)
711 boolean matches = false;
712 addSequence(pe, getSequence()[pe]);
713 if (fileName == null)
716 // see JAL-623 - need method of matching pasted data up
718 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
719 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
720 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
727 File fl = new File(getPdbEntry(pe).getFile());
728 matches = fl.equals(new File(fileName));
732 // TODO: Jmol can in principle retrieve from CLASSLOADER but
735 // to be tested. See mantis bug
736 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
737 DataSourceType protocol = DataSourceType.URL;
742 protocol = DataSourceType.FILE;
744 } catch (Exception e)
749 // Explicitly map to the filename used by Jmol ;
750 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
751 fileName, protocol, getIProgressIndicator());
752 // pdbentry[pe].getFile(), protocol);
758 stashFoundChains(pdb, fileName);
763 if (!foundEntry && associateNewStructs)
765 // this is a foreign pdb file that jalview doesn't know about - add
766 // it to the dataset and try to find a home - either on a matching
767 // sequence or as a new sequence.
768 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
770 // parse pdb file into a chain, etc.
771 // locate best match for pdb in associated views and add mapping to
773 // if properly registered then
779 // so finally, update the jmol bits and pieces
780 // if (jmolpopup != null)
782 // // potential for deadlock here:
783 // // jmolpopup.updateComputedMenus();
785 if (!isLoadingFromArchive())
788 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
790 // register ourselves as a listener and notify the gui that it needs to
792 getSsm().addStructureViewerListener(this);
795 FeatureRenderer fr = getFeatureRenderer(null);
798 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
799 ((AppJmol) getViewer()).getAlignmentPanel().av
800 .applyFeaturesStyle(colours);
803 loadNotifiesHandled++;
805 setLoadingFromArchive(false);
808 protected IProgressIndicator getIProgressIndicator()
813 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
815 notifyAtomPicked(iatom, strMeasure, null);
818 public abstract void notifyScriptTermination(String strStatus,
822 * display a message echoed from the jmol viewer
826 public abstract void sendConsoleEcho(String strEcho); /*
827 * { showConsole(true);
829 * history.append("\n" + strEcho); }
832 // /End JmolStatusListener
833 // /////////////////////////////
837 * status message - usually the response received after a script
840 public abstract void sendConsoleMessage(String strStatus);
843 public void setCallbackFunction(String callbackType,
844 String callbackFunction)
846 System.err.println("Ignoring set-callback request to associate "
847 + callbackType + " with function " + callbackFunction);
851 public void showHelp()
853 showUrl("http://wiki.jmol.org"
854 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
859 * open the URL somehow
863 public abstract void showUrl(String url, String target);
866 * called to show or hide the associated console window container.
870 public abstract void showConsole(boolean show);
872 public static Viewer getJmolData(JmolParser jmolParser)
874 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
875 "-x -o -n", jmolParser);
884 * - when true will initialise jmol's file IO system (should be false
887 * @param documentBase
889 * @param commandOptions
891 public void allocateViewer(Container renderPanel, boolean jmolfileio,
892 String htmlName, URL documentBase, URL codeBase,
893 String commandOptions)
895 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
896 codeBase, commandOptions, null, null);
903 * - when true will initialise jmol's file IO system (should be false
906 * @param documentBase
908 * @param commandOptions
909 * @param consolePanel
910 * - panel to contain Jmol console
911 * @param buttonsToShow
912 * - buttons to show on the console, in order
914 public void allocateViewer(Container renderPanel, boolean jmolfileio,
915 String htmlName, URL documentBase, URL codeBase,
916 String commandOptions, final Container consolePanel,
917 String buttonsToShow)
920 System.err.println("Allocating Jmol Viewer: " + commandOptions);
922 if (commandOptions == null)
926 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
927 (jmolfileio ? new SmarterJmolAdapter() : null),
928 htmlName + ((Object) this).toString(), documentBase, codeBase,
929 commandOptions, this);
931 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
935 console = createJmolConsole(consolePanel, buttonsToShow);
936 } catch (Throwable e)
938 System.err.println("Could not create Jmol application console. "
942 if (consolePanel != null)
944 consolePanel.addComponentListener(this);
950 protected abstract JmolAppConsoleInterface createJmolConsole(
951 Container consolePanel, String buttonsToShow);
953 // BH 2018 -- Jmol console is not working due to problems with styled
956 protected org.jmol.api.JmolAppConsoleInterface console = null;
959 public int[] resizeInnerPanel(String data)
961 // Jalview doesn't honour resize panel requests
968 protected void closeConsole()
974 console.setVisible(false);
977 } catch (Exception x)
986 * ComponentListener method
989 public void componentMoved(ComponentEvent e)
994 * ComponentListener method
997 public void componentResized(ComponentEvent e)
1002 * ComponentListener method
1005 public void componentShown(ComponentEvent e)
1011 * ComponentListener method
1014 public void componentHidden(ComponentEvent e)
1020 protected String getModelIdForFile(String pdbFile)
1022 if (modelFileNames == null)
1026 for (int i = 0; i < modelFileNames.length; i++)
1028 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1030 return String.valueOf(i + 1);
1037 protected ViewerType getViewerType()
1039 return ViewerType.JMOL;
1043 protected String getModelId(int pdbfnum, String file)
1045 return String.valueOf(pdbfnum + 1);
1049 * Returns ".spt" - the Jmol session file extension
1052 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1055 public String getSessionFileExtension()
1061 public void selectionChanged(BS arg0)
1063 // TODO Auto-generated method stub
1068 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1070 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1074 public String getHelpURL()
1076 return "http://wiki.jmol.org"; // BH 2018
1079 private boolean setGlobalUseScriptWait(boolean b)
1081 boolean prev = globalUseScriptWait;
1082 globalUseScriptWait = b;