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);
285 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
286 String pdbfile, boolean useScriptWait)
288 String modelId = getModelIdForFile(pdbfile);
289 if (modelId.isEmpty())
294 jmolHistory(false, useScriptWait);
296 StringBuilder selection = new StringBuilder(32);
297 StringBuilder cmd = new StringBuilder(64);
298 selection.append("select ").append(String.valueOf(pdbResNum));
299 selection.append(":");
300 if (!chain.equals(" "))
302 selection.append(chain);
304 selection.append(" /").append(modelId);
306 cmd.append(selection).append(";wireframe 100;").append(selection)
307 .append(" and not hetero;").append("spacefill 200;select none");
309 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
310 .append(" and not hetero; spacefill 0;");
312 jmolScript(cmd.toString(), useScriptWait);
313 jmolHistory(true, useScriptWait);
316 private boolean debug = true;
318 private void jmolHistory(boolean enable)
320 jmolHistory(enable, false);
323 private void jmolHistory(boolean enable, boolean useScriptWait)
325 jmolScript("History " + ((debug || enable) ? "on" : "off"),
329 public void loadInline(String string)
333 // viewer.loadInline(strModel, isAppend);
335 // construct fake fullPathName and fileName so we can identify the file
337 // Then, construct pass a reader for the string to Jmol.
338 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
339 // fileName, null, reader, false, null, null, 0);
340 jmolViewer.openStringInline(string);
343 protected void mouseOverStructure(int atomIndex, final String strInfo)
346 int alocsep = strInfo.indexOf("^");
347 int mdlSep = strInfo.indexOf("/");
348 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
350 if (chainSeparator == -1)
352 chainSeparator = strInfo.indexOf(".");
353 if (mdlSep > -1 && mdlSep < chainSeparator)
355 chainSeparator1 = chainSeparator;
356 chainSeparator = mdlSep;
359 // handle insertion codes
362 pdbResNum = Integer.parseInt(
363 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
368 pdbResNum = Integer.parseInt(
369 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
373 if (strInfo.indexOf(":") > -1)
375 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
376 strInfo.indexOf("."));
383 String pdbfilename = modelFileNames[0]; // default is first model
386 if (chainSeparator1 == -1)
388 chainSeparator1 = strInfo.indexOf(".", mdlSep);
390 String mdlId = (chainSeparator1 > -1)
391 ? strInfo.substring(mdlSep + 1, chainSeparator1)
392 : strInfo.substring(mdlSep + 1);
395 // recover PDB filename for the model hovered over.
396 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
397 if (_modelFileNameMap != null)
399 int _mp = _modelFileNameMap.length - 1;
401 while (mnumber < _modelFileNameMap[_mp])
405 pdbfilename = modelFileNames[_mp];
409 if (mnumber >= 0 && mnumber < modelFileNames.length)
411 pdbfilename = modelFileNames[mnumber];
414 if (pdbfilename == null)
416 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
420 } catch (Exception e)
426 * highlight position on alignment(s); if some text is returned, show this as a
427 * second line on the structure hover tooltip
429 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
433 // change comma to pipe separator (newline token for Jmol)
434 label = label.replace(',', '|');
435 StringTokenizer toks = new StringTokenizer(strInfo, " ");
436 StringBuilder sb = new StringBuilder();
437 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
438 .append(chainId).append("/1");
439 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
440 .append(toks.nextToken());
441 sb.append("|").append(label).append("\"");
442 executeCommand(new StructureCommand(sb.toString()), false);
446 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
448 if (strInfo.equals(lastMessage))
452 lastMessage = strInfo;
455 System.err.println("Ignoring additional hover info: " + data
456 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
458 mouseOverStructure(atomIndex, strInfo);
462 * { if (history != null && strStatus != null &&
463 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
467 public void notifyAtomPicked(int atomIndex, String strInfo,
471 * this implements the toggle label behaviour copied from the original
472 * structure viewer, mc_view
476 System.err.println("Ignoring additional pick data string " + strData);
478 int chainSeparator = strInfo.indexOf(":");
480 if (chainSeparator == -1)
482 chainSeparator = strInfo.indexOf(".");
485 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
487 String mdlString = "";
488 if ((p = strInfo.indexOf(":")) > -1)
490 picked += strInfo.substring(p, strInfo.indexOf("."));
493 if ((p = strInfo.indexOf("/")) > -1)
495 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
497 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
501 if (!atomsPicked.contains(picked))
503 jmolScript("select " + picked + ";label %n %r:%c");
504 atomsPicked.addElement(picked);
508 jmolViewer.evalString("select " + picked + ";label off");
509 atomsPicked.removeElement(picked);
512 // TODO: in application this happens
514 // if (scriptWindow != null)
516 // scriptWindow.sendConsoleMessage(strInfo);
517 // scriptWindow.sendConsoleMessage("\n");
523 public void notifyCallback(CBK type, Object[] data)
526 * ensure processed in AWT thread to avoid risk of deadlocks
528 SwingUtilities.invokeLater(new Runnable()
534 processCallback(type, data);
540 * Processes one callback notification from Jmol
545 protected void processCallback(CBK type, Object[] data)
552 notifyFileLoaded((String) data[1], (String) data[2],
553 (String) data[3], (String) data[4],
554 ((Integer) data[5]).intValue());
558 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
560 // also highlight in alignment
561 // deliberate fall through
563 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
567 notifyScriptTermination((String) data[2],
568 ((Integer) data[3]).intValue());
571 sendConsoleEcho((String) data[1]);
575 (data == null) ? ((String) null) : (String) data[1]);
578 // System.err.println("Ignoring error callback.");
589 "Unhandled callback " + type + " " + data[1].toString());
592 } catch (Exception e)
594 System.err.println("Squashed Jmol callback handler error:");
600 public boolean notifyEnabled(CBK callbackPick)
602 switch (callbackPick)
618 // incremented every time a load notification is successfully handled -
619 // lightweight mechanism for other threads to detect when they can start
620 // referrring to new structures.
621 private long loadNotifiesHandled = 0;
623 public long getLoadNotifiesHandled()
625 return loadNotifiesHandled;
628 public void notifyFileLoaded(String fullPathName, String fileName2,
629 String modelName, String errorMsg, int modelParts)
631 if (errorMsg != null)
633 fileLoadingError = errorMsg;
637 // TODO: deal sensibly with models loaded inLine:
638 // modelName will be null, as will fullPathName.
640 // the rest of this routine ignores the arguments, and simply interrogates
641 // the Jmol view to find out what structures it contains, and adds them to
642 // the structure selection manager.
643 fileLoadingError = null;
644 String[] oldmodels = modelFileNames;
645 modelFileNames = null;
646 boolean notifyLoaded = false;
647 String[] modelfilenames = getStructureFiles();
648 if (modelfilenames == null)
650 // Jmol is still loading files!
653 // first check if we've lost any structures
654 if (oldmodels != null && oldmodels.length > 0)
657 for (int i = 0; i < oldmodels.length; i++)
659 for (int n = 0; n < modelfilenames.length; n++)
661 if (modelfilenames[n] == oldmodels[i])
667 if (oldmodels[i] != null)
674 String[] oldmfn = new String[oldm];
676 for (int i = 0; i < oldmodels.length; i++)
678 if (oldmodels[i] != null)
680 oldmfn[oldm++] = oldmodels[i];
683 // deregister the Jmol instance for these structures - we'll add
684 // ourselves again at the end for the current structure set.
685 getSsm().removeStructureViewerListener(this, oldmfn);
689 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
691 String fileName = modelfilenames[modelnum];
692 boolean foundEntry = false;
693 StructureFile pdb = null;
694 String pdbfile = null;
695 // model was probably loaded inline - so check the pdb file hashcode
698 // calculate essential attributes for the pdb data imported inline.
699 // prolly need to resolve modelnumber properly - for now just use our
701 pdbfile = jmolViewer.getData(
702 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
704 // search pdbentries and sequences to find correct pdbentry for this
706 for (int pe = 0; pe < getPdbCount(); pe++)
708 boolean matches = false;
709 addSequence(pe, getSequence()[pe]);
710 if (fileName == null)
713 // see JAL-623 - need method of matching pasted data up
715 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
716 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
717 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
724 File fl = new File(getPdbEntry(pe).getFile());
725 matches = fl.equals(new File(fileName));
729 // TODO: Jmol can in principle retrieve from CLASSLOADER but
732 // to be tested. See mantis bug
733 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
734 DataSourceType protocol = DataSourceType.URL;
739 protocol = DataSourceType.FILE;
741 } catch (Exception e)
746 // Explicitly map to the filename used by Jmol ;
747 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
748 fileName, protocol, getIProgressIndicator());
749 // pdbentry[pe].getFile(), protocol);
755 stashFoundChains(pdb, fileName);
760 if (!foundEntry && associateNewStructs)
762 // this is a foreign pdb file that jalview doesn't know about - add
763 // it to the dataset and try to find a home - either on a matching
764 // sequence or as a new sequence.
765 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
767 // parse pdb file into a chain, etc.
768 // locate best match for pdb in associated views and add mapping to
770 // if properly registered then
776 // so finally, update the jmol bits and pieces
777 // if (jmolpopup != null)
779 // // potential for deadlock here:
780 // // jmolpopup.updateComputedMenus();
782 if (!isLoadingFromArchive())
785 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
787 // register ourselves as a listener and notify the gui that it needs to
789 getSsm().addStructureViewerListener(this);
792 FeatureRenderer fr = getFeatureRenderer(null);
795 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
796 ((AppJmol) getViewer()).getAlignmentPanel().av
797 .applyFeaturesStyle(colours);
800 loadNotifiesHandled++;
802 setLoadingFromArchive(false);
805 protected IProgressIndicator getIProgressIndicator()
810 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
812 notifyAtomPicked(iatom, strMeasure, null);
815 public abstract void notifyScriptTermination(String strStatus,
819 * display a message echoed from the jmol viewer
823 public abstract void sendConsoleEcho(String strEcho); /*
824 * { showConsole(true);
826 * history.append("\n" + strEcho); }
829 // /End JmolStatusListener
830 // /////////////////////////////
834 * status message - usually the response received after a script
837 public abstract void sendConsoleMessage(String strStatus);
840 public void setCallbackFunction(String callbackType,
841 String callbackFunction)
843 System.err.println("Ignoring set-callback request to associate "
844 + callbackType + " with function " + callbackFunction);
848 public void showHelp()
850 showUrl("http://wiki.jmol.org"
851 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
856 * open the URL somehow
860 public abstract void showUrl(String url, String target);
863 * called to show or hide the associated console window container.
867 public abstract void showConsole(boolean show);
869 public static Viewer getJmolData(JmolParser jmolParser)
871 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
872 "-x -o -n", jmolParser);
881 * - when true will initialise jmol's file IO system (should be false
884 * @param documentBase
886 * @param commandOptions
888 public void allocateViewer(Container renderPanel, boolean jmolfileio,
889 String htmlName, URL documentBase, URL codeBase,
890 String commandOptions)
892 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
893 codeBase, commandOptions, null, null);
900 * - when true will initialise jmol's file IO system (should be false
903 * @param documentBase
905 * @param commandOptions
906 * @param consolePanel
907 * - panel to contain Jmol console
908 * @param buttonsToShow
909 * - buttons to show on the console, in order
911 public void allocateViewer(Container renderPanel, boolean jmolfileio,
912 String htmlName, URL documentBase, URL codeBase,
913 String commandOptions, final Container consolePanel,
914 String buttonsToShow)
917 System.err.println("Allocating Jmol Viewer: " + commandOptions);
919 if (commandOptions == null)
923 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
924 (jmolfileio ? new SmarterJmolAdapter() : null),
925 htmlName + ((Object) this).toString(), documentBase, codeBase,
926 commandOptions, this);
928 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
932 console = createJmolConsole(consolePanel, buttonsToShow);
933 } catch (Throwable e)
935 System.err.println("Could not create Jmol application console. "
939 if (consolePanel != null)
941 consolePanel.addComponentListener(this);
947 protected abstract JmolAppConsoleInterface createJmolConsole(
948 Container consolePanel, String buttonsToShow);
950 // BH 2018 -- Jmol console is not working due to problems with styled
953 protected org.jmol.api.JmolAppConsoleInterface console = null;
956 public int[] resizeInnerPanel(String data)
958 // Jalview doesn't honour resize panel requests
965 protected void closeConsole()
971 console.setVisible(false);
974 } catch (Exception x)
983 * ComponentListener method
986 public void componentMoved(ComponentEvent e)
991 * ComponentListener method
994 public void componentResized(ComponentEvent e)
999 * ComponentListener method
1002 public void componentShown(ComponentEvent e)
1008 * ComponentListener method
1011 public void componentHidden(ComponentEvent e)
1017 protected String getModelIdForFile(String pdbFile)
1019 if (modelFileNames == null)
1023 for (int i = 0; i < modelFileNames.length; i++)
1025 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1027 return String.valueOf(i + 1);
1034 protected ViewerType getViewerType()
1036 return ViewerType.JMOL;
1040 protected String getModelId(int pdbfnum, String file)
1042 return String.valueOf(pdbfnum + 1);
1046 * Returns ".spt" - the Jmol session file extension
1049 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1052 public String getSessionFileExtension()
1058 public void selectionChanged(BS arg0)
1060 // TODO Auto-generated method stub
1065 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1067 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1071 public String getHelpURL()
1073 return "http://wiki.jmol.org"; // BH 2018