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.Cache;
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, ComponentListener {
66 private String lastMessage;
69 * when true, try to search the associated datamodel for sequences that are
70 * associated with any unknown structures in the Jmol view.
72 private boolean associateNewStructs = false;
74 private Vector<String> atomsPicked = new Vector<>();
76 private String lastCommand;
78 private boolean loadedInline;
80 private StringBuffer resetLastRes = new StringBuffer();
82 public Viewer jmolViewer;
84 public JalviewJmolBinding(StructureSelectionManager ssm, PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
85 DataSourceType protocol) {
86 super(ssm, pdbentry, sequenceIs, protocol);
87 setStructureCommands(new JmolCommands());
89 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
90 * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
93 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
97 public JalviewJmolBinding(StructureSelectionManager ssm, SequenceI[][] seqs, Viewer theViewer) {
100 jmolViewer = theViewer;
101 jmolViewer.setJmolStatusListener(this);
102 jmolViewer.addSelectionListener(this);
103 setStructureCommands(new JmolCommands());
107 * construct a title string for the viewer window based on the data jalview
112 public String getViewerTitle() {
113 return getViewerTitle("Jmol", true);
116 private String jmolScript(String script) {
117 Cache.log.debug(">>Jmol>> " + script);
118 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
119 Cache.log.debug("<<Jmol<< " + s);
125 public List<String> executeCommand(StructureCommandI command, boolean getReply) {
126 if (command == null) {
129 String cmd = command.getCommand();
131 if (lastCommand == null || !lastCommand.equals(cmd)) {
132 jmolScript(cmd + "\n");
139 public void createImage(String file, String type, int quality) {
140 System.out.println("JMOL CREATE IMAGE");
144 public String createImage(String fileName, String type, Object textOrBytes, int quality) {
145 System.out.println("JMOL CREATE IMAGE");
150 public String eval(String strEval) {
151 // System.out.println(strEval);
152 // "# 'eval' is implemented only for the applet.";
156 // End StructureListener
157 // //////////////////////////
160 public float[][] functionXY(String functionName, int x, int y) {
165 public float[][][] functionXYZ(String functionName, int nx, int ny, int nz) {
166 // TODO Auto-generated method stub
171 * map between index of model filename returned from getPdbFile and the first
172 * index of models from this file in the viewer. Note - this is not trimmed -
173 * use getPdbFile to get number of unique models.
175 private int _modelFileNameMap[];
178 public synchronized String[] getStructureFiles() {
179 if (jmolViewer == null) {
180 return new String[0];
183 if (modelFileNames == null) {
184 int modelCount = jmolViewer.ms.mc;
185 String filePath = null;
186 List<String> mset = new ArrayList<>();
187 for (int i = 0; i < modelCount; ++i) {
189 * defensive check for null as getModelFileName can return null even when model
192 filePath = jmolViewer.ms.getModelFileName(i);
193 if (filePath != null && !mset.contains(filePath)) {
197 if (!mset.isEmpty()) {
198 modelFileNames = mset.toArray(new String[mset.size()]);
202 return modelFileNames;
206 * map from string to applet
209 public Map<String, Object> getRegistryInfo() {
210 // TODO Auto-generated method stub
214 // ///////////////////////////////
215 // JmolStatusListener
217 public void handlePopupMenu(int x, int y) {
218 // jmolpopup.show(x, y);
219 // jmolpopup.jpiShow(x, y);
223 * Highlight zero, one or more atoms on the structure
226 public void highlightAtoms(List<AtomSpec> atoms) {
228 if (resetLastRes.length() > 0) {
229 jmolScript(resetLastRes.toString());
230 resetLastRes.setLength(0);
232 for (AtomSpec atom : atoms) {
233 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(), atom.getChain(), atom.getPdbFile());
239 public void highlightAtom(int atomIndex, int pdbResNum, String chain, String pdbfile) {
240 String modelId = getModelIdForFile(pdbfile);
241 if (modelId.isEmpty()) {
247 StringBuilder selection = new StringBuilder(32);
248 StringBuilder cmd = new StringBuilder(64);
249 selection.append("select ").append(String.valueOf(pdbResNum));
250 selection.append(":");
251 if (!chain.equals(" ")) {
252 selection.append(chain);
254 selection.append(" /").append(modelId);
256 cmd.append(selection).append(";wireframe 100;").append(selection).append(" and not hetero;")
257 .append("spacefill 200;select none");
259 resetLastRes.append(selection).append(";wireframe 0;").append(selection).append(" and not hetero; spacefill 0;");
261 jmolScript(cmd.toString());
265 private boolean debug = true;
267 private void jmolHistory(boolean enable) {
268 jmolScript("History " + ((debug || enable) ? "on" : "off"));
271 public void loadInline(String string) {
274 // viewer.loadInline(strModel, isAppend);
276 // construct fake fullPathName and fileName so we can identify the file
278 // Then, construct pass a reader for the string to Jmol.
279 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
280 // fileName, null, reader, false, null, null, 0);
281 jmolViewer.openStringInline(string);
284 protected void mouseOverStructure(int atomIndex, final String strInfo) {
286 int alocsep = strInfo.indexOf("^");
287 int mdlSep = strInfo.indexOf("/");
288 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
290 if (chainSeparator == -1) {
291 chainSeparator = strInfo.indexOf(".");
292 if (mdlSep > -1 && mdlSep < chainSeparator) {
293 chainSeparator1 = chainSeparator;
294 chainSeparator = mdlSep;
297 // handle insertion codes
299 pdbResNum = Integer.parseInt(strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
302 pdbResNum = Integer.parseInt(strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
306 if (strInfo.indexOf(":") > -1) {
307 chainId = strInfo.substring(strInfo.indexOf(":") + 1, strInfo.indexOf("."));
312 String pdbfilename = modelFileNames[0]; // default is first model
314 if (chainSeparator1 == -1) {
315 chainSeparator1 = strInfo.indexOf(".", mdlSep);
317 String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1, chainSeparator1)
318 : strInfo.substring(mdlSep + 1);
320 // recover PDB filename for the model hovered over.
321 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
322 if (_modelFileNameMap != null) {
323 int _mp = _modelFileNameMap.length - 1;
325 while (mnumber < _modelFileNameMap[_mp]) {
328 pdbfilename = modelFileNames[_mp];
330 if (mnumber >= 0 && mnumber < modelFileNames.length) {
331 pdbfilename = modelFileNames[mnumber];
334 if (pdbfilename == null) {
335 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber)).getAbsolutePath();
338 } catch (Exception e) {
343 * highlight position on alignment(s); if some text is returned, show this as a
344 * second line on the structure hover tooltip
346 String label = getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
348 // change comma to pipe separator (newline token for Jmol)
349 label = label.replace(',', '|');
350 StringTokenizer toks = new StringTokenizer(strInfo, " ");
351 StringBuilder sb = new StringBuilder();
352 sb.append("select ").append(String.valueOf(pdbResNum)).append(":").append(chainId).append("/1");
353 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ").append(toks.nextToken());
354 sb.append("|").append(label).append("\"");
355 executeCommand(new StructureCommand(sb.toString()), false);
359 public void notifyAtomHovered(int atomIndex, String strInfo, String data) {
360 if (strInfo.equals(lastMessage)) {
363 lastMessage = strInfo;
366 "Ignoring additional hover info: " + data + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
368 mouseOverStructure(atomIndex, strInfo);
372 * { if (history != null && strStatus != null &&
373 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
377 public void notifyAtomPicked(int atomIndex, String strInfo, String strData) {
379 * this implements the toggle label behaviour copied from the original structure
382 if (strData != null) {
383 System.err.println("Ignoring additional pick data string " + strData);
385 int chainSeparator = strInfo.indexOf(":");
387 if (chainSeparator == -1) {
388 chainSeparator = strInfo.indexOf(".");
391 String picked = strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator);
392 String mdlString = "";
393 if ((p = strInfo.indexOf(":")) > -1) {
394 picked += strInfo.substring(p, strInfo.indexOf("."));
397 if ((p = strInfo.indexOf("/")) > -1) {
398 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
400 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P" + mdlString + "))";
403 if (!atomsPicked.contains(picked)) {
404 jmolScript("select " + picked + ";label %n %r:%c");
405 atomsPicked.addElement(picked);
407 jmolViewer.evalString("select " + picked + ";label off");
408 atomsPicked.removeElement(picked);
411 // TODO: in application this happens
413 // if (scriptWindow != null)
415 // scriptWindow.sendConsoleMessage(strInfo);
416 // scriptWindow.sendConsoleMessage("\n");
422 public void notifyCallback(CBK type, Object[] data) {
424 * ensure processed in AWT thread to avoid risk of deadlocks
426 SwingUtilities.invokeLater(new Runnable() {
430 processCallback(type, data);
436 * Processes one callback notification from Jmol
441 protected void processCallback(CBK type, Object[] data) {
445 notifyFileLoaded((String) data[1], (String) data[2], (String) data[3], (String) data[4],
446 ((Integer) data[5]).intValue());
450 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
451 // also highlight in alignment
452 // deliberate fall through
454 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
457 notifyScriptTermination((String) data[2], ((Integer) data[3]).intValue());
460 sendConsoleEcho((String) data[1]);
463 sendConsoleMessage((data == null) ? ((String) null) : (String) data[1]);
466 // System.err.println("Ignoring error callback.");
476 System.err.println("Unhandled callback " + type + " " + data[1].toString());
479 } catch (Exception e) {
480 System.err.println("Squashed Jmol callback handler error:");
486 public boolean notifyEnabled(CBK callbackPick) {
487 switch (callbackPick) {
502 // incremented every time a load notification is successfully handled -
503 // lightweight mechanism for other threads to detect when they can start
504 // referrring to new structures.
505 private long loadNotifiesHandled = 0;
507 public long getLoadNotifiesHandled() {
508 return loadNotifiesHandled;
511 public void notifyFileLoaded(String fullPathName, String fileName2, String modelName, String errorMsg,
513 if (errorMsg != null) {
514 fileLoadingError = errorMsg;
518 // TODO: deal sensibly with models loaded inLine:
519 // modelName will be null, as will fullPathName.
521 // the rest of this routine ignores the arguments, and simply interrogates
522 // the Jmol view to find out what structures it contains, and adds them to
523 // the structure selection manager.
524 fileLoadingError = null;
525 String[] oldmodels = modelFileNames;
526 modelFileNames = null;
527 boolean notifyLoaded = false;
528 String[] modelfilenames = getStructureFiles();
529 if (modelfilenames == null) {
530 // Jmol is still loading files!
533 // first check if we've lost any structures
534 if (oldmodels != null && oldmodels.length > 0) {
536 for (int i = 0; i < oldmodels.length; i++) {
537 for (int n = 0; n < modelfilenames.length; n++) {
538 if (modelfilenames[n] == oldmodels[i]) {
543 if (oldmodels[i] != null) {
548 String[] oldmfn = new String[oldm];
550 for (int i = 0; i < oldmodels.length; i++) {
551 if (oldmodels[i] != null) {
552 oldmfn[oldm++] = oldmodels[i];
555 // deregister the Jmol instance for these structures - we'll add
556 // ourselves again at the end for the current structure set.
557 getSsm().removeStructureViewerListener(this, oldmfn);
561 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++) {
562 String fileName = modelfilenames[modelnum];
563 boolean foundEntry = false;
564 StructureFile pdb = null;
565 String pdbfile = null;
566 // model was probably loaded inline - so check the pdb file hashcode
568 // calculate essential attributes for the pdb data imported inline.
569 // prolly need to resolve modelnumber properly - for now just use our
571 pdbfile = jmolViewer.getData("" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
573 // search pdbentries and sequences to find correct pdbentry for this
575 for (int pe = 0; pe < getPdbCount(); pe++) {
576 boolean matches = false;
577 addSequence(pe, getSequence()[pe]);
578 if (fileName == null) {
580 // see JAL-623 - need method of matching pasted data up
582 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], pdbfile, DataSourceType.PASTE,
583 getIProgressIndicator());
584 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
589 File fl = new File(getPdbEntry(pe).getFile());
590 matches = fl.equals(new File(fileName));
593 // TODO: Jmol can in principle retrieve from CLASSLOADER but
596 // to be tested. See mantis bug
597 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
598 DataSourceType protocol = DataSourceType.URL;
601 protocol = DataSourceType.FILE;
603 } catch (Exception e) {
606 // Explicitly map to the filename used by Jmol ;
607 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], fileName, protocol, getIProgressIndicator());
608 // pdbentry[pe].getFile(), protocol);
613 stashFoundChains(pdb, fileName);
618 if (!foundEntry && associateNewStructs) {
619 // this is a foreign pdb file that jalview doesn't know about - add
620 // it to the dataset and try to find a home - either on a matching
621 // sequence or as a new sequence.
622 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1", "PDB");
623 // parse pdb file into a chain, etc.
624 // locate best match for pdb in associated views and add mapping to
626 // if properly registered then
632 // so finally, update the jmol bits and pieces
633 // if (jmolpopup != null)
635 // // potential for deadlock here:
636 // // jmolpopup.updateComputedMenus();
638 if (!isLoadingFromArchive()) {
639 jmolScript("model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
641 // register ourselves as a listener and notify the gui that it needs to
643 getSsm().addStructureViewerListener(this);
645 FeatureRenderer fr = getFeatureRenderer(null);
647 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
648 ((AppJmol) getViewer()).getAlignmentPanel().av.applyFeaturesStyle(colours);
651 loadNotifiesHandled++;
653 setLoadingFromArchive(false);
656 protected IProgressIndicator getIProgressIndicator() {
660 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure) {
661 notifyAtomPicked(iatom, strMeasure, null);
664 public abstract void notifyScriptTermination(String strStatus, int msWalltime);
667 * display a message echoed from the jmol viewer
671 public abstract void sendConsoleEcho(String strEcho); /*
672 * { showConsole(true);
674 * history.append("\n" + strEcho); }
677 // /End JmolStatusListener
678 // /////////////////////////////
681 * @param strStatus status message - usually the response received after a
684 public abstract void sendConsoleMessage(String strStatus);
687 public void setCallbackFunction(String callbackType, String callbackFunction) {
689 .println("Ignoring set-callback request to associate " + callbackType + " with function " + callbackFunction);
693 public void showHelp() {
694 showUrl("http://wiki.jmol.org"
695 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
700 * open the URL somehow
704 public abstract void showUrl(String url, String target);
707 * called to show or hide the associated console window container.
711 public abstract void showConsole(boolean show);
713 public static Viewer getJmolData(JmolParser jmolParser) {
714 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null, "-x -o -n", jmolParser);
722 * @param jmolfileio - when true will initialise jmol's file IO system
723 * (should be false in applet context)
725 * @param documentBase
727 * @param commandOptions
729 public void allocateViewer(Container renderPanel, boolean jmolfileio, String htmlName, URL documentBase, URL codeBase,
730 String commandOptions) {
731 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase, codeBase, commandOptions, null, null);
737 * @param jmolfileio - when true will initialise jmol's file IO system
738 * (should be false in applet context)
740 * @param documentBase
742 * @param commandOptions
743 * @param consolePanel - panel to contain Jmol console
744 * @param buttonsToShow - buttons to show on the console, in order
746 public void allocateViewer(Container renderPanel, boolean jmolfileio, String htmlName, URL documentBase, URL codeBase,
747 String commandOptions, final Container consolePanel, String buttonsToShow) {
749 System.err.println("Allocating Jmol Viewer: " + commandOptions);
751 if (commandOptions == null) {
754 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel, (jmolfileio ? new SmarterJmolAdapter() : null),
755 htmlName + ((Object) this).toString(), documentBase, codeBase, commandOptions, this);
757 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
760 console = createJmolConsole(consolePanel, buttonsToShow);
761 } catch (Throwable e) {
762 System.err.println("Could not create Jmol application console. " + e.getMessage());
765 if (consolePanel != null) {
766 consolePanel.addComponentListener(this);
772 protected abstract JmolAppConsoleInterface createJmolConsole(Container consolePanel, String buttonsToShow);
774 // BH 2018 -- Jmol console is not working due to problems with styled
777 protected org.jmol.api.JmolAppConsoleInterface console = null;
780 public int[] resizeInnerPanel(String data) {
781 // Jalview doesn't honour resize panel requests
788 protected void closeConsole() {
789 if (console != null) {
791 console.setVisible(false);
793 } catch (Exception x) {
801 * ComponentListener method
804 public void componentMoved(ComponentEvent e) {
808 * ComponentListener method
811 public void componentResized(ComponentEvent e) {
815 * ComponentListener method
818 public void componentShown(ComponentEvent e) {
823 * ComponentListener method
826 public void componentHidden(ComponentEvent e) {
831 protected String getModelIdForFile(String pdbFile) {
832 if (modelFileNames == null) {
835 for (int i = 0; i < modelFileNames.length; i++) {
836 if (modelFileNames[i].equalsIgnoreCase(pdbFile)) {
837 return String.valueOf(i + 1);
844 protected ViewerType getViewerType() {
845 return ViewerType.JMOL;
849 protected String getModelId(int pdbfnum, String file) {
850 return String.valueOf(pdbfnum + 1);
854 * Returns ".spt" - the Jmol session file extension
857 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
860 public String getSessionFileExtension() {
865 public void selectionChanged(BS arg0) {
866 // TODO Auto-generated method stub
871 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp) {
872 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
876 public String getHelpURL() {
877 return "http://wiki.jmol.org"; // BH 2018