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 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
170 public String createImage(String fileName, String type,
171 Object textOrBytes, int quality)
173 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
178 public String eval(String strEval)
180 // jalview.bin.Console.outPrintln(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 if (resetLastRes.length() > 0)
272 jmolScript(resetLastRes.toString());
273 resetLastRes.setLength(0);
275 StringBuilder highlightCommands=null;
276 for (AtomSpec atom : atoms)
278 StringBuilder thisAtom = highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
279 atom.getChain(), atom.getPdbFile());
280 if (thisAtom!=null) {
281 if (highlightCommands==null)
283 highlightCommands=thisAtom;
285 highlightCommands.append(thisAtom);
289 if (highlightCommands!=null)
292 jmolScript(highlightCommands.toString());
295 // Highlight distances between atoms with a 'measure' command - not yet
297 // if (atoms.size() >= 2)
299 // StringBuilder sb = new StringBuilder();
300 // for (int a = 0; a < atoms.size(); a++)
302 // AtomSpec speca = atoms.get(a);
303 // String a_model = getModelIdForFile(speca.getPdbFile());
304 // for (int b = a + 1; b < atoms.size(); b++)
306 // AtomSpec specb = atoms.get(b);
307 // String b_model = getModelIdForFile(speca.getPdbFile());
308 // sb.append("measure ALL (" + speca.getAtomIndex() + " and */"
309 // + a_model + ") (" + specb.getAtomIndex() + " and */"
310 // + b_model + ");");
313 // jmolHistory(false, useScriptWait);
314 // jmolScript(sb.toString(), useScriptWait);
315 // jmolHistory(true, useScriptWait);
323 private StringBuilder highlightAtom(int atomIndex, int pdbResNum, String chain,
326 String modelId = getModelIdForFile(pdbfile);
327 if (modelId.isEmpty())
332 StringBuilder selection = new StringBuilder(32);
333 StringBuilder cmd = new StringBuilder(64);
334 selection.append("select ").append(String.valueOf(pdbResNum));
335 selection.append(":");
336 if (!chain.equals(" "))
338 selection.append(chain);
340 selection.append(" /").append(modelId);
342 cmd.append(selection).append(";wireframe 100;").append(selection)
343 .append(" and not hetero;").append("spacefill 200;select none");
345 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
346 .append(" and not hetero; spacefill 0;");
351 private boolean debug = true;
353 private void jmolHistory(boolean enable)
355 jmolHistory(enable, false);
358 private void jmolHistory(boolean enable, boolean useScriptWait)
360 jmolScript("History " + ((debug || enable) ? "on" : "off"),
364 public void loadInline(String string)
368 // viewer.loadInline(strModel, isAppend);
370 // construct fake fullPathName and fileName so we can identify the file
372 // Then, construct pass a reader for the string to Jmol.
373 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
374 // fileName, null, reader, false, null, null, 0);
375 jmolViewer.openStringInline(string);
378 protected void mouseOverStructure(int atomIndex, final String strInfo)
381 int alocsep = strInfo.indexOf("^");
382 int mdlSep = strInfo.indexOf("/");
383 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
385 if (chainSeparator == -1)
387 chainSeparator = strInfo.indexOf(".");
388 if (mdlSep > -1 && mdlSep < chainSeparator)
390 chainSeparator1 = chainSeparator;
391 chainSeparator = mdlSep;
394 // handle insertion codes
397 pdbResNum = Integer.parseInt(
398 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
403 pdbResNum = Integer.parseInt(
404 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
408 if (strInfo.indexOf(":") > -1)
410 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
411 strInfo.indexOf("."));
418 String pdbfilename = modelFileNames[0]; // default is first model
421 if (chainSeparator1 == -1)
423 chainSeparator1 = strInfo.indexOf(".", mdlSep);
425 String mdlId = (chainSeparator1 > -1)
426 ? strInfo.substring(mdlSep + 1, chainSeparator1)
427 : strInfo.substring(mdlSep + 1);
430 // recover PDB filename for the model hovered over.
431 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
432 if (_modelFileNameMap != null)
434 int _mp = _modelFileNameMap.length - 1;
436 while (mnumber < _modelFileNameMap[_mp])
440 pdbfilename = modelFileNames[_mp];
444 if (mnumber >= 0 && mnumber < modelFileNames.length)
446 pdbfilename = modelFileNames[mnumber];
449 if (pdbfilename == null)
451 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
455 } catch (Exception e)
461 * highlight position on alignment(s); if some text is returned, show this as a
462 * second line on the structure hover tooltip
464 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
468 // change comma to pipe separator (newline token for Jmol)
469 label = label.replace(',', '|');
470 StringTokenizer toks = new StringTokenizer(strInfo, " ");
471 StringBuilder sb = new StringBuilder();
472 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
473 .append(chainId).append("/1");
474 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
475 .append(toks.nextToken());
476 sb.append("|").append(label).append("\"");
477 executeCommand(new StructureCommand(sb.toString()), false);
481 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
483 if (strInfo.equals(lastMessage))
487 lastMessage = strInfo;
490 jalview.bin.Console.errPrintln("Ignoring additional hover info: " + data
491 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
493 mouseOverStructure(atomIndex, strInfo);
497 * { if (history != null && strStatus != null &&
498 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
502 public void notifyAtomPicked(int atomIndex, String strInfo,
506 * this implements the toggle label behaviour copied from the original
507 * structure viewer, mc_view
511 jalview.bin.Console.errPrintln("Ignoring additional pick data string " + strData);
513 int chainSeparator = strInfo.indexOf(":");
515 if (chainSeparator == -1)
517 chainSeparator = strInfo.indexOf(".");
520 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
522 String mdlString = "";
523 if ((p = strInfo.indexOf(":")) > -1)
525 picked += strInfo.substring(p, strInfo.indexOf("."));
528 if ((p = strInfo.indexOf("/")) > -1)
530 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
532 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
536 if (!atomsPicked.contains(picked))
538 jmolScript("select " + picked + ";label %n %r:%c");
539 atomsPicked.addElement(picked);
543 jmolViewer.evalString("select " + picked + ";label off");
544 atomsPicked.removeElement(picked);
547 // TODO: in application this happens
549 // if (scriptWindow != null)
551 // scriptWindow.sendConsoleMessage(strInfo);
552 // scriptWindow.sendConsoleMessage("\n");
558 public void notifyCallback(CBK type, Object[] data)
561 * ensure processed in AWT thread to avoid risk of deadlocks
563 SwingUtilities.invokeLater(new Runnable()
569 processCallback(type, data);
575 * Processes one callback notification from Jmol
580 protected void processCallback(CBK type, Object[] data)
587 notifyFileLoaded((String) data[1], (String) data[2],
588 (String) data[3], (String) data[4],
589 ((Integer) data[5]).intValue());
593 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
595 // also highlight in alignment
596 // deliberate fall through
598 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
602 notifyScriptTermination((String) data[2],
603 ((Integer) data[3]).intValue());
606 sendConsoleEcho((String) data[1]);
610 (data == null) ? ((String) null) : (String) data[1]);
613 // jalview.bin.Console.errPrintln("Ignoring error callback.");
623 jalview.bin.Console.errPrintln(
624 "Unhandled callback " + type + " " + data[1].toString());
627 } catch (Exception e)
629 jalview.bin.Console.errPrintln("Squashed Jmol callback handler error:");
635 public boolean notifyEnabled(CBK callbackPick)
637 switch (callbackPick)
653 // incremented every time a load notification is successfully handled -
654 // lightweight mechanism for other threads to detect when they can start
655 // referrring to new structures.
656 private long loadNotifiesHandled = 0;
658 public long getLoadNotifiesHandled()
660 return loadNotifiesHandled;
663 public void notifyFileLoaded(String fullPathName, String fileName2,
664 String modelName, String errorMsg, int modelParts)
666 if (errorMsg != null)
668 fileLoadingError = errorMsg;
672 // TODO: deal sensibly with models loaded inLine:
673 // modelName will be null, as will fullPathName.
675 // the rest of this routine ignores the arguments, and simply interrogates
676 // the Jmol view to find out what structures it contains, and adds them to
677 // the structure selection manager.
678 fileLoadingError = null;
679 String[] oldmodels = modelFileNames;
680 modelFileNames = null;
681 boolean notifyLoaded = false;
682 String[] modelfilenames = getStructureFiles();
683 if (modelfilenames == null)
685 // Jmol is still loading files!
688 // first check if we've lost any structures
689 if (oldmodels != null && oldmodels.length > 0)
692 for (int i = 0; i < oldmodels.length; i++)
694 for (int n = 0; n < modelfilenames.length; n++)
696 if (modelfilenames[n] == oldmodels[i])
702 if (oldmodels[i] != null)
709 String[] oldmfn = new String[oldm];
711 for (int i = 0; i < oldmodels.length; i++)
713 if (oldmodels[i] != null)
715 oldmfn[oldm++] = oldmodels[i];
718 // deregister the Jmol instance for these structures - we'll add
719 // ourselves again at the end for the current structure set.
720 getSsm().removeStructureViewerListener(this, oldmfn);
724 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
726 String fileName = modelfilenames[modelnum];
727 boolean foundEntry = false;
728 StructureFile pdb = null;
729 String pdbfile = null;
730 // model was probably loaded inline - so check the pdb file hashcode
733 // calculate essential attributes for the pdb data imported inline.
734 // prolly need to resolve modelnumber properly - for now just use our
736 pdbfile = jmolViewer.getData(
737 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
739 // search pdbentries and sequences to find correct pdbentry for this
741 for (int pe = 0; pe < getPdbCount(); pe++)
743 boolean matches = false;
744 addSequence(pe, getSequence()[pe]);
745 if (fileName == null)
748 // see JAL-623 - need method of matching pasted data up
750 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
751 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
752 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
759 File fl = new File(getPdbEntry(pe).getFile());
760 matches = fl.equals(new File(fileName));
764 // TODO: Jmol can in principle retrieve from CLASSLOADER but
767 // to be tested. See mantis bug
768 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
769 DataSourceType protocol = DataSourceType.URL;
774 protocol = DataSourceType.FILE;
776 } catch (Exception e)
781 // Explicitly map to the filename used by Jmol ;
782 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
783 fileName, protocol, getIProgressIndicator());
784 // pdbentry[pe].getFile(), protocol);
790 stashFoundChains(pdb, fileName);
795 if (!foundEntry && associateNewStructs)
797 // this is a foreign pdb file that jalview doesn't know about - add
798 // it to the dataset and try to find a home - either on a matching
799 // sequence or as a new sequence.
800 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
802 // parse pdb file into a chain, etc.
803 // locate best match for pdb in associated views and add mapping to
805 // if properly registered then
811 // so finally, update the jmol bits and pieces
812 // if (jmolpopup != null)
814 // // potential for deadlock here:
815 // // jmolpopup.updateComputedMenus();
817 if (!isLoadingFromArchive())
820 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
822 // register ourselves as a listener and notify the gui that it needs to
824 getSsm().addStructureViewerListener(this);
827 FeatureRenderer fr = getFeatureRenderer(null);
830 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
831 ((AppJmol) getViewer()).getAlignmentPanel().av
832 .applyFeaturesStyle(colours);
835 loadNotifiesHandled++;
837 setLoadingFromArchive(false);
840 protected IProgressIndicator getIProgressIndicator()
845 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
847 notifyAtomPicked(iatom, strMeasure, null);
850 public abstract void notifyScriptTermination(String strStatus,
854 * display a message echoed from the jmol viewer
858 public abstract void sendConsoleEcho(String strEcho); /*
859 * { showConsole(true);
861 * history.append("\n" + strEcho); }
864 // /End JmolStatusListener
865 // /////////////////////////////
869 * status message - usually the response received after a script
872 public abstract void sendConsoleMessage(String strStatus);
875 public void setCallbackFunction(String callbackType,
876 String callbackFunction)
878 jalview.bin.Console.errPrintln("Ignoring set-callback request to associate "
879 + callbackType + " with function " + callbackFunction);
883 public void showHelp()
885 showUrl("http://wiki.jmol.org"
886 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
891 * open the URL somehow
895 public abstract void showUrl(String url, String target);
898 * called to show or hide the associated console window container.
902 public abstract void showConsole(boolean show);
904 public static Viewer getJmolData(JmolParser jmolParser)
906 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
907 "-x -o -n", jmolParser);
916 * - when true will initialise jmol's file IO system (should be false
919 * @param documentBase
921 * @param commandOptions
923 public void allocateViewer(Container renderPanel, boolean jmolfileio,
924 String htmlName, URL documentBase, URL codeBase,
925 String commandOptions)
927 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
928 codeBase, commandOptions, null, null);
935 * - when true will initialise jmol's file IO system (should be false
938 * @param documentBase
940 * @param commandOptions
941 * @param consolePanel
942 * - panel to contain Jmol console
943 * @param buttonsToShow
944 * - buttons to show on the console, in order
946 public void allocateViewer(Container renderPanel, boolean jmolfileio,
947 String htmlName, URL documentBase, URL codeBase,
948 String commandOptions, final Container consolePanel,
949 String buttonsToShow)
952 jalview.bin.Console.errPrintln("Allocating Jmol Viewer: " + commandOptions);
954 if (commandOptions == null)
958 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
959 (jmolfileio ? new SmarterJmolAdapter() : null),
960 htmlName + ((Object) this).toString(), documentBase, codeBase,
961 commandOptions, this);
963 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
967 console = createJmolConsole(consolePanel, buttonsToShow);
968 } catch (Throwable e)
970 jalview.bin.Console.errPrintln("Could not create Jmol application console. "
974 if (consolePanel != null)
976 consolePanel.addComponentListener(this);
982 protected abstract JmolAppConsoleInterface createJmolConsole(
983 Container consolePanel, String buttonsToShow);
985 // BH 2018 -- Jmol console is not working due to problems with styled
988 protected org.jmol.api.JmolAppConsoleInterface console = null;
991 public int[] resizeInnerPanel(String data)
993 // Jalview doesn't honour resize panel requests
1000 protected void closeConsole()
1002 if (console != null)
1006 console.setVisible(false);
1009 } catch (Exception x)
1018 * ComponentListener method
1021 public void componentMoved(ComponentEvent e)
1026 * ComponentListener method
1029 public void componentResized(ComponentEvent e)
1034 * ComponentListener method
1037 public void componentShown(ComponentEvent e)
1043 * ComponentListener method
1046 public void componentHidden(ComponentEvent e)
1052 protected String getModelIdForFile(String pdbFile)
1054 if (modelFileNames == null)
1058 for (int i = 0; i < modelFileNames.length; i++)
1060 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1062 return String.valueOf(i + 1);
1069 protected ViewerType getViewerType()
1071 return ViewerType.JMOL;
1075 protected String getModelId(int pdbfnum, String file)
1077 return String.valueOf(pdbfnum + 1);
1081 * Returns ".spt" - the Jmol session file extension
1084 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1087 public String getSessionFileExtension()
1093 public void selectionChanged(BS arg0)
1095 // TODO Auto-generated method stub
1100 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1102 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1106 public String getHelpURL()
1108 return "http://wiki.jmol.org"; // BH 2018