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.Arrays;
30 import java.util.HashMap;
31 import java.util.List;
33 import java.util.StringTokenizer;
34 import java.util.Vector;
36 import javax.swing.SwingUtilities;
38 import org.jmol.adapter.smarter.SmarterJmolAdapter;
39 import org.jmol.api.JmolAppConsoleInterface;
40 import org.jmol.api.JmolSelectionListener;
41 import org.jmol.api.JmolStatusListener;
42 import org.jmol.api.JmolViewer;
43 import org.jmol.c.CBK;
44 import org.jmol.viewer.Viewer;
46 import jalview.api.AlignmentViewPanel;
47 import jalview.api.FeatureRenderer;
48 import jalview.api.FeatureSettingsModelI;
49 import jalview.api.SequenceRenderer;
50 import jalview.bin.Console;
51 import jalview.datamodel.PDBEntry;
52 import jalview.datamodel.SequenceI;
53 import jalview.gui.AppJmol;
54 import jalview.gui.IProgressIndicator;
55 import jalview.gui.StructureViewer.ViewerType;
56 import jalview.io.DataSourceType;
57 import jalview.io.StructureFile;
58 import jalview.structure.AtomSpec;
59 import jalview.structure.StructureCommand;
60 import jalview.structure.StructureCommandI;
61 import jalview.structure.StructureSelectionManager;
62 import jalview.structures.models.AAStructureBindingModel;
63 import jalview.ws.dbsources.Pdb;
64 import javajs.util.BS;
66 public abstract class JalviewJmolBinding extends AAStructureBindingModel
67 implements JmolStatusListener, JmolSelectionListener,
70 private String lastMessage;
73 * when true, try to search the associated datamodel for sequences that are
74 * associated with any unknown structures in the Jmol view.
76 private boolean associateNewStructs = false;
78 private Vector<String> atomsPicked = new Vector<>();
80 private String lastCommand;
82 private boolean loadedInline;
84 private StringBuffer resetLastRes = new StringBuffer();
86 public Viewer jmolViewer;
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);
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", command.isWaitNeeded());
166 public void createImage(String file, String type, int quality)
168 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
172 public String createImage(String fileName, String type,
173 Object textOrBytes, int quality)
175 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
180 public String eval(String strEval)
182 // jalview.bin.Console.outPrintln(strEval);
183 // "# 'eval' is implemented only for the applet.";
187 // End StructureListener
188 // //////////////////////////
190 ////////////////////////////
195 public Map<String, String> getHetatmNames()
197 HashMap<String, String> hetlist = new HashMap();
198 for (int mc = 0; mc < jmolViewer.ms.mc; mc++)
200 Map<String, String> hets = jmolViewer.ms.getHeteroList(mc);
203 hetlist.putAll(hets);
209 ////////////////////////////
212 public float[][] functionXY(String functionName, int x, int y)
218 public float[][][] functionXYZ(String functionName, int nx, int ny,
221 // TODO Auto-generated method stub
226 * map between index of model filename returned from getPdbFile and the first
227 * index of models from this file in the viewer. Note - this is not trimmed -
228 * use getPdbFile to get number of unique models.
230 private int _modelFileNameMap[];
233 public synchronized String[] getStructureFiles()
235 if (jmolViewer == null)
237 return new String[0];
240 if (modelFileNames == null)
242 int modelCount = jmolViewer.ms.mc;
243 String filePath = null;
244 List<String> mset = new ArrayList<>();
245 for (int i = 0; i < modelCount; ++i)
248 * defensive check for null as getModelFileName can return null even when model
251 filePath = jmolViewer.ms.getModelFileName(i);
252 if (filePath != null && !mset.contains(filePath))
259 modelFileNames = mset.toArray(new String[mset.size()]);
263 return modelFileNames;
267 * map from string to applet
270 public Map<String, Object> getRegistryInfo()
272 // TODO Auto-generated method stub
276 // ///////////////////////////////
277 // JmolStatusListener
279 public void handlePopupMenu(int x, int y)
281 // jmolpopup.show(x, y);
282 // jmolpopup.jpiShow(x, y);
286 * Highlight zero, one or more atoms on the structure
289 public void highlightAtoms(List<AtomSpec> atoms)
293 if (resetLastRes.length() > 0)
295 jmolScript(resetLastRes.toString());
296 resetLastRes.setLength(0);
298 StringBuilder highlightCommands = null;
299 for (AtomSpec atom : atoms)
301 StringBuilder thisAtom = highlightAtom(atom.getAtomIndex(),
302 atom.getPdbResNum(), atom.getChain(), atom.getPdbFile());
303 if (thisAtom != null)
305 if (highlightCommands == null)
307 highlightCommands = thisAtom;
311 highlightCommands.append(thisAtom);
315 if (highlightCommands != null)
318 jmolScript(highlightCommands.toString());
321 // Highlight distances between atoms with a 'measure' command - not yet
323 // if (atoms.size() >= 2)
325 // StringBuilder sb = new StringBuilder();
326 // for (int a = 0; a < atoms.size(); a++)
328 // AtomSpec speca = atoms.get(a);
329 // String a_model = getModelIdForFile(speca.getPdbFile());
330 // for (int b = a + 1; b < atoms.size(); b++)
332 // AtomSpec specb = atoms.get(b);
333 // String b_model = getModelIdForFile(speca.getPdbFile());
334 // sb.append("measure ALL (" + speca.getAtomIndex() + " and */"
335 // + a_model + ") (" + specb.getAtomIndex() + " and */"
336 // + b_model + ");");
339 // jmolHistory(false, useScriptWait);
340 // jmolScript(sb.toString(), useScriptWait);
341 // jmolHistory(true, useScriptWait);
349 private StringBuilder highlightAtom(int atomIndex, int pdbResNum,
350 String chain, String pdbfile)
352 String modelId = getModelIdForFile(pdbfile);
353 if (modelId.isEmpty())
358 StringBuilder selection = new StringBuilder(32);
359 StringBuilder cmd = new StringBuilder(64);
360 selection.append("select ").append(String.valueOf(pdbResNum));
361 selection.append(":");
362 if (!chain.equals(" "))
364 selection.append(chain);
366 selection.append(" /").append(modelId);
368 cmd.append(selection).append(";wireframe 100;").append(selection)
369 .append(" and not hetero;").append("spacefill 200;select none");
371 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
372 .append(" and not hetero; spacefill 0;");
377 private boolean debug = true;
379 private void jmolHistory(boolean enable)
381 jmolHistory(enable, false);
384 private void jmolHistory(boolean enable, boolean useScriptWait)
386 jmolScript("History " + ((debug || enable) ? "on" : "off"),
390 public void loadInline(String string)
394 // viewer.loadInline(strModel, isAppend);
396 // construct fake fullPathName and fileName so we can identify the file
398 // Then, construct pass a reader for the string to Jmol.
399 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
400 // fileName, null, reader, false, null, null, 0);
401 jmolViewer.openStringInline(string);
404 protected void mouseOverStructure(int atomIndex, final String strInfo)
407 int alocsep = strInfo.indexOf("^");
408 int mdlSep = strInfo.indexOf("/");
409 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
411 if (chainSeparator == -1)
413 chainSeparator = strInfo.indexOf(".");
414 if (mdlSep > -1 && mdlSep < chainSeparator)
416 chainSeparator1 = chainSeparator;
417 chainSeparator = mdlSep;
420 // handle insertion codes
423 pdbResNum = Integer.parseInt(
424 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
429 pdbResNum = Integer.parseInt(
430 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
434 if (strInfo.indexOf(":") > -1)
436 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
437 strInfo.indexOf("."));
444 String pdbfilename = modelFileNames[0]; // default is first model
447 if (chainSeparator1 == -1)
449 chainSeparator1 = strInfo.indexOf(".", mdlSep);
451 String mdlId = (chainSeparator1 > -1)
452 ? strInfo.substring(mdlSep + 1, chainSeparator1)
453 : strInfo.substring(mdlSep + 1);
456 // recover PDB filename for the model hovered over.
457 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
458 if (_modelFileNameMap != null)
460 int _mp = _modelFileNameMap.length - 1;
462 while (mnumber < _modelFileNameMap[_mp])
466 pdbfilename = modelFileNames[_mp];
470 if (mnumber >= 0 && mnumber < modelFileNames.length)
472 pdbfilename = modelFileNames[mnumber];
475 if (pdbfilename == null)
477 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
481 } catch (Exception e)
487 * highlight position on alignment(s); if some text is returned, show this as a
488 * second line on the structure hover tooltip
490 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
494 // change comma to pipe separator (newline token for Jmol)
495 label = label.replace(',', '|');
496 StringTokenizer toks = new StringTokenizer(strInfo, " ");
497 StringBuilder sb = new StringBuilder();
498 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
499 .append(chainId).append("/1");
500 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
501 .append(toks.nextToken());
502 sb.append("|").append(label).append("\"");
503 executeCommand(new StructureCommand(sb.toString()), false);
507 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
509 if (strInfo.equals(lastMessage))
513 lastMessage = strInfo;
516 jalview.bin.Console.errPrintln(
517 "Ignoring additional hover info: " + data + " (other info: '"
518 + strInfo + "' pos " + atomIndex + ")");
520 mouseOverStructure(atomIndex, strInfo);
524 * { if (history != null && strStatus != null &&
525 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
529 public void notifyAtomPicked(int atomIndex, String strInfo,
533 * this implements the toggle label behaviour copied from the original
534 * structure viewer, mc_view
538 jalview.bin.Console.errPrintln(
539 "Ignoring additional pick data string " + strData);
541 int chainSeparator = strInfo.indexOf(":");
543 if (chainSeparator == -1)
545 chainSeparator = strInfo.indexOf(".");
548 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
550 String mdlString = "";
551 if ((p = strInfo.indexOf(":")) > -1)
553 picked += strInfo.substring(p, strInfo.indexOf("."));
556 if ((p = strInfo.indexOf("/")) > -1)
558 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
560 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
564 if (!atomsPicked.contains(picked))
566 jmolScript("select " + picked + ";label %n %r:%c");
567 atomsPicked.addElement(picked);
571 jmolViewer.evalString("select " + picked + ";label off");
572 atomsPicked.removeElement(picked);
575 // TODO: in application this happens
577 // if (scriptWindow != null)
579 // scriptWindow.sendConsoleMessage(strInfo);
580 // scriptWindow.sendConsoleMessage("\n");
586 public void notifyCallback(CBK type, Object[] data)
589 * ensure processed in AWT thread to avoid risk of deadlocks
591 SwingUtilities.invokeLater(new Runnable()
597 processCallback(type, data);
603 * Processes one callback notification from Jmol
608 protected void processCallback(CBK type, Object[] data)
615 notifyFileLoaded((String) data[1], (String) data[2],
616 (String) data[3], (String) data[4],
617 ((Integer) data[5]).intValue());
621 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
623 // also highlight in alignment
624 // deliberate fall through
626 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
630 notifyScriptTermination((String) data[2],
631 ((Integer) data[3]).intValue());
634 sendConsoleEcho((String) data[1]);
638 (data == null) ? ((String) null) : (String) data[1]);
641 // jalview.bin.Console.errPrintln("Ignoring error callback.");
651 jalview.bin.Console.errPrintln(
652 "Unhandled callback " + type + " " + data[1].toString());
655 } catch (Exception e)
658 .errPrintln("Squashed Jmol callback handler error:");
664 public boolean notifyEnabled(CBK callbackPick)
666 switch (callbackPick)
682 // incremented every time a load notification is successfully handled -
683 // lightweight mechanism for other threads to detect when they can start
684 // referrring to new structures.
685 private long loadNotifiesHandled = 0;
687 public long getLoadNotifiesHandled()
689 return loadNotifiesHandled;
692 public void notifyFileLoaded(String fullPathName, String fileName2,
693 String modelName, String errorMsg, int modelParts)
695 if (errorMsg != null)
697 fileLoadingError = errorMsg;
701 // TODO: deal sensibly with models loaded inLine:
702 // modelName will be null, as will fullPathName.
704 // the rest of this routine ignores the arguments, and simply interrogates
705 // the Jmol view to find out what structures it contains, and adds them to
706 // the structure selection manager.
707 fileLoadingError = null;
708 String[] oldmodels = modelFileNames;
709 modelFileNames = null;
710 boolean notifyLoaded = false;
711 String[] modelfilenames = getStructureFiles();
712 if (modelfilenames == null)
714 // Jmol is still loading files!
717 // first check if we've lost any structures
718 if (oldmodels != null && oldmodels.length > 0)
721 for (int i = 0; i < oldmodels.length; i++)
723 for (int n = 0; n < modelfilenames.length; n++)
725 if (modelfilenames[n] == oldmodels[i])
731 if (oldmodels[i] != null)
738 String[] oldmfn = new String[oldm];
740 for (int i = 0; i < oldmodels.length; i++)
742 if (oldmodels[i] != null)
744 oldmfn[oldm++] = oldmodels[i];
747 // deregister the Jmol instance for these structures - we'll add
748 // ourselves again at the end for the current structure set.
749 getSsm().removeStructureViewerListener(this, oldmfn);
753 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
755 String fileName = modelfilenames[modelnum];
756 boolean foundEntry = false;
757 StructureFile pdb = null;
758 String pdbfile = null;
759 // model was probably loaded inline - so check the pdb file hashcode
762 // calculate essential attributes for the pdb data imported inline.
763 // prolly need to resolve modelnumber properly - for now just use our
765 pdbfile = jmolViewer.getData(
766 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
768 // search pdbentries and sequences to find correct pdbentry for this
770 for (int pe = 0; pe < getPdbCount(); pe++)
772 boolean matches = false;
773 addSequence(pe, getSequence()[pe]);
774 if (fileName == null)
777 // see JAL-623 - need method of matching pasted data up
779 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
780 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
781 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
788 File fl = new File(getPdbEntry(pe).getFile());
789 matches = fl.equals(new File(fileName));
793 // TODO: Jmol can in principle retrieve from CLASSLOADER but
796 // to be tested. See mantis bug
797 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
798 DataSourceType protocol = DataSourceType.URL;
803 protocol = DataSourceType.FILE;
805 } catch (Exception e)
810 // Explicitly map to the filename used by Jmol ;
811 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
812 fileName, protocol, getIProgressIndicator());
813 // pdbentry[pe].getFile(), protocol);
819 stashFoundChains(pdb, fileName);
824 if (!foundEntry && associateNewStructs)
826 // this is a foreign pdb file that jalview doesn't know about - add
827 // it to the dataset and try to find a home - either on a matching
828 // sequence or as a new sequence.
829 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
831 // parse pdb file into a chain, etc.
832 // locate best match for pdb in associated views and add mapping to
834 // if properly registered then
840 // so finally, update the jmol bits and pieces
841 // if (jmolpopup != null)
843 // // potential for deadlock here:
844 // // jmolpopup.updateComputedMenus();
846 if (!isLoadingFromArchive())
849 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
851 // register ourselves as a listener and notify the gui that it needs to
853 getSsm().addStructureViewerListener(this);
856 FeatureRenderer fr = getFeatureRenderer(null);
859 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
860 ((AppJmol) getViewer()).getAlignmentPanel().av
861 .applyFeaturesStyle(colours);
864 loadNotifiesHandled++;
866 setLoadingFromArchive(false);
869 protected IProgressIndicator getIProgressIndicator()
874 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
876 notifyAtomPicked(iatom, strMeasure, null);
879 public abstract void notifyScriptTermination(String strStatus,
883 * display a message echoed from the jmol viewer
887 public abstract void sendConsoleEcho(String strEcho); /*
888 * { showConsole(true);
890 * history.append("\n" + strEcho); }
893 // /End JmolStatusListener
894 // /////////////////////////////
898 * status message - usually the response received after a script
901 public abstract void sendConsoleMessage(String strStatus);
904 public void setCallbackFunction(String callbackType,
905 String callbackFunction)
908 .errPrintln("Ignoring set-callback request to associate "
909 + callbackType + " with function " + callbackFunction);
913 public void showHelp()
915 showUrl("http://wiki.jmol.org"
916 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
921 * open the URL somehow
925 public abstract void showUrl(String url, String target);
928 * called to show or hide the associated console window container.
932 public abstract void showConsole(boolean show);
934 public static Viewer getJmolData(JmolParser jmolParser)
936 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
937 "-x -o -n", jmolParser);
946 * - when true will initialise jmol's file IO system (should be false
949 * @param documentBase
951 * @param commandOptions
953 public void allocateViewer(Container renderPanel, boolean jmolfileio,
954 String htmlName, URL documentBase, URL codeBase,
955 String commandOptions)
957 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
958 codeBase, commandOptions, null, null);
965 * - when true will initialise jmol's file IO system (should be false
968 * @param documentBase
970 * @param commandOptions
971 * @param consolePanel
972 * - panel to contain Jmol console
973 * @param buttonsToShow
974 * - buttons to show on the console, in order
976 public void allocateViewer(Container renderPanel, boolean jmolfileio,
977 String htmlName, URL documentBase, URL codeBase,
978 String commandOptions, final Container consolePanel,
979 String buttonsToShow)
983 .errPrintln("Allocating Jmol Viewer: " + commandOptions);
985 if (commandOptions == null)
989 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
990 (jmolfileio ? new SmarterJmolAdapter() : null),
991 htmlName + ((Object) this).toString(), documentBase, codeBase,
992 commandOptions, this);
994 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
998 console = createJmolConsole(consolePanel, buttonsToShow);
999 } catch (Throwable e)
1002 .errPrintln("Could not create Jmol application console. "
1004 e.printStackTrace();
1006 if (consolePanel != null)
1008 consolePanel.addComponentListener(this);
1014 protected abstract JmolAppConsoleInterface createJmolConsole(
1015 Container consolePanel, String buttonsToShow);
1017 // BH 2018 -- Jmol console is not working due to problems with styled
1020 protected org.jmol.api.JmolAppConsoleInterface console = null;
1023 public int[] resizeInnerPanel(String data)
1025 // Jalview doesn't honour resize panel requests
1032 protected void closeConsole()
1034 if (console != null)
1038 console.setVisible(false);
1041 } catch (Exception x)
1050 * ComponentListener method
1053 public void componentMoved(ComponentEvent e)
1058 * ComponentListener method
1061 public void componentResized(ComponentEvent e)
1066 * ComponentListener method
1069 public void componentShown(ComponentEvent e)
1075 * ComponentListener method
1078 public void componentHidden(ComponentEvent e)
1084 protected String getModelIdForFile(String pdbFile)
1086 if (modelFileNames == null)
1090 for (int i = 0; i < modelFileNames.length; i++)
1092 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1094 return String.valueOf(i + 1);
1101 protected ViewerType getViewerType()
1103 return ViewerType.JMOL;
1107 protected String getModelId(int pdbfnum, String file)
1109 return String.valueOf(pdbfnum + 1);
1113 * Returns ".spt" - the Jmol session file extension
1116 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1119 public String getSessionFileExtension()
1125 public void selectionChanged(BS arg0)
1127 // TODO Auto-generated method stub
1132 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1134 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1138 public String getHelpURL()
1140 return "http://wiki.jmol.org"; // BH 2018