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 Console.debug(">>Jmol>> " + script);
126 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
127 Console.debug("<<Jmol<< " + s);
133 public List<String> executeCommand(StructureCommandI command,
140 String cmd = command.getCommand();
142 if (lastCommand == null || !lastCommand.equals(cmd))
144 jmolScript(cmd + "\n");
151 public void createImage(String file, String type, int quality)
153 System.out.println("JMOL CREATE IMAGE");
157 public String createImage(String fileName, String type,
158 Object textOrBytes, int quality)
160 System.out.println("JMOL CREATE IMAGE");
165 public String eval(String strEval)
167 // System.out.println(strEval);
168 // "# 'eval' is implemented only for the applet.";
172 // End StructureListener
173 // //////////////////////////
176 public float[][] functionXY(String functionName, int x, int y)
182 public float[][][] functionXYZ(String functionName, int nx, int ny,
185 // TODO Auto-generated method stub
190 * map between index of model filename returned from getPdbFile and the first
191 * index of models from this file in the viewer. Note - this is not trimmed -
192 * use getPdbFile to get number of unique models.
194 private int _modelFileNameMap[];
197 public synchronized String[] getStructureFiles()
199 if (jmolViewer == null)
201 return new String[0];
204 if (modelFileNames == null)
206 int modelCount = jmolViewer.ms.mc;
207 String filePath = null;
208 List<String> mset = new ArrayList<>();
209 for (int i = 0; i < modelCount; ++i)
212 * defensive check for null as getModelFileName can return null even when model
215 filePath = jmolViewer.ms.getModelFileName(i);
216 if (filePath != null && !mset.contains(filePath))
223 modelFileNames = mset.toArray(new String[mset.size()]);
227 return modelFileNames;
231 * map from string to applet
234 public Map<String, Object> getRegistryInfo()
236 // TODO Auto-generated method stub
240 // ///////////////////////////////
241 // JmolStatusListener
243 public void handlePopupMenu(int x, int y)
245 // jmolpopup.show(x, y);
246 // jmolpopup.jpiShow(x, y);
250 * Highlight zero, one or more atoms on the structure
253 public void highlightAtoms(List<AtomSpec> atoms)
257 if (resetLastRes.length() > 0)
259 jmolScript(resetLastRes.toString());
260 resetLastRes.setLength(0);
262 for (AtomSpec atom : atoms)
264 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
265 atom.getChain(), atom.getPdbFile());
271 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
274 String modelId = getModelIdForFile(pdbfile);
275 if (modelId.isEmpty())
282 StringBuilder selection = new StringBuilder(32);
283 StringBuilder cmd = new StringBuilder(64);
284 selection.append("select ").append(String.valueOf(pdbResNum));
285 selection.append(":");
286 if (!chain.equals(" "))
288 selection.append(chain);
290 selection.append(" /").append(modelId);
292 cmd.append(selection).append(";wireframe 100;").append(selection)
293 .append(" and not hetero;").append("spacefill 200;select none");
295 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
296 .append(" and not hetero; spacefill 0;");
298 jmolScript(cmd.toString());
302 private boolean debug = true;
304 private void jmolHistory(boolean enable)
306 jmolScript("History " + ((debug || enable) ? "on" : "off"));
309 public void loadInline(String string)
313 // viewer.loadInline(strModel, isAppend);
315 // construct fake fullPathName and fileName so we can identify the file
317 // Then, construct pass a reader for the string to Jmol.
318 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
319 // fileName, null, reader, false, null, null, 0);
320 jmolViewer.openStringInline(string);
323 protected void mouseOverStructure(int atomIndex, final String strInfo)
326 int alocsep = strInfo.indexOf("^");
327 int mdlSep = strInfo.indexOf("/");
328 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
330 if (chainSeparator == -1)
332 chainSeparator = strInfo.indexOf(".");
333 if (mdlSep > -1 && mdlSep < chainSeparator)
335 chainSeparator1 = chainSeparator;
336 chainSeparator = mdlSep;
339 // handle insertion codes
342 pdbResNum = Integer.parseInt(
343 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
348 pdbResNum = Integer.parseInt(
349 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
353 if (strInfo.indexOf(":") > -1)
355 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
356 strInfo.indexOf("."));
363 String pdbfilename = modelFileNames[0]; // default is first model
366 if (chainSeparator1 == -1)
368 chainSeparator1 = strInfo.indexOf(".", mdlSep);
370 String mdlId = (chainSeparator1 > -1)
371 ? strInfo.substring(mdlSep + 1, chainSeparator1)
372 : strInfo.substring(mdlSep + 1);
375 // recover PDB filename for the model hovered over.
376 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
377 if (_modelFileNameMap != null)
379 int _mp = _modelFileNameMap.length - 1;
381 while (mnumber < _modelFileNameMap[_mp])
385 pdbfilename = modelFileNames[_mp];
389 if (mnumber >= 0 && mnumber < modelFileNames.length)
391 pdbfilename = modelFileNames[mnumber];
394 if (pdbfilename == null)
396 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
400 } catch (Exception e)
406 * highlight position on alignment(s); if some text is returned, show this as a
407 * second line on the structure hover tooltip
409 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
413 // change comma to pipe separator (newline token for Jmol)
414 label = label.replace(',', '|');
415 StringTokenizer toks = new StringTokenizer(strInfo, " ");
416 StringBuilder sb = new StringBuilder();
417 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
418 .append(chainId).append("/1");
419 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
420 .append(toks.nextToken());
421 sb.append("|").append(label).append("\"");
422 executeCommand(new StructureCommand(sb.toString()), false);
426 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
428 if (strInfo.equals(lastMessage))
432 lastMessage = strInfo;
435 System.err.println("Ignoring additional hover info: " + data
436 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
438 mouseOverStructure(atomIndex, strInfo);
442 * { if (history != null && strStatus != null &&
443 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
447 public void notifyAtomPicked(int atomIndex, String strInfo,
451 * this implements the toggle label behaviour copied from the original
452 * structure viewer, mc_view
456 System.err.println("Ignoring additional pick data string " + strData);
458 int chainSeparator = strInfo.indexOf(":");
460 if (chainSeparator == -1)
462 chainSeparator = strInfo.indexOf(".");
465 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
467 String mdlString = "";
468 if ((p = strInfo.indexOf(":")) > -1)
470 picked += strInfo.substring(p, strInfo.indexOf("."));
473 if ((p = strInfo.indexOf("/")) > -1)
475 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
477 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
481 if (!atomsPicked.contains(picked))
483 jmolScript("select " + picked + ";label %n %r:%c");
484 atomsPicked.addElement(picked);
488 jmolViewer.evalString("select " + picked + ";label off");
489 atomsPicked.removeElement(picked);
492 // TODO: in application this happens
494 // if (scriptWindow != null)
496 // scriptWindow.sendConsoleMessage(strInfo);
497 // scriptWindow.sendConsoleMessage("\n");
503 public void notifyCallback(CBK type, Object[] data)
506 * ensure processed in AWT thread to avoid risk of deadlocks
508 SwingUtilities.invokeLater(new Runnable()
514 processCallback(type, data);
520 * Processes one callback notification from Jmol
525 protected void processCallback(CBK type, Object[] data)
532 notifyFileLoaded((String) data[1], (String) data[2],
533 (String) data[3], (String) data[4],
534 ((Integer) data[5]).intValue());
538 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
540 // also highlight in alignment
541 // deliberate fall through
543 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
547 notifyScriptTermination((String) data[2],
548 ((Integer) data[3]).intValue());
551 sendConsoleEcho((String) data[1]);
555 (data == null) ? ((String) null) : (String) data[1]);
558 // System.err.println("Ignoring error callback.");
569 "Unhandled callback " + type + " " + data[1].toString());
572 } catch (Exception e)
574 System.err.println("Squashed Jmol callback handler error:");
580 public boolean notifyEnabled(CBK callbackPick)
582 switch (callbackPick)
598 // incremented every time a load notification is successfully handled -
599 // lightweight mechanism for other threads to detect when they can start
600 // referrring to new structures.
601 private long loadNotifiesHandled = 0;
603 public long getLoadNotifiesHandled()
605 return loadNotifiesHandled;
608 public void notifyFileLoaded(String fullPathName, String fileName2,
609 String modelName, String errorMsg, int modelParts)
611 if (errorMsg != null)
613 fileLoadingError = errorMsg;
617 // TODO: deal sensibly with models loaded inLine:
618 // modelName will be null, as will fullPathName.
620 // the rest of this routine ignores the arguments, and simply interrogates
621 // the Jmol view to find out what structures it contains, and adds them to
622 // the structure selection manager.
623 fileLoadingError = null;
624 String[] oldmodels = modelFileNames;
625 modelFileNames = null;
626 boolean notifyLoaded = false;
627 String[] modelfilenames = getStructureFiles();
628 if (modelfilenames == null)
630 // Jmol is still loading files!
633 // first check if we've lost any structures
634 if (oldmodels != null && oldmodels.length > 0)
637 for (int i = 0; i < oldmodels.length; i++)
639 for (int n = 0; n < modelfilenames.length; n++)
641 if (modelfilenames[n] == oldmodels[i])
647 if (oldmodels[i] != null)
654 String[] oldmfn = new String[oldm];
656 for (int i = 0; i < oldmodels.length; i++)
658 if (oldmodels[i] != null)
660 oldmfn[oldm++] = oldmodels[i];
663 // deregister the Jmol instance for these structures - we'll add
664 // ourselves again at the end for the current structure set.
665 getSsm().removeStructureViewerListener(this, oldmfn);
669 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
671 String fileName = modelfilenames[modelnum];
672 boolean foundEntry = false;
673 StructureFile pdb = null;
674 String pdbfile = null;
675 // model was probably loaded inline - so check the pdb file hashcode
678 // calculate essential attributes for the pdb data imported inline.
679 // prolly need to resolve modelnumber properly - for now just use our
681 pdbfile = jmolViewer.getData(
682 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
684 // search pdbentries and sequences to find correct pdbentry for this
686 for (int pe = 0; pe < getPdbCount(); pe++)
688 boolean matches = false;
689 addSequence(pe, getSequence()[pe]);
690 if (fileName == null)
693 // see JAL-623 - need method of matching pasted data up
695 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
696 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
697 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
704 File fl = new File(getPdbEntry(pe).getFile());
705 matches = fl.equals(new File(fileName));
709 // TODO: Jmol can in principle retrieve from CLASSLOADER but
712 // to be tested. See mantis bug
713 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
714 DataSourceType protocol = DataSourceType.URL;
719 protocol = DataSourceType.FILE;
721 } catch (Exception e)
726 // Explicitly map to the filename used by Jmol ;
727 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
728 fileName, protocol, getIProgressIndicator());
729 // pdbentry[pe].getFile(), protocol);
735 stashFoundChains(pdb, fileName);
740 if (!foundEntry && associateNewStructs)
742 // this is a foreign pdb file that jalview doesn't know about - add
743 // it to the dataset and try to find a home - either on a matching
744 // sequence or as a new sequence.
745 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
747 // parse pdb file into a chain, etc.
748 // locate best match for pdb in associated views and add mapping to
750 // if properly registered then
756 // so finally, update the jmol bits and pieces
757 // if (jmolpopup != null)
759 // // potential for deadlock here:
760 // // jmolpopup.updateComputedMenus();
762 if (!isLoadingFromArchive())
765 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
767 // register ourselves as a listener and notify the gui that it needs to
769 getSsm().addStructureViewerListener(this);
772 FeatureRenderer fr = getFeatureRenderer(null);
775 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
776 ((AppJmol) getViewer()).getAlignmentPanel().av
777 .applyFeaturesStyle(colours);
780 loadNotifiesHandled++;
782 setLoadingFromArchive(false);
785 protected IProgressIndicator getIProgressIndicator()
790 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
792 notifyAtomPicked(iatom, strMeasure, null);
795 public abstract void notifyScriptTermination(String strStatus,
799 * display a message echoed from the jmol viewer
803 public abstract void sendConsoleEcho(String strEcho); /*
804 * { showConsole(true);
806 * history.append("\n" + strEcho); }
809 // /End JmolStatusListener
810 // /////////////////////////////
814 * status message - usually the response received after a script
817 public abstract void sendConsoleMessage(String strStatus);
820 public void setCallbackFunction(String callbackType,
821 String callbackFunction)
823 System.err.println("Ignoring set-callback request to associate "
824 + callbackType + " with function " + callbackFunction);
828 public void showHelp()
830 showUrl("http://wiki.jmol.org"
831 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
836 * open the URL somehow
840 public abstract void showUrl(String url, String target);
843 * called to show or hide the associated console window container.
847 public abstract void showConsole(boolean show);
849 public static Viewer getJmolData(JmolParser jmolParser)
851 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
852 "-x -o -n", jmolParser);
861 * - when true will initialise jmol's file IO system (should be false
864 * @param documentBase
866 * @param commandOptions
868 public void allocateViewer(Container renderPanel, boolean jmolfileio,
869 String htmlName, URL documentBase, URL codeBase,
870 String commandOptions)
872 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
873 codeBase, commandOptions, null, null);
880 * - when true will initialise jmol's file IO system (should be false
883 * @param documentBase
885 * @param commandOptions
886 * @param consolePanel
887 * - panel to contain Jmol console
888 * @param buttonsToShow
889 * - buttons to show on the console, in order
891 public void allocateViewer(Container renderPanel, boolean jmolfileio,
892 String htmlName, URL documentBase, URL codeBase,
893 String commandOptions, final Container consolePanel,
894 String buttonsToShow)
897 System.err.println("Allocating Jmol Viewer: " + commandOptions);
899 if (commandOptions == null)
903 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
904 (jmolfileio ? new SmarterJmolAdapter() : null),
905 htmlName + ((Object) this).toString(), documentBase, codeBase,
906 commandOptions, this);
908 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
912 console = createJmolConsole(consolePanel, buttonsToShow);
913 } catch (Throwable e)
915 System.err.println("Could not create Jmol application console. "
919 if (consolePanel != null)
921 consolePanel.addComponentListener(this);
927 protected abstract JmolAppConsoleInterface createJmolConsole(
928 Container consolePanel, String buttonsToShow);
930 // BH 2018 -- Jmol console is not working due to problems with styled
933 protected org.jmol.api.JmolAppConsoleInterface console = null;
936 public int[] resizeInnerPanel(String data)
938 // Jalview doesn't honour resize panel requests
945 protected void closeConsole()
951 console.setVisible(false);
954 } catch (Exception x)
963 * ComponentListener method
966 public void componentMoved(ComponentEvent e)
971 * ComponentListener method
974 public void componentResized(ComponentEvent e)
979 * ComponentListener method
982 public void componentShown(ComponentEvent e)
988 * ComponentListener method
991 public void componentHidden(ComponentEvent e)
997 protected String getModelIdForFile(String pdbFile)
999 if (modelFileNames == null)
1003 for (int i = 0; i < modelFileNames.length; i++)
1005 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1007 return String.valueOf(i + 1);
1014 protected ViewerType getViewerType()
1016 return ViewerType.JMOL;
1020 protected String getModelId(int pdbfnum, String file)
1022 return String.valueOf(pdbfnum + 1);
1026 * Returns ".spt" - the Jmol session file extension
1029 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1032 public String getSessionFileExtension()
1038 public void selectionChanged(BS arg0)
1040 // TODO Auto-generated method stub
1045 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1047 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1051 public String getHelpURL()
1053 return "http://wiki.jmol.org"; // BH 2018