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 public JalviewJmolBinding(StructureSelectionManager ssm,
87 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
88 DataSourceType protocol)
90 super(ssm, pdbentry, sequenceIs, protocol);
91 setStructureCommands(new JmolCommands());
93 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
94 * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
97 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
101 public JalviewJmolBinding(StructureSelectionManager ssm,
102 SequenceI[][] seqs, Viewer theViewer)
106 jmolViewer = theViewer;
107 jmolViewer.setJmolStatusListener(this);
108 jmolViewer.addSelectionListener(this);
109 setStructureCommands(new JmolCommands());
113 * construct a title string for the viewer window based on the data jalview
118 public String getViewerTitle()
120 return getViewerTitle("Jmol", true);
123 private String jmolScript(String script)
125 return jmolScript(script, false);
128 private String jmolScript(String script, boolean useScriptWait)
130 Console.debug(">>Jmol>> " + script);
134 s = jmolViewer.scriptWait(script);
138 s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
140 Console.debug("<<Jmol<< " + s);
146 public List<String> executeCommand(StructureCommandI command,
153 String cmd = command.getCommand();
155 if (lastCommand == null || !lastCommand.equals(cmd))
157 jmolScript(cmd + "\n");
164 public void createImage(String file, String type, int quality)
166 System.out.println("JMOL CREATE IMAGE");
170 public String createImage(String fileName, String type,
171 Object textOrBytes, int quality)
173 System.out.println("JMOL CREATE IMAGE");
178 public String eval(String strEval)
180 // System.out.println(strEval);
181 // "# 'eval' is implemented only for the applet.";
185 // End StructureListener
186 // //////////////////////////
189 public float[][] functionXY(String functionName, int x, int y)
195 public float[][][] functionXYZ(String functionName, int nx, int ny,
198 // TODO Auto-generated method stub
203 * map between index of model filename returned from getPdbFile and the first
204 * index of models from this file in the viewer. Note - this is not trimmed -
205 * use getPdbFile to get number of unique models.
207 private int _modelFileNameMap[];
210 public synchronized String[] getStructureFiles()
212 if (jmolViewer == null)
214 return new String[0];
217 if (modelFileNames == null)
219 int modelCount = jmolViewer.ms.mc;
220 String filePath = null;
221 List<String> mset = new ArrayList<>();
222 for (int i = 0; i < modelCount; ++i)
225 * defensive check for null as getModelFileName can return null even when model
228 filePath = jmolViewer.ms.getModelFileName(i);
229 if (filePath != null && !mset.contains(filePath))
236 modelFileNames = mset.toArray(new String[mset.size()]);
240 return modelFileNames;
244 * map from string to applet
247 public Map<String, Object> getRegistryInfo()
249 // TODO Auto-generated method stub
253 // ///////////////////////////////
254 // JmolStatusListener
256 public void handlePopupMenu(int x, int y)
258 // jmolpopup.show(x, y);
259 // jmolpopup.jpiShow(x, y);
263 * Highlight zero, one or more atoms on the structure
266 public void highlightAtoms(List<AtomSpec> atoms)
270 boolean useScriptWait = atoms.size() > 1;
271 if (resetLastRes.length() > 0)
273 jmolScript(resetLastRes.toString(), useScriptWait);
274 resetLastRes.setLength(0);
276 for (AtomSpec atom : atoms)
278 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
279 atom.getChain(), atom.getPdbFile(), useScriptWait);
281 // Highlight distances between atoms with a 'measure' command - not yet
283 // if (atoms.size() >= 2)
285 // StringBuilder sb = new StringBuilder();
286 // for (int a = 0; a < atoms.size(); a++)
288 // AtomSpec speca = atoms.get(a);
289 // String a_model = getModelIdForFile(speca.getPdbFile());
290 // for (int b = a + 1; b < atoms.size(); b++)
292 // AtomSpec specb = atoms.get(b);
293 // String b_model = getModelIdForFile(speca.getPdbFile());
294 // sb.append("measure ALL (" + speca.getAtomIndex() + " and */"
295 // + a_model + ") (" + specb.getAtomIndex() + " and */"
296 // + b_model + ");");
299 // jmolHistory(false, useScriptWait);
300 // jmolScript(sb.toString(), useScriptWait);
301 // jmolHistory(true, useScriptWait);
309 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
310 String pdbfile, boolean useScriptWait)
312 String modelId = getModelIdForFile(pdbfile);
313 if (modelId.isEmpty())
318 jmolHistory(false, useScriptWait);
320 StringBuilder selection = new StringBuilder(32);
321 StringBuilder cmd = new StringBuilder(64);
322 selection.append("select ").append(String.valueOf(pdbResNum));
323 selection.append(":");
324 if (!chain.equals(" "))
326 selection.append(chain);
328 selection.append(" /").append(modelId);
330 cmd.append(selection).append(";wireframe 100;").append(selection)
331 .append(" and not hetero;").append("spacefill 200;select none");
333 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
334 .append(" and not hetero; spacefill 0;");
336 jmolScript(cmd.toString(), useScriptWait);
337 jmolHistory(true, useScriptWait);
340 private boolean debug = true;
342 private void jmolHistory(boolean enable)
344 jmolHistory(enable, false);
347 private void jmolHistory(boolean enable, boolean useScriptWait)
349 jmolScript("History " + ((debug || enable) ? "on" : "off"),
353 public void loadInline(String string)
357 // viewer.loadInline(strModel, isAppend);
359 // construct fake fullPathName and fileName so we can identify the file
361 // Then, construct pass a reader for the string to Jmol.
362 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
363 // fileName, null, reader, false, null, null, 0);
364 jmolViewer.openStringInline(string);
367 protected void mouseOverStructure(int atomIndex, final String strInfo)
370 int alocsep = strInfo.indexOf("^");
371 int mdlSep = strInfo.indexOf("/");
372 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
374 if (chainSeparator == -1)
376 chainSeparator = strInfo.indexOf(".");
377 if (mdlSep > -1 && mdlSep < chainSeparator)
379 chainSeparator1 = chainSeparator;
380 chainSeparator = mdlSep;
383 // handle insertion codes
386 pdbResNum = Integer.parseInt(
387 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
392 pdbResNum = Integer.parseInt(
393 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
397 if (strInfo.indexOf(":") > -1)
399 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
400 strInfo.indexOf("."));
407 String pdbfilename = modelFileNames[0]; // default is first model
410 if (chainSeparator1 == -1)
412 chainSeparator1 = strInfo.indexOf(".", mdlSep);
414 String mdlId = (chainSeparator1 > -1)
415 ? strInfo.substring(mdlSep + 1, chainSeparator1)
416 : strInfo.substring(mdlSep + 1);
419 // recover PDB filename for the model hovered over.
420 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
421 if (_modelFileNameMap != null)
423 int _mp = _modelFileNameMap.length - 1;
425 while (mnumber < _modelFileNameMap[_mp])
429 pdbfilename = modelFileNames[_mp];
433 if (mnumber >= 0 && mnumber < modelFileNames.length)
435 pdbfilename = modelFileNames[mnumber];
438 if (pdbfilename == null)
440 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
444 } catch (Exception e)
450 * highlight position on alignment(s); if some text is returned, show this as a
451 * second line on the structure hover tooltip
453 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
457 // change comma to pipe separator (newline token for Jmol)
458 label = label.replace(',', '|');
459 StringTokenizer toks = new StringTokenizer(strInfo, " ");
460 StringBuilder sb = new StringBuilder();
461 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
462 .append(chainId).append("/1");
463 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
464 .append(toks.nextToken());
465 sb.append("|").append(label).append("\"");
466 executeCommand(new StructureCommand(sb.toString()), false);
470 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
472 if (strInfo.equals(lastMessage))
476 lastMessage = strInfo;
479 System.err.println("Ignoring additional hover info: " + data
480 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
482 mouseOverStructure(atomIndex, strInfo);
486 * { if (history != null && strStatus != null &&
487 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
491 public void notifyAtomPicked(int atomIndex, String strInfo,
495 * this implements the toggle label behaviour copied from the original
496 * structure viewer, mc_view
500 System.err.println("Ignoring additional pick data string " + strData);
502 int chainSeparator = strInfo.indexOf(":");
504 if (chainSeparator == -1)
506 chainSeparator = strInfo.indexOf(".");
509 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
511 String mdlString = "";
512 if ((p = strInfo.indexOf(":")) > -1)
514 picked += strInfo.substring(p, strInfo.indexOf("."));
517 if ((p = strInfo.indexOf("/")) > -1)
519 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
521 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
525 if (!atomsPicked.contains(picked))
527 jmolScript("select " + picked + ";label %n %r:%c");
528 atomsPicked.addElement(picked);
532 jmolViewer.evalString("select " + picked + ";label off");
533 atomsPicked.removeElement(picked);
536 // TODO: in application this happens
538 // if (scriptWindow != null)
540 // scriptWindow.sendConsoleMessage(strInfo);
541 // scriptWindow.sendConsoleMessage("\n");
547 public void notifyCallback(CBK type, Object[] data)
550 * ensure processed in AWT thread to avoid risk of deadlocks
552 SwingUtilities.invokeLater(new Runnable()
558 processCallback(type, data);
564 * Processes one callback notification from Jmol
569 protected void processCallback(CBK type, Object[] data)
576 notifyFileLoaded((String) data[1], (String) data[2],
577 (String) data[3], (String) data[4],
578 ((Integer) data[5]).intValue());
582 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
584 // also highlight in alignment
585 // deliberate fall through
587 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
591 notifyScriptTermination((String) data[2],
592 ((Integer) data[3]).intValue());
595 sendConsoleEcho((String) data[1]);
599 (data == null) ? ((String) null) : (String) data[1]);
602 // System.err.println("Ignoring error callback.");
613 "Unhandled callback " + type + " " + data[1].toString());
616 } catch (Exception e)
618 System.err.println("Squashed Jmol callback handler error:");
624 public boolean notifyEnabled(CBK callbackPick)
626 switch (callbackPick)
642 // incremented every time a load notification is successfully handled -
643 // lightweight mechanism for other threads to detect when they can start
644 // referrring to new structures.
645 private long loadNotifiesHandled = 0;
647 public long getLoadNotifiesHandled()
649 return loadNotifiesHandled;
652 public void notifyFileLoaded(String fullPathName, String fileName2,
653 String modelName, String errorMsg, int modelParts)
655 if (errorMsg != null)
657 fileLoadingError = errorMsg;
661 // TODO: deal sensibly with models loaded inLine:
662 // modelName will be null, as will fullPathName.
664 // the rest of this routine ignores the arguments, and simply interrogates
665 // the Jmol view to find out what structures it contains, and adds them to
666 // the structure selection manager.
667 fileLoadingError = null;
668 String[] oldmodels = modelFileNames;
669 modelFileNames = null;
670 boolean notifyLoaded = false;
671 String[] modelfilenames = getStructureFiles();
672 if (modelfilenames == null)
674 // Jmol is still loading files!
677 // first check if we've lost any structures
678 if (oldmodels != null && oldmodels.length > 0)
681 for (int i = 0; i < oldmodels.length; i++)
683 for (int n = 0; n < modelfilenames.length; n++)
685 if (modelfilenames[n] == oldmodels[i])
691 if (oldmodels[i] != null)
698 String[] oldmfn = new String[oldm];
700 for (int i = 0; i < oldmodels.length; i++)
702 if (oldmodels[i] != null)
704 oldmfn[oldm++] = oldmodels[i];
707 // deregister the Jmol instance for these structures - we'll add
708 // ourselves again at the end for the current structure set.
709 getSsm().removeStructureViewerListener(this, oldmfn);
713 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
715 String fileName = modelfilenames[modelnum];
716 boolean foundEntry = false;
717 StructureFile pdb = null;
718 String pdbfile = null;
719 // model was probably loaded inline - so check the pdb file hashcode
722 // calculate essential attributes for the pdb data imported inline.
723 // prolly need to resolve modelnumber properly - for now just use our
725 pdbfile = jmolViewer.getData(
726 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
728 // search pdbentries and sequences to find correct pdbentry for this
730 for (int pe = 0; pe < getPdbCount(); pe++)
732 boolean matches = false;
733 addSequence(pe, getSequence()[pe]);
734 if (fileName == null)
737 // see JAL-623 - need method of matching pasted data up
739 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
740 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
741 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
748 File fl = new File(getPdbEntry(pe).getFile());
749 matches = fl.equals(new File(fileName));
753 // TODO: Jmol can in principle retrieve from CLASSLOADER but
756 // to be tested. See mantis bug
757 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
758 DataSourceType protocol = DataSourceType.URL;
763 protocol = DataSourceType.FILE;
765 } catch (Exception e)
770 // Explicitly map to the filename used by Jmol ;
771 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
772 fileName, protocol, getIProgressIndicator());
773 // pdbentry[pe].getFile(), protocol);
779 stashFoundChains(pdb, fileName);
784 if (!foundEntry && associateNewStructs)
786 // this is a foreign pdb file that jalview doesn't know about - add
787 // it to the dataset and try to find a home - either on a matching
788 // sequence or as a new sequence.
789 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
791 // parse pdb file into a chain, etc.
792 // locate best match for pdb in associated views and add mapping to
794 // if properly registered then
800 // so finally, update the jmol bits and pieces
801 // if (jmolpopup != null)
803 // // potential for deadlock here:
804 // // jmolpopup.updateComputedMenus();
806 if (!isLoadingFromArchive())
809 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
811 // register ourselves as a listener and notify the gui that it needs to
813 getSsm().addStructureViewerListener(this);
816 FeatureRenderer fr = getFeatureRenderer(null);
819 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
820 ((AppJmol) getViewer()).getAlignmentPanel().av
821 .applyFeaturesStyle(colours);
824 loadNotifiesHandled++;
826 setLoadingFromArchive(false);
829 protected IProgressIndicator getIProgressIndicator()
834 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
836 notifyAtomPicked(iatom, strMeasure, null);
839 public abstract void notifyScriptTermination(String strStatus,
843 * display a message echoed from the jmol viewer
847 public abstract void sendConsoleEcho(String strEcho); /*
848 * { showConsole(true);
850 * history.append("\n" + strEcho); }
853 // /End JmolStatusListener
854 // /////////////////////////////
858 * status message - usually the response received after a script
861 public abstract void sendConsoleMessage(String strStatus);
864 public void setCallbackFunction(String callbackType,
865 String callbackFunction)
867 System.err.println("Ignoring set-callback request to associate "
868 + callbackType + " with function " + callbackFunction);
872 public void showHelp()
874 showUrl("http://wiki.jmol.org"
875 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
880 * open the URL somehow
884 public abstract void showUrl(String url, String target);
887 * called to show or hide the associated console window container.
891 public abstract void showConsole(boolean show);
893 public static Viewer getJmolData(JmolParser jmolParser)
895 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
896 "-x -o -n", jmolParser);
905 * - when true will initialise jmol's file IO system (should be false
908 * @param documentBase
910 * @param commandOptions
912 public void allocateViewer(Container renderPanel, boolean jmolfileio,
913 String htmlName, URL documentBase, URL codeBase,
914 String commandOptions)
916 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
917 codeBase, commandOptions, null, null);
924 * - when true will initialise jmol's file IO system (should be false
927 * @param documentBase
929 * @param commandOptions
930 * @param consolePanel
931 * - panel to contain Jmol console
932 * @param buttonsToShow
933 * - buttons to show on the console, in order
935 public void allocateViewer(Container renderPanel, boolean jmolfileio,
936 String htmlName, URL documentBase, URL codeBase,
937 String commandOptions, final Container consolePanel,
938 String buttonsToShow)
941 System.err.println("Allocating Jmol Viewer: " + commandOptions);
943 if (commandOptions == null)
947 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
948 (jmolfileio ? new SmarterJmolAdapter() : null),
949 htmlName + ((Object) this).toString(), documentBase, codeBase,
950 commandOptions, this);
952 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
956 console = createJmolConsole(consolePanel, buttonsToShow);
957 } catch (Throwable e)
959 System.err.println("Could not create Jmol application console. "
963 if (consolePanel != null)
965 consolePanel.addComponentListener(this);
971 protected abstract JmolAppConsoleInterface createJmolConsole(
972 Container consolePanel, String buttonsToShow);
974 // BH 2018 -- Jmol console is not working due to problems with styled
977 protected org.jmol.api.JmolAppConsoleInterface console = null;
980 public int[] resizeInnerPanel(String data)
982 // Jalview doesn't honour resize panel requests
989 protected void closeConsole()
995 console.setVisible(false);
998 } catch (Exception x)
1007 * ComponentListener method
1010 public void componentMoved(ComponentEvent e)
1015 * ComponentListener method
1018 public void componentResized(ComponentEvent e)
1023 * ComponentListener method
1026 public void componentShown(ComponentEvent e)
1032 * ComponentListener method
1035 public void componentHidden(ComponentEvent e)
1041 protected String getModelIdForFile(String pdbFile)
1043 if (modelFileNames == null)
1047 for (int i = 0; i < modelFileNames.length; i++)
1049 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1051 return String.valueOf(i + 1);
1058 protected ViewerType getViewerType()
1060 return ViewerType.JMOL;
1064 protected String getModelId(int pdbfnum, String file)
1066 return String.valueOf(pdbfnum + 1);
1070 * Returns ".spt" - the Jmol session file extension
1073 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1076 public String getSessionFileExtension()
1082 public void selectionChanged(BS arg0)
1084 // TODO Auto-generated method stub
1089 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1091 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1095 public String getHelpURL()
1097 return "http://wiki.jmol.org"; // BH 2018