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 private boolean globalUseScriptWait = false;
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);
134 if (globalUseScriptWait || useScriptWait)
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");
166 public void createImage(String file, String type, int quality)
168 System.out.println("JMOL CREATE IMAGE");
172 public String createImage(String fileName, String type,
173 Object textOrBytes, int quality)
175 System.out.println("JMOL CREATE IMAGE");
180 public String eval(String strEval)
182 // System.out.println(strEval);
183 // "# 'eval' is implemented only for the applet.";
187 // End StructureListener
188 // //////////////////////////
191 public float[][] functionXY(String functionName, int x, int y)
197 public float[][][] functionXYZ(String functionName, int nx, int ny,
200 // TODO Auto-generated method stub
205 * map between index of model filename returned from getPdbFile and the first
206 * index of models from this file in the viewer. Note - this is not trimmed -
207 * use getPdbFile to get number of unique models.
209 private int _modelFileNameMap[];
212 public synchronized String[] getStructureFiles()
214 if (jmolViewer == null)
216 return new String[0];
219 if (modelFileNames == null)
221 int modelCount = jmolViewer.ms.mc;
222 String filePath = null;
223 List<String> mset = new ArrayList<>();
224 for (int i = 0; i < modelCount; ++i)
227 * defensive check for null as getModelFileName can return null even when model
230 filePath = jmolViewer.ms.getModelFileName(i);
231 if (filePath != null && !mset.contains(filePath))
238 modelFileNames = mset.toArray(new String[mset.size()]);
242 return modelFileNames;
246 * map from string to applet
249 public Map<String, Object> getRegistryInfo()
251 // TODO Auto-generated method stub
255 // ///////////////////////////////
256 // JmolStatusListener
258 public void handlePopupMenu(int x, int y)
260 // jmolpopup.show(x, y);
261 // jmolpopup.jpiShow(x, y);
265 * Highlight zero, one or more atoms on the structure
268 public void highlightAtoms(List<AtomSpec> atoms)
272 boolean useScriptWait = atoms.size() > 1;
273 boolean prevUseScriptWait = setGlobalUseScriptWait(useScriptWait);
274 if (resetLastRes.length() > 0)
276 jmolScript(resetLastRes.toString());
277 resetLastRes.setLength(0);
279 for (AtomSpec atom : atoms)
281 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
282 atom.getChain(), atom.getPdbFile(), useScriptWait);
284 setGlobalUseScriptWait(prevUseScriptWait);
289 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
290 String pdbfile, boolean useScriptWait)
292 String modelId = getModelIdForFile(pdbfile);
293 if (modelId.isEmpty())
300 StringBuilder selection = new StringBuilder(32);
301 StringBuilder cmd = new StringBuilder(64);
302 selection.append("select ").append(String.valueOf(pdbResNum));
303 selection.append(":");
304 if (!chain.equals(" "))
306 selection.append(chain);
308 selection.append(" /").append(modelId);
310 cmd.append(selection).append(";wireframe 100;").append(selection)
311 .append(" and not hetero;").append("spacefill 200;select none");
313 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
314 .append(" and not hetero; spacefill 0;");
316 jmolScript(cmd.toString(), useScriptWait);
320 private boolean debug = true;
322 private void jmolHistory(boolean enable)
324 jmolScript("History " + ((debug || enable) ? "on" : "off"));
327 public void loadInline(String string)
331 // viewer.loadInline(strModel, isAppend);
333 // construct fake fullPathName and fileName so we can identify the file
335 // Then, construct pass a reader for the string to Jmol.
336 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
337 // fileName, null, reader, false, null, null, 0);
338 jmolViewer.openStringInline(string);
341 protected void mouseOverStructure(int atomIndex, final String strInfo)
344 int alocsep = strInfo.indexOf("^");
345 int mdlSep = strInfo.indexOf("/");
346 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
348 if (chainSeparator == -1)
350 chainSeparator = strInfo.indexOf(".");
351 if (mdlSep > -1 && mdlSep < chainSeparator)
353 chainSeparator1 = chainSeparator;
354 chainSeparator = mdlSep;
357 // handle insertion codes
360 pdbResNum = Integer.parseInt(
361 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
366 pdbResNum = Integer.parseInt(
367 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
371 if (strInfo.indexOf(":") > -1)
373 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
374 strInfo.indexOf("."));
381 String pdbfilename = modelFileNames[0]; // default is first model
384 if (chainSeparator1 == -1)
386 chainSeparator1 = strInfo.indexOf(".", mdlSep);
388 String mdlId = (chainSeparator1 > -1)
389 ? strInfo.substring(mdlSep + 1, chainSeparator1)
390 : strInfo.substring(mdlSep + 1);
393 // recover PDB filename for the model hovered over.
394 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
395 if (_modelFileNameMap != null)
397 int _mp = _modelFileNameMap.length - 1;
399 while (mnumber < _modelFileNameMap[_mp])
403 pdbfilename = modelFileNames[_mp];
407 if (mnumber >= 0 && mnumber < modelFileNames.length)
409 pdbfilename = modelFileNames[mnumber];
412 if (pdbfilename == null)
414 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
418 } catch (Exception e)
424 * highlight position on alignment(s); if some text is returned, show this as a
425 * second line on the structure hover tooltip
427 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
431 // change comma to pipe separator (newline token for Jmol)
432 label = label.replace(',', '|');
433 StringTokenizer toks = new StringTokenizer(strInfo, " ");
434 StringBuilder sb = new StringBuilder();
435 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
436 .append(chainId).append("/1");
437 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
438 .append(toks.nextToken());
439 sb.append("|").append(label).append("\"");
440 executeCommand(new StructureCommand(sb.toString()), false);
444 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
446 if (strInfo.equals(lastMessage))
450 lastMessage = strInfo;
453 System.err.println("Ignoring additional hover info: " + data
454 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
456 mouseOverStructure(atomIndex, strInfo);
460 * { if (history != null && strStatus != null &&
461 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
465 public void notifyAtomPicked(int atomIndex, String strInfo,
469 * this implements the toggle label behaviour copied from the original
470 * structure viewer, mc_view
474 System.err.println("Ignoring additional pick data string " + strData);
476 int chainSeparator = strInfo.indexOf(":");
478 if (chainSeparator == -1)
480 chainSeparator = strInfo.indexOf(".");
483 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
485 String mdlString = "";
486 if ((p = strInfo.indexOf(":")) > -1)
488 picked += strInfo.substring(p, strInfo.indexOf("."));
491 if ((p = strInfo.indexOf("/")) > -1)
493 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
495 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
499 if (!atomsPicked.contains(picked))
501 jmolScript("select " + picked + ";label %n %r:%c");
502 atomsPicked.addElement(picked);
506 jmolViewer.evalString("select " + picked + ";label off");
507 atomsPicked.removeElement(picked);
510 // TODO: in application this happens
512 // if (scriptWindow != null)
514 // scriptWindow.sendConsoleMessage(strInfo);
515 // scriptWindow.sendConsoleMessage("\n");
521 public void notifyCallback(CBK type, Object[] data)
524 * ensure processed in AWT thread to avoid risk of deadlocks
526 SwingUtilities.invokeLater(new Runnable()
532 processCallback(type, data);
538 * Processes one callback notification from Jmol
543 protected void processCallback(CBK type, Object[] data)
550 notifyFileLoaded((String) data[1], (String) data[2],
551 (String) data[3], (String) data[4],
552 ((Integer) data[5]).intValue());
556 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
558 // also highlight in alignment
559 // deliberate fall through
561 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
565 notifyScriptTermination((String) data[2],
566 ((Integer) data[3]).intValue());
569 sendConsoleEcho((String) data[1]);
573 (data == null) ? ((String) null) : (String) data[1]);
576 // System.err.println("Ignoring error callback.");
587 "Unhandled callback " + type + " " + data[1].toString());
590 } catch (Exception e)
592 System.err.println("Squashed Jmol callback handler error:");
598 public boolean notifyEnabled(CBK callbackPick)
600 switch (callbackPick)
616 // incremented every time a load notification is successfully handled -
617 // lightweight mechanism for other threads to detect when they can start
618 // referrring to new structures.
619 private long loadNotifiesHandled = 0;
621 public long getLoadNotifiesHandled()
623 return loadNotifiesHandled;
626 public void notifyFileLoaded(String fullPathName, String fileName2,
627 String modelName, String errorMsg, int modelParts)
629 if (errorMsg != null)
631 fileLoadingError = errorMsg;
635 // TODO: deal sensibly with models loaded inLine:
636 // modelName will be null, as will fullPathName.
638 // the rest of this routine ignores the arguments, and simply interrogates
639 // the Jmol view to find out what structures it contains, and adds them to
640 // the structure selection manager.
641 fileLoadingError = null;
642 String[] oldmodels = modelFileNames;
643 modelFileNames = null;
644 boolean notifyLoaded = false;
645 String[] modelfilenames = getStructureFiles();
646 if (modelfilenames == null)
648 // Jmol is still loading files!
651 // first check if we've lost any structures
652 if (oldmodels != null && oldmodels.length > 0)
655 for (int i = 0; i < oldmodels.length; i++)
657 for (int n = 0; n < modelfilenames.length; n++)
659 if (modelfilenames[n] == oldmodels[i])
665 if (oldmodels[i] != null)
672 String[] oldmfn = new String[oldm];
674 for (int i = 0; i < oldmodels.length; i++)
676 if (oldmodels[i] != null)
678 oldmfn[oldm++] = oldmodels[i];
681 // deregister the Jmol instance for these structures - we'll add
682 // ourselves again at the end for the current structure set.
683 getSsm().removeStructureViewerListener(this, oldmfn);
687 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
689 String fileName = modelfilenames[modelnum];
690 boolean foundEntry = false;
691 StructureFile pdb = null;
692 String pdbfile = null;
693 // model was probably loaded inline - so check the pdb file hashcode
696 // calculate essential attributes for the pdb data imported inline.
697 // prolly need to resolve modelnumber properly - for now just use our
699 pdbfile = jmolViewer.getData(
700 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
702 // search pdbentries and sequences to find correct pdbentry for this
704 for (int pe = 0; pe < getPdbCount(); pe++)
706 boolean matches = false;
707 addSequence(pe, getSequence()[pe]);
708 if (fileName == null)
711 // see JAL-623 - need method of matching pasted data up
713 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
714 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
715 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
722 File fl = new File(getPdbEntry(pe).getFile());
723 matches = fl.equals(new File(fileName));
727 // TODO: Jmol can in principle retrieve from CLASSLOADER but
730 // to be tested. See mantis bug
731 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
732 DataSourceType protocol = DataSourceType.URL;
737 protocol = DataSourceType.FILE;
739 } catch (Exception e)
744 // Explicitly map to the filename used by Jmol ;
745 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
746 fileName, protocol, getIProgressIndicator());
747 // pdbentry[pe].getFile(), protocol);
753 stashFoundChains(pdb, fileName);
758 if (!foundEntry && associateNewStructs)
760 // this is a foreign pdb file that jalview doesn't know about - add
761 // it to the dataset and try to find a home - either on a matching
762 // sequence or as a new sequence.
763 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
765 // parse pdb file into a chain, etc.
766 // locate best match for pdb in associated views and add mapping to
768 // if properly registered then
774 // so finally, update the jmol bits and pieces
775 // if (jmolpopup != null)
777 // // potential for deadlock here:
778 // // jmolpopup.updateComputedMenus();
780 if (!isLoadingFromArchive())
783 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
785 // register ourselves as a listener and notify the gui that it needs to
787 getSsm().addStructureViewerListener(this);
790 FeatureRenderer fr = getFeatureRenderer(null);
793 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
794 ((AppJmol) getViewer()).getAlignmentPanel().av
795 .applyFeaturesStyle(colours);
798 loadNotifiesHandled++;
800 setLoadingFromArchive(false);
803 protected IProgressIndicator getIProgressIndicator()
808 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
810 notifyAtomPicked(iatom, strMeasure, null);
813 public abstract void notifyScriptTermination(String strStatus,
817 * display a message echoed from the jmol viewer
821 public abstract void sendConsoleEcho(String strEcho); /*
822 * { showConsole(true);
824 * history.append("\n" + strEcho); }
827 // /End JmolStatusListener
828 // /////////////////////////////
832 * status message - usually the response received after a script
835 public abstract void sendConsoleMessage(String strStatus);
838 public void setCallbackFunction(String callbackType,
839 String callbackFunction)
841 System.err.println("Ignoring set-callback request to associate "
842 + callbackType + " with function " + callbackFunction);
846 public void showHelp()
848 showUrl("http://wiki.jmol.org"
849 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
854 * open the URL somehow
858 public abstract void showUrl(String url, String target);
861 * called to show or hide the associated console window container.
865 public abstract void showConsole(boolean show);
867 public static Viewer getJmolData(JmolParser jmolParser)
869 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
870 "-x -o -n", jmolParser);
879 * - when true will initialise jmol's file IO system (should be false
882 * @param documentBase
884 * @param commandOptions
886 public void allocateViewer(Container renderPanel, boolean jmolfileio,
887 String htmlName, URL documentBase, URL codeBase,
888 String commandOptions)
890 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
891 codeBase, commandOptions, null, null);
898 * - when true will initialise jmol's file IO system (should be false
901 * @param documentBase
903 * @param commandOptions
904 * @param consolePanel
905 * - panel to contain Jmol console
906 * @param buttonsToShow
907 * - buttons to show on the console, in order
909 public void allocateViewer(Container renderPanel, boolean jmolfileio,
910 String htmlName, URL documentBase, URL codeBase,
911 String commandOptions, final Container consolePanel,
912 String buttonsToShow)
915 System.err.println("Allocating Jmol Viewer: " + commandOptions);
917 if (commandOptions == null)
921 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
922 (jmolfileio ? new SmarterJmolAdapter() : null),
923 htmlName + ((Object) this).toString(), documentBase, codeBase,
924 commandOptions, this);
926 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
930 console = createJmolConsole(consolePanel, buttonsToShow);
931 } catch (Throwable e)
933 System.err.println("Could not create Jmol application console. "
937 if (consolePanel != null)
939 consolePanel.addComponentListener(this);
945 protected abstract JmolAppConsoleInterface createJmolConsole(
946 Container consolePanel, String buttonsToShow);
948 // BH 2018 -- Jmol console is not working due to problems with styled
951 protected org.jmol.api.JmolAppConsoleInterface console = null;
954 public int[] resizeInnerPanel(String data)
956 // Jalview doesn't honour resize panel requests
963 protected void closeConsole()
969 console.setVisible(false);
972 } catch (Exception x)
981 * ComponentListener method
984 public void componentMoved(ComponentEvent e)
989 * ComponentListener method
992 public void componentResized(ComponentEvent e)
997 * ComponentListener method
1000 public void componentShown(ComponentEvent e)
1006 * ComponentListener method
1009 public void componentHidden(ComponentEvent e)
1015 protected String getModelIdForFile(String pdbFile)
1017 if (modelFileNames == null)
1021 for (int i = 0; i < modelFileNames.length; i++)
1023 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1025 return String.valueOf(i + 1);
1032 protected ViewerType getViewerType()
1034 return ViewerType.JMOL;
1038 protected String getModelId(int pdbfnum, String file)
1040 return String.valueOf(pdbfnum + 1);
1044 * Returns ".spt" - the Jmol session file extension
1047 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1050 public String getSessionFileExtension()
1056 public void selectionChanged(BS arg0)
1058 // TODO Auto-generated method stub
1063 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1065 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1069 public String getHelpURL()
1071 return "http://wiki.jmol.org"; // BH 2018
1074 private boolean setGlobalUseScriptWait(boolean b)
1076 boolean prev = globalUseScriptWait;
1077 globalUseScriptWait = b;