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 com.google.common.collect.Lists;
48 import jalview.api.AlignmentViewPanel;
49 import jalview.api.FeatureRenderer;
50 import jalview.api.FeatureSettingsModelI;
51 import jalview.api.SequenceRenderer;
52 import jalview.bin.Console;
53 import jalview.datamodel.PDBEntry;
54 import jalview.datamodel.SequenceI;
55 import jalview.gui.AppJmol;
56 import jalview.gui.IProgressIndicator;
57 import jalview.gui.StructureViewer.ViewerType;
58 import jalview.io.DataSourceType;
59 import jalview.io.StructureFile;
60 import jalview.structure.AtomSpec;
61 import jalview.structure.StructureCommand;
62 import jalview.structure.StructureCommandI;
63 import jalview.structure.StructureSelectionManager;
64 import jalview.structures.models.AAStructureBindingModel;
65 import jalview.ws.dbsources.Pdb;
66 import javajs.util.BS;
68 public abstract class JalviewJmolBinding extends AAStructureBindingModel
69 implements JmolStatusListener, JmolSelectionListener,
72 private String lastMessage;
75 * when true, try to search the associated datamodel for sequences that are
76 * associated with any unknown structures in the Jmol view.
78 private boolean associateNewStructs = false;
80 private Vector<String> atomsPicked = new Vector<>();
82 private String lastCommand;
84 private boolean loadedInline;
86 private StringBuffer resetLastRes = new StringBuffer();
88 public Viewer jmolViewer;
90 public JalviewJmolBinding(StructureSelectionManager ssm,
91 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
92 DataSourceType protocol)
94 super(ssm, pdbentry, sequenceIs, protocol);
95 setStructureCommands(new JmolCommands());
97 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
98 * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
101 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
105 public JalviewJmolBinding(StructureSelectionManager ssm,
106 SequenceI[][] seqs, Viewer theViewer)
110 jmolViewer = theViewer;
111 jmolViewer.setJmolStatusListener(this);
112 jmolViewer.addSelectionListener(this);
113 setStructureCommands(new JmolCommands());
117 * construct a title string for the viewer window based on the data jalview
122 public String getViewerTitle()
124 return getViewerTitle("Jmol", true);
127 private String jmolScript(String script)
129 return jmolScript(script, false);
132 private String jmolScript(String script, boolean useScriptWait)
134 Console.debug(">>Jmol>> " + script);
138 s = jmolViewer.scriptWait(script);
142 s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
144 Console.debug("<<Jmol<< " + s);
150 public List<String> executeCommand(StructureCommandI command,
157 String cmd = command.getCommand();
159 if (lastCommand == null || !lastCommand.equals(cmd))
161 jmolScript(cmd + "\n");
168 public void createImage(String file, String type, int quality)
170 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
174 public String createImage(String fileName, String type,
175 Object textOrBytes, int quality)
177 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
182 public String eval(String strEval)
184 // jalview.bin.Console.outPrintln(strEval);
185 // "# 'eval' is implemented only for the applet.";
189 // End StructureListener
190 // //////////////////////////
192 ////////////////////////////
197 public List<String> getHetatmNames()
199 HashMap<String,String> hetlist=new HashMap();
200 for (int mc=0;mc<jmolViewer.ms.mc; mc++)
202 Map<String,String> hets = jmolViewer.ms.getHeteroList(mc);
203 hetlist.putAll(hets);
205 return Arrays.asList(hetlist.keySet().toArray(new String[0]));
208 ////////////////////////////
211 public float[][] functionXY(String functionName, int x, int y)
217 public float[][][] functionXYZ(String functionName, int nx, int ny,
220 // TODO Auto-generated method stub
225 * map between index of model filename returned from getPdbFile and the first
226 * index of models from this file in the viewer. Note - this is not trimmed -
227 * use getPdbFile to get number of unique models.
229 private int _modelFileNameMap[];
232 public synchronized String[] getStructureFiles()
234 if (jmolViewer == null)
236 return new String[0];
239 if (modelFileNames == null)
241 int modelCount = jmolViewer.ms.mc;
242 String filePath = null;
243 List<String> mset = new ArrayList<>();
244 for (int i = 0; i < modelCount; ++i)
247 * defensive check for null as getModelFileName can return null even when model
250 filePath = jmolViewer.ms.getModelFileName(i);
251 if (filePath != null && !mset.contains(filePath))
258 modelFileNames = mset.toArray(new String[mset.size()]);
262 return modelFileNames;
266 * map from string to applet
269 public Map<String, Object> getRegistryInfo()
271 // TODO Auto-generated method stub
275 // ///////////////////////////////
276 // JmolStatusListener
278 public void handlePopupMenu(int x, int y)
280 // jmolpopup.show(x, y);
281 // jmolpopup.jpiShow(x, y);
285 * Highlight zero, one or more atoms on the structure
288 public void highlightAtoms(List<AtomSpec> atoms)
292 if (resetLastRes.length() > 0)
294 jmolScript(resetLastRes.toString());
295 resetLastRes.setLength(0);
297 StringBuilder highlightCommands=null;
298 for (AtomSpec atom : atoms)
300 StringBuilder thisAtom = highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
301 atom.getChain(), atom.getPdbFile());
302 if (thisAtom!=null) {
303 if (highlightCommands==null)
305 highlightCommands=thisAtom;
307 highlightCommands.append(thisAtom);
311 if (highlightCommands!=null)
314 jmolScript(highlightCommands.toString());
317 // Highlight distances between atoms with a 'measure' command - not yet
319 // if (atoms.size() >= 2)
321 // StringBuilder sb = new StringBuilder();
322 // for (int a = 0; a < atoms.size(); a++)
324 // AtomSpec speca = atoms.get(a);
325 // String a_model = getModelIdForFile(speca.getPdbFile());
326 // for (int b = a + 1; b < atoms.size(); b++)
328 // AtomSpec specb = atoms.get(b);
329 // String b_model = getModelIdForFile(speca.getPdbFile());
330 // sb.append("measure ALL (" + speca.getAtomIndex() + " and */"
331 // + a_model + ") (" + specb.getAtomIndex() + " and */"
332 // + b_model + ");");
335 // jmolHistory(false, useScriptWait);
336 // jmolScript(sb.toString(), useScriptWait);
337 // jmolHistory(true, useScriptWait);
345 private StringBuilder highlightAtom(int atomIndex, int pdbResNum, String chain,
348 String modelId = getModelIdForFile(pdbfile);
349 if (modelId.isEmpty())
354 StringBuilder selection = new StringBuilder(32);
355 StringBuilder cmd = new StringBuilder(64);
356 selection.append("select ").append(String.valueOf(pdbResNum));
357 selection.append(":");
358 if (!chain.equals(" "))
360 selection.append(chain);
362 selection.append(" /").append(modelId);
364 cmd.append(selection).append(";wireframe 100;").append(selection)
365 .append(" and not hetero;").append("spacefill 200;select none");
367 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
368 .append(" and not hetero; spacefill 0;");
373 private boolean debug = true;
375 private void jmolHistory(boolean enable)
377 jmolHistory(enable, false);
380 private void jmolHistory(boolean enable, boolean useScriptWait)
382 jmolScript("History " + ((debug || enable) ? "on" : "off"),
386 public void loadInline(String string)
390 // viewer.loadInline(strModel, isAppend);
392 // construct fake fullPathName and fileName so we can identify the file
394 // Then, construct pass a reader for the string to Jmol.
395 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
396 // fileName, null, reader, false, null, null, 0);
397 jmolViewer.openStringInline(string);
400 protected void mouseOverStructure(int atomIndex, final String strInfo)
403 int alocsep = strInfo.indexOf("^");
404 int mdlSep = strInfo.indexOf("/");
405 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
407 if (chainSeparator == -1)
409 chainSeparator = strInfo.indexOf(".");
410 if (mdlSep > -1 && mdlSep < chainSeparator)
412 chainSeparator1 = chainSeparator;
413 chainSeparator = mdlSep;
416 // handle insertion codes
419 pdbResNum = Integer.parseInt(
420 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
425 pdbResNum = Integer.parseInt(
426 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
430 if (strInfo.indexOf(":") > -1)
432 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
433 strInfo.indexOf("."));
440 String pdbfilename = modelFileNames[0]; // default is first model
443 if (chainSeparator1 == -1)
445 chainSeparator1 = strInfo.indexOf(".", mdlSep);
447 String mdlId = (chainSeparator1 > -1)
448 ? strInfo.substring(mdlSep + 1, chainSeparator1)
449 : strInfo.substring(mdlSep + 1);
452 // recover PDB filename for the model hovered over.
453 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
454 if (_modelFileNameMap != null)
456 int _mp = _modelFileNameMap.length - 1;
458 while (mnumber < _modelFileNameMap[_mp])
462 pdbfilename = modelFileNames[_mp];
466 if (mnumber >= 0 && mnumber < modelFileNames.length)
468 pdbfilename = modelFileNames[mnumber];
471 if (pdbfilename == null)
473 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
477 } catch (Exception e)
483 * highlight position on alignment(s); if some text is returned, show this as a
484 * second line on the structure hover tooltip
486 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
490 // change comma to pipe separator (newline token for Jmol)
491 label = label.replace(',', '|');
492 StringTokenizer toks = new StringTokenizer(strInfo, " ");
493 StringBuilder sb = new StringBuilder();
494 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
495 .append(chainId).append("/1");
496 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
497 .append(toks.nextToken());
498 sb.append("|").append(label).append("\"");
499 executeCommand(new StructureCommand(sb.toString()), false);
503 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
505 if (strInfo.equals(lastMessage))
509 lastMessage = strInfo;
512 jalview.bin.Console.errPrintln("Ignoring additional hover info: " + data
513 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
515 mouseOverStructure(atomIndex, strInfo);
519 * { if (history != null && strStatus != null &&
520 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
524 public void notifyAtomPicked(int atomIndex, String strInfo,
528 * this implements the toggle label behaviour copied from the original
529 * structure viewer, mc_view
533 jalview.bin.Console.errPrintln("Ignoring additional pick data string " + strData);
535 int chainSeparator = strInfo.indexOf(":");
537 if (chainSeparator == -1)
539 chainSeparator = strInfo.indexOf(".");
542 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
544 String mdlString = "";
545 if ((p = strInfo.indexOf(":")) > -1)
547 picked += strInfo.substring(p, strInfo.indexOf("."));
550 if ((p = strInfo.indexOf("/")) > -1)
552 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
554 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
558 if (!atomsPicked.contains(picked))
560 jmolScript("select " + picked + ";label %n %r:%c");
561 atomsPicked.addElement(picked);
565 jmolViewer.evalString("select " + picked + ";label off");
566 atomsPicked.removeElement(picked);
569 // TODO: in application this happens
571 // if (scriptWindow != null)
573 // scriptWindow.sendConsoleMessage(strInfo);
574 // scriptWindow.sendConsoleMessage("\n");
580 public void notifyCallback(CBK type, Object[] data)
583 * ensure processed in AWT thread to avoid risk of deadlocks
585 SwingUtilities.invokeLater(new Runnable()
591 processCallback(type, data);
597 * Processes one callback notification from Jmol
602 protected void processCallback(CBK type, Object[] data)
609 notifyFileLoaded((String) data[1], (String) data[2],
610 (String) data[3], (String) data[4],
611 ((Integer) data[5]).intValue());
615 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
617 // also highlight in alignment
618 // deliberate fall through
620 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
624 notifyScriptTermination((String) data[2],
625 ((Integer) data[3]).intValue());
628 sendConsoleEcho((String) data[1]);
632 (data == null) ? ((String) null) : (String) data[1]);
635 // jalview.bin.Console.errPrintln("Ignoring error callback.");
645 jalview.bin.Console.errPrintln(
646 "Unhandled callback " + type + " " + data[1].toString());
649 } catch (Exception e)
651 jalview.bin.Console.errPrintln("Squashed Jmol callback handler error:");
657 public boolean notifyEnabled(CBK callbackPick)
659 switch (callbackPick)
675 // incremented every time a load notification is successfully handled -
676 // lightweight mechanism for other threads to detect when they can start
677 // referrring to new structures.
678 private long loadNotifiesHandled = 0;
680 public long getLoadNotifiesHandled()
682 return loadNotifiesHandled;
685 public void notifyFileLoaded(String fullPathName, String fileName2,
686 String modelName, String errorMsg, int modelParts)
688 if (errorMsg != null)
690 fileLoadingError = errorMsg;
694 // TODO: deal sensibly with models loaded inLine:
695 // modelName will be null, as will fullPathName.
697 // the rest of this routine ignores the arguments, and simply interrogates
698 // the Jmol view to find out what structures it contains, and adds them to
699 // the structure selection manager.
700 fileLoadingError = null;
701 String[] oldmodels = modelFileNames;
702 modelFileNames = null;
703 boolean notifyLoaded = false;
704 String[] modelfilenames = getStructureFiles();
705 if (modelfilenames == null)
707 // Jmol is still loading files!
710 // first check if we've lost any structures
711 if (oldmodels != null && oldmodels.length > 0)
714 for (int i = 0; i < oldmodels.length; i++)
716 for (int n = 0; n < modelfilenames.length; n++)
718 if (modelfilenames[n] == oldmodels[i])
724 if (oldmodels[i] != null)
731 String[] oldmfn = new String[oldm];
733 for (int i = 0; i < oldmodels.length; i++)
735 if (oldmodels[i] != null)
737 oldmfn[oldm++] = oldmodels[i];
740 // deregister the Jmol instance for these structures - we'll add
741 // ourselves again at the end for the current structure set.
742 getSsm().removeStructureViewerListener(this, oldmfn);
746 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
748 String fileName = modelfilenames[modelnum];
749 boolean foundEntry = false;
750 StructureFile pdb = null;
751 String pdbfile = null;
752 // model was probably loaded inline - so check the pdb file hashcode
755 // calculate essential attributes for the pdb data imported inline.
756 // prolly need to resolve modelnumber properly - for now just use our
758 pdbfile = jmolViewer.getData(
759 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
761 // search pdbentries and sequences to find correct pdbentry for this
763 for (int pe = 0; pe < getPdbCount(); pe++)
765 boolean matches = false;
766 addSequence(pe, getSequence()[pe]);
767 if (fileName == null)
770 // see JAL-623 - need method of matching pasted data up
772 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
773 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
774 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
781 File fl = new File(getPdbEntry(pe).getFile());
782 matches = fl.equals(new File(fileName));
786 // TODO: Jmol can in principle retrieve from CLASSLOADER but
789 // to be tested. See mantis bug
790 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
791 DataSourceType protocol = DataSourceType.URL;
796 protocol = DataSourceType.FILE;
798 } catch (Exception e)
803 // Explicitly map to the filename used by Jmol ;
804 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
805 fileName, protocol, getIProgressIndicator());
806 // pdbentry[pe].getFile(), protocol);
812 stashFoundChains(pdb, fileName);
817 if (!foundEntry && associateNewStructs)
819 // this is a foreign pdb file that jalview doesn't know about - add
820 // it to the dataset and try to find a home - either on a matching
821 // sequence or as a new sequence.
822 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
824 // parse pdb file into a chain, etc.
825 // locate best match for pdb in associated views and add mapping to
827 // if properly registered then
833 // so finally, update the jmol bits and pieces
834 // if (jmolpopup != null)
836 // // potential for deadlock here:
837 // // jmolpopup.updateComputedMenus();
839 if (!isLoadingFromArchive())
842 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
844 // register ourselves as a listener and notify the gui that it needs to
846 getSsm().addStructureViewerListener(this);
849 FeatureRenderer fr = getFeatureRenderer(null);
852 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
853 ((AppJmol) getViewer()).getAlignmentPanel().av
854 .applyFeaturesStyle(colours);
857 loadNotifiesHandled++;
859 setLoadingFromArchive(false);
862 protected IProgressIndicator getIProgressIndicator()
867 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
869 notifyAtomPicked(iatom, strMeasure, null);
872 public abstract void notifyScriptTermination(String strStatus,
876 * display a message echoed from the jmol viewer
880 public abstract void sendConsoleEcho(String strEcho); /*
881 * { showConsole(true);
883 * history.append("\n" + strEcho); }
886 // /End JmolStatusListener
887 // /////////////////////////////
891 * status message - usually the response received after a script
894 public abstract void sendConsoleMessage(String strStatus);
897 public void setCallbackFunction(String callbackType,
898 String callbackFunction)
900 jalview.bin.Console.errPrintln("Ignoring set-callback request to associate "
901 + callbackType + " with function " + callbackFunction);
905 public void showHelp()
907 showUrl("http://wiki.jmol.org"
908 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
913 * open the URL somehow
917 public abstract void showUrl(String url, String target);
920 * called to show or hide the associated console window container.
924 public abstract void showConsole(boolean show);
926 public static Viewer getJmolData(JmolParser jmolParser)
928 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
929 "-x -o -n", jmolParser);
938 * - when true will initialise jmol's file IO system (should be false
941 * @param documentBase
943 * @param commandOptions
945 public void allocateViewer(Container renderPanel, boolean jmolfileio,
946 String htmlName, URL documentBase, URL codeBase,
947 String commandOptions)
949 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
950 codeBase, commandOptions, null, null);
957 * - when true will initialise jmol's file IO system (should be false
960 * @param documentBase
962 * @param commandOptions
963 * @param consolePanel
964 * - panel to contain Jmol console
965 * @param buttonsToShow
966 * - buttons to show on the console, in order
968 public void allocateViewer(Container renderPanel, boolean jmolfileio,
969 String htmlName, URL documentBase, URL codeBase,
970 String commandOptions, final Container consolePanel,
971 String buttonsToShow)
974 jalview.bin.Console.errPrintln("Allocating Jmol Viewer: " + commandOptions);
976 if (commandOptions == null)
980 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
981 (jmolfileio ? new SmarterJmolAdapter() : null),
982 htmlName + ((Object) this).toString(), documentBase, codeBase,
983 commandOptions, this);
985 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
989 console = createJmolConsole(consolePanel, buttonsToShow);
990 } catch (Throwable e)
992 jalview.bin.Console.errPrintln("Could not create Jmol application console. "
996 if (consolePanel != null)
998 consolePanel.addComponentListener(this);
1004 protected abstract JmolAppConsoleInterface createJmolConsole(
1005 Container consolePanel, String buttonsToShow);
1007 // BH 2018 -- Jmol console is not working due to problems with styled
1010 protected org.jmol.api.JmolAppConsoleInterface console = null;
1013 public int[] resizeInnerPanel(String data)
1015 // Jalview doesn't honour resize panel requests
1022 protected void closeConsole()
1024 if (console != null)
1028 console.setVisible(false);
1031 } catch (Exception x)
1040 * ComponentListener method
1043 public void componentMoved(ComponentEvent e)
1048 * ComponentListener method
1051 public void componentResized(ComponentEvent e)
1056 * ComponentListener method
1059 public void componentShown(ComponentEvent e)
1065 * ComponentListener method
1068 public void componentHidden(ComponentEvent e)
1074 protected String getModelIdForFile(String pdbFile)
1076 if (modelFileNames == null)
1080 for (int i = 0; i < modelFileNames.length; i++)
1082 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1084 return String.valueOf(i + 1);
1091 protected ViewerType getViewerType()
1093 return ViewerType.JMOL;
1097 protected String getModelId(int pdbfnum, String file)
1099 return String.valueOf(pdbfnum + 1);
1103 * Returns ".spt" - the Jmol session file extension
1106 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1109 public String getSessionFileExtension()
1115 public void selectionChanged(BS arg0)
1117 // TODO Auto-generated method stub
1122 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1124 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1128 public String getHelpURL()
1130 return "http://wiki.jmol.org"; // BH 2018