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 org.jmol.adapter.smarter.SmarterJmolAdapter;
35 import org.jmol.api.JmolAppConsoleInterface;
36 import org.jmol.api.JmolSelectionListener;
37 import org.jmol.api.JmolStatusListener;
38 import org.jmol.api.JmolViewer;
39 import org.jmol.c.CBK;
40 import org.jmol.viewer.Viewer;
42 import jalview.api.AlignViewportI;
43 import jalview.api.AlignmentViewPanel;
44 import jalview.api.FeatureRenderer;
45 import jalview.api.SequenceRenderer;
46 import jalview.bin.Cache;
47 import jalview.datamodel.PDBEntry;
48 import jalview.datamodel.SequenceI;
49 import jalview.gui.IProgressIndicator;
50 import jalview.gui.StructureViewer.ViewerType;
51 import jalview.io.DataSourceType;
52 import jalview.io.StructureFile;
53 import jalview.structure.AtomSpec;
54 import jalview.structure.AtomSpecModel;
55 import jalview.structure.StructureCommand;
56 import jalview.structure.StructureCommandI;
57 import jalview.structure.StructureSelectionManager;
58 import jalview.structures.models.AAStructureBindingModel;
59 import javajs.util.BS;
61 public abstract class JalviewJmolBinding extends AAStructureBindingModel
62 implements JmolStatusListener, JmolSelectionListener,
65 private String lastMessage;
68 * when true, try to search the associated datamodel for sequences that are
69 * associated with any unknown structures in the Jmol view.
71 private boolean associateNewStructs = false;
73 private Vector<String> atomsPicked = new Vector<>();
75 private String lastCommand;
77 private boolean loadedInline;
79 private StringBuffer resetLastRes = new StringBuffer();
81 public Viewer jmolViewer;
83 public JalviewJmolBinding(StructureSelectionManager ssm,
84 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
85 DataSourceType protocol)
87 super(ssm, pdbentry, sequenceIs, protocol);
88 setStructureCommands(new JmolCommands());
90 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
91 * "jalviewJmol", ap.av.applet .getDocumentBase(),
92 * ap.av.applet.getCodeBase(), "", this);
94 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
98 public JalviewJmolBinding(StructureSelectionManager ssm,
99 SequenceI[][] seqs, Viewer theViewer)
103 jmolViewer = theViewer;
104 jmolViewer.setJmolStatusListener(this);
105 jmolViewer.addSelectionListener(this);
106 setStructureCommands(new JmolCommands());
110 * construct a title string for the viewer window based on the data jalview
115 public String getViewerTitle()
117 return getViewerTitle("Jmol", true);
121 * prepare the view for a given set of models/chains. chainList contains strings
122 * of the form 'pdbfilename:Chaincode'
124 * @deprecated now only used by applet code
127 public void centerViewer()
129 StringBuilder cmd = new StringBuilder(128);
131 for (String lbl : chainsToHide)
137 mlength = lbl.indexOf(":", p);
138 } while (p < mlength && mlength < (lbl.length() - 2));
139 // TODO: lookup each pdb id and recover proper model number for it.
140 cmd.append(":" + lbl.substring(mlength + 1) + " /"
141 + (getModelIdForFile(getFileForChain(lbl))) + " or ");
143 if (cmd.length() > 0)
145 cmd.setLength(cmd.length() - 4);
147 // todo: revised command is untested - but this method is obsolete anyway
148 String command = "select *;hide " + cmd + ";cartoon;center " + cmd;
149 executeCommand(new StructureCommand(command), false);
152 private String jmolScript(String script)
154 Cache.log.debug(">>Jmol>> " + script);
155 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
156 Cache.log.debug("<<Jmol<< " + s);
161 public List<String> executeCommand(StructureCommandI command,
168 String cmd = command.getCommand();
170 if (lastCommand == null || !lastCommand.equals(cmd))
172 jmolScript(cmd + "\n");
179 public void createImage(String file, String type, int quality)
181 System.out.println("JMOL CREATE IMAGE");
185 public String createImage(String fileName, String type,
186 Object textOrBytes, int quality)
188 System.out.println("JMOL CREATE IMAGE");
193 public String eval(String strEval)
195 // System.out.println(strEval);
196 // "# 'eval' is implemented only for the applet.";
200 // End StructureListener
201 // //////////////////////////
204 public float[][] functionXY(String functionName, int x, int y)
210 public float[][][] functionXYZ(String functionName, int nx, int ny,
213 // TODO Auto-generated method stub
218 * map between index of model filename returned from getPdbFile and the first
219 * index of models from this file in the viewer. Note - this is not trimmed -
220 * use getPdbFile to get number of unique models.
222 private int _modelFileNameMap[];
225 public synchronized String[] getStructureFiles()
227 if (modelFileNames != null)
229 return modelFileNames;
231 if (jmolViewer == null)
233 return new String[0];
236 List<String> mset = new ArrayList<>();
237 int modelCount = jmolViewer.ms.mc;
238 String filePath = null;
239 for (int i = 0; i < modelCount; ++i)
242 * defensive check for null as getModelFileName can return null
243 * even when model count ms.mc is > 0
245 filePath = jmolViewer.ms.getModelFileName(i);
246 if (filePath != null && !mset.contains(filePath))
254 modelFileNames = mset.toArray(new String[mset.size()]);
257 return modelFileNames;
261 * map from string to applet
264 public Map<String, Object> getRegistryInfo()
266 // TODO Auto-generated method stub
270 // ///////////////////////////////
271 // JmolStatusListener
273 public void handlePopupMenu(int x, int y)
275 // jmolpopup.show(x, y);
276 // jmolpopup.jpiShow(x, y);
280 * Highlight zero, one or more atoms on the structure
283 public void highlightAtoms(List<AtomSpec> atoms)
287 if (resetLastRes.length() > 0)
289 jmolScript(resetLastRes.toString());
290 resetLastRes.setLength(0);
292 for (AtomSpec atom : atoms)
294 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
295 atom.getChain(), atom.getPdbFile());
301 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
304 String modelId = getModelIdForFile(pdbfile);
305 if (modelId.isEmpty())
312 StringBuilder selection = new StringBuilder(32);
313 StringBuilder cmd = new StringBuilder(64);
314 selection.append("select ").append(String.valueOf(pdbResNum));
315 selection.append(":");
316 if (!chain.equals(" "))
318 selection.append(chain);
320 selection.append(" /").append(modelId);
322 cmd.append(selection).append(";wireframe 100;").append(selection)
323 .append(" and not hetero;").append("spacefill 200;select none");
325 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
326 .append(" and not hetero; spacefill 0;");
328 jmolScript(cmd.toString());
332 private boolean debug = true;
334 private void jmolHistory(boolean enable)
336 jmolScript("History " + ((debug || enable) ? "on" : "off"));
339 public void loadInline(String string)
343 // viewer.loadInline(strModel, isAppend);
345 // construct fake fullPathName and fileName so we can identify the file
347 // Then, construct pass a reader for the string to Jmol.
348 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
349 // fileName, null, reader, false, null, null, 0);
350 jmolViewer.openStringInline(string);
353 protected void mouseOverStructure(int atomIndex, final String strInfo)
356 int alocsep = strInfo.indexOf("^");
357 int mdlSep = strInfo.indexOf("/");
358 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
360 if (chainSeparator == -1)
362 chainSeparator = strInfo.indexOf(".");
363 if (mdlSep > -1 && mdlSep < chainSeparator)
365 chainSeparator1 = chainSeparator;
366 chainSeparator = mdlSep;
369 // handle insertion codes
372 pdbResNum = Integer.parseInt(
373 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
378 pdbResNum = Integer.parseInt(
379 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
383 if (strInfo.indexOf(":") > -1)
385 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
386 strInfo.indexOf("."));
393 String pdbfilename = modelFileNames[0]; // default is first model
396 if (chainSeparator1 == -1)
398 chainSeparator1 = strInfo.indexOf(".", mdlSep);
400 String mdlId = (chainSeparator1 > -1)
401 ? strInfo.substring(mdlSep + 1, chainSeparator1)
402 : strInfo.substring(mdlSep + 1);
405 // recover PDB filename for the model hovered over.
406 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
407 if (_modelFileNameMap != null)
409 int _mp = _modelFileNameMap.length - 1;
411 while (mnumber < _modelFileNameMap[_mp])
415 pdbfilename = modelFileNames[_mp];
419 if (mnumber >= 0 && mnumber < modelFileNames.length)
421 pdbfilename = modelFileNames[mnumber];
424 if (pdbfilename == null)
426 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
430 } catch (Exception e)
436 * highlight position on alignment(s); if some text is returned,
437 * show this as a second line on the structure hover tooltip
439 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
443 // change comma to pipe separator (newline token for Jmol)
444 label = label.replace(',', '|');
445 StringTokenizer toks = new StringTokenizer(strInfo, " ");
446 StringBuilder sb = new StringBuilder();
447 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
448 .append(chainId).append("/1");
449 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
450 .append(toks.nextToken());
451 sb.append("|").append(label).append("\"");
452 executeCommand(new StructureCommand(sb.toString()), false);
456 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
458 if (strInfo.equals(lastMessage))
462 lastMessage = strInfo;
465 System.err.println("Ignoring additional hover info: " + data
466 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
468 mouseOverStructure(atomIndex, strInfo);
472 * { if (history != null && strStatus != null &&
473 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
477 public void notifyAtomPicked(int atomIndex, String strInfo,
481 * this implements the toggle label behaviour copied from the original
482 * structure viewer, mc_view
486 System.err.println("Ignoring additional pick data string " + strData);
488 int chainSeparator = strInfo.indexOf(":");
490 if (chainSeparator == -1)
492 chainSeparator = strInfo.indexOf(".");
495 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
497 String mdlString = "";
498 if ((p = strInfo.indexOf(":")) > -1)
500 picked += strInfo.substring(p, strInfo.indexOf("."));
503 if ((p = strInfo.indexOf("/")) > -1)
505 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
507 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
511 if (!atomsPicked.contains(picked))
513 jmolScript("select " + picked + ";label %n %r:%c");
514 atomsPicked.addElement(picked);
518 jmolViewer.evalString("select " + picked + ";label off");
519 atomsPicked.removeElement(picked);
522 // TODO: in application this happens
524 // if (scriptWindow != null)
526 // scriptWindow.sendConsoleMessage(strInfo);
527 // scriptWindow.sendConsoleMessage("\n");
533 public void notifyCallback(CBK type, Object[] data)
540 notifyFileLoaded((String) data[1], (String) data[2],
541 (String) data[3], (String) data[4],
542 ((Integer) data[5]).intValue());
546 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
548 // also highlight in alignment
549 // deliberate fall through
551 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
555 notifyScriptTermination((String) data[2],
556 ((Integer) data[3]).intValue());
559 sendConsoleEcho((String) data[1]);
563 (data == null) ? ((String) null) : (String) data[1]);
566 // System.err.println("Ignoring error callback.");
577 "Unhandled callback " + type + " " + data[1].toString());
580 } catch (Exception e)
582 System.err.println("Squashed Jmol callback handler error:");
588 public boolean notifyEnabled(CBK callbackPick)
590 switch (callbackPick)
606 // incremented every time a load notification is successfully handled -
607 // lightweight mechanism for other threads to detect when they can start
608 // referrring to new structures.
609 private long loadNotifiesHandled = 0;
611 public long getLoadNotifiesHandled()
613 return loadNotifiesHandled;
616 public void notifyFileLoaded(String fullPathName, String fileName2,
617 String modelName, String errorMsg, int modelParts)
619 if (errorMsg != null)
621 fileLoadingError = errorMsg;
625 // TODO: deal sensibly with models loaded inLine:
626 // modelName will be null, as will fullPathName.
628 // the rest of this routine ignores the arguments, and simply interrogates
629 // the Jmol view to find out what structures it contains, and adds them to
630 // the structure selection manager.
631 fileLoadingError = null;
632 String[] oldmodels = modelFileNames;
633 modelFileNames = null;
634 boolean notifyLoaded = false;
635 String[] modelfilenames = getStructureFiles();
636 if (modelfilenames == null)
638 // Jmol is still loading files!
641 // first check if we've lost any structures
642 if (oldmodels != null && oldmodels.length > 0)
645 for (int i = 0; i < oldmodels.length; i++)
647 for (int n = 0; n < modelfilenames.length; n++)
649 if (modelfilenames[n] == oldmodels[i])
655 if (oldmodels[i] != null)
662 String[] oldmfn = new String[oldm];
664 for (int i = 0; i < oldmodels.length; i++)
666 if (oldmodels[i] != null)
668 oldmfn[oldm++] = oldmodels[i];
671 // deregister the Jmol instance for these structures - we'll add
672 // ourselves again at the end for the current structure set.
673 getSsm().removeStructureViewerListener(this, oldmfn);
677 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
679 String fileName = modelfilenames[modelnum];
680 boolean foundEntry = false;
681 StructureFile pdb = null;
682 String pdbfile = null;
683 // model was probably loaded inline - so check the pdb file hashcode
686 // calculate essential attributes for the pdb data imported inline.
687 // prolly need to resolve modelnumber properly - for now just use our
689 pdbfile = jmolViewer.getData(
690 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
692 // search pdbentries and sequences to find correct pdbentry for this
694 for (int pe = 0; pe < getPdbCount(); pe++)
696 boolean matches = false;
697 addSequence(pe, getSequence()[pe]);
698 if (fileName == null)
701 // see JAL-623 - need method of matching pasted data up
703 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
704 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
705 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
712 File fl = new File(getPdbEntry(pe).getFile());
713 matches = fl.equals(new File(fileName));
717 // TODO: Jmol can in principle retrieve from CLASSLOADER but
720 // to be tested. See mantis bug
721 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
722 DataSourceType protocol = DataSourceType.URL;
727 protocol = DataSourceType.FILE;
729 } catch (Exception e)
734 // Explicitly map to the filename used by Jmol ;
735 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
736 fileName, protocol, getIProgressIndicator());
737 // pdbentry[pe].getFile(), protocol);
743 stashFoundChains(pdb, fileName);
748 if (!foundEntry && associateNewStructs)
750 // this is a foreign pdb file that jalview doesn't know about - add
751 // it to the dataset and try to find a home - either on a matching
752 // sequence or as a new sequence.
753 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
755 // parse pdb file into a chain, etc.
756 // locate best match for pdb in associated views and add mapping to
758 // if properly registered then
764 // so finally, update the jmol bits and pieces
765 // if (jmolpopup != null)
767 // // potential for deadlock here:
768 // // jmolpopup.updateComputedMenus();
770 if (!isLoadingFromArchive())
773 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
775 // register ourselves as a listener and notify the gui that it needs to
777 getSsm().addStructureViewerListener(this);
780 FeatureRenderer fr = getFeatureRenderer(null);
786 loadNotifiesHandled++;
788 setLoadingFromArchive(false);
791 protected IProgressIndicator getIProgressIndicator()
796 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
798 notifyAtomPicked(iatom, strMeasure, null);
801 public abstract void notifyScriptTermination(String strStatus,
805 * display a message echoed from the jmol viewer
809 public abstract void sendConsoleEcho(String strEcho); /*
810 * { showConsole(true);
812 * history.append("\n" +
816 // /End JmolStatusListener
817 // /////////////////////////////
821 * status message - usually the response received after a script
824 public abstract void sendConsoleMessage(String strStatus);
827 public void setCallbackFunction(String callbackType,
828 String callbackFunction)
830 System.err.println("Ignoring set-callback request to associate "
831 + callbackType + " with function " + callbackFunction);
835 public void showHelp()
837 showUrl("http://wiki.jmol.org"
838 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
843 * open the URL somehow
847 public abstract void showUrl(String url, String target);
850 * called to show or hide the associated console window container.
854 public abstract void showConsole(boolean show);
856 public static Viewer getJmolData(JmolParser jmolParser)
858 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
859 "-x -o -n", jmolParser);
868 * - when true will initialise jmol's file IO system (should be false
871 * @param documentBase
873 * @param commandOptions
875 public void allocateViewer(Container renderPanel, boolean jmolfileio,
876 String htmlName, URL documentBase, URL codeBase,
877 String commandOptions)
879 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
880 codeBase, commandOptions, null, null);
887 * - when true will initialise jmol's file IO system (should be false
890 * @param documentBase
892 * @param commandOptions
893 * @param consolePanel
894 * - panel to contain Jmol console
895 * @param buttonsToShow
896 * - buttons to show on the console, in order
898 public void allocateViewer(Container renderPanel, boolean jmolfileio,
899 String htmlName, URL documentBase, URL codeBase,
900 String commandOptions, final Container consolePanel,
901 String buttonsToShow)
904 System.err.println("Allocating Jmol Viewer: " + commandOptions);
906 if (commandOptions == null)
910 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
911 (jmolfileio ? new SmarterJmolAdapter() : null),
912 htmlName + ((Object) this).toString(), documentBase, codeBase,
913 commandOptions, this);
915 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
919 console = createJmolConsole(consolePanel, buttonsToShow);
920 } catch (Throwable e)
922 System.err.println("Could not create Jmol application console. "
926 if (consolePanel != null)
928 consolePanel.addComponentListener(this);
934 protected abstract JmolAppConsoleInterface createJmolConsole(
935 Container consolePanel, String buttonsToShow);
937 // BH 2018 -- Jmol console is not working due to problems with styled
940 protected org.jmol.api.JmolAppConsoleInterface console = null;
943 public int[] resizeInnerPanel(String data)
945 // Jalview doesn't honour resize panel requests
952 protected void closeConsole()
958 console.setVisible(false);
961 } catch (Exception x)
970 * ComponentListener method
973 public void componentMoved(ComponentEvent e)
978 * ComponentListener method
981 public void componentResized(ComponentEvent e)
986 * ComponentListener method
989 public void componentShown(ComponentEvent e)
995 * ComponentListener method
998 public void componentHidden(ComponentEvent e)
1004 public void showStructures(AlignViewportI av, boolean refocus)
1006 String cmd = buildShowStructuresCommand(av, refocus);
1007 executeCommand(new StructureCommand(cmd), false);
1011 * Builds a command to show parts of the structure, depending on whether
1013 * <li>all structures or regions mapped to alignment only are shown</li>
1014 * <li>all chains or only selected chains are shown</li>
1021 protected String buildShowStructuresCommand(AlignViewportI av,
1024 StringBuilder cmd = new StringBuilder(128);
1025 if (!isShowAlignmentOnly())
1027 cmd.append("display *");
1031 AtomSpecModel model = getShownResidues(av);
1032 String atomSpec = getCommandGenerator().getAtomSpec(model, false);
1034 cmd.append("hide *;display ").append(atomSpec)
1035 .append("; select displayed");
1039 * hide any chains not selected to be shown
1041 if (!chainsToHide.isEmpty())
1043 cmd.append("; hide add ");
1044 boolean firstHide = true;
1045 for (String pdbChain : chainsToHide)
1047 String[] toks = pdbChain.split(":");
1048 String chainId = toks[1];
1049 String modelNo = getModelIdForFile(getFileForChain(pdbChain));
1050 if ("".equals(modelNo))
1059 cmd.append(":").append(chainId).append("/")
1060 .append(String.valueOf(modelNo)).append(".1");
1064 cmd.append("; cartoon only");
1067 cmd.append("; zoom 0");
1069 return cmd.toString();
1073 * Answers a Jmol syntax style structure model specification. Model number 0, 1,
1074 * 2... is formatted as "1.1", "2.1", "3.1" etc.
1077 public String getModelSpec(int model)
1079 return String.valueOf(model + 1) + ".1";
1083 protected String getModelIdForFile(String pdbFile)
1085 if (modelFileNames == null)
1089 for (int i = 0; i < modelFileNames.length; i++)
1091 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1093 return String.valueOf(i + 1);
1100 protected ViewerType getViewerType()
1102 return ViewerType.JMOL;
1106 protected String getModelId(int pdbfnum, String file)
1108 return String.valueOf(pdbfnum + 1);
1112 * Returns ".spt" - the Jmol session file extension
1115 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1118 public String getSessionFileExtension()
1124 public void selectionChanged(BS arg0)
1126 // TODO Auto-generated method stub
1131 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1133 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1137 public String getHelpURL()
1139 return "http://wiki.jmol.org"; // BH 2018