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.Color;
24 import java.awt.Container;
25 import java.awt.event.ComponentEvent;
26 import java.awt.event.ComponentListener;
29 import java.util.ArrayList;
30 import java.util.List;
32 import java.util.StringTokenizer;
33 import java.util.Vector;
35 import org.jmol.adapter.smarter.SmarterJmolAdapter;
36 import org.jmol.api.JmolAppConsoleInterface;
37 import org.jmol.api.JmolSelectionListener;
38 import org.jmol.api.JmolStatusListener;
39 import org.jmol.api.JmolViewer;
40 import org.jmol.c.CBK;
41 import org.jmol.viewer.Viewer;
43 import jalview.api.AlignmentViewPanel;
44 import jalview.api.FeatureRenderer;
45 import jalview.api.FeatureSettingsModelI;
46 import jalview.api.SequenceRenderer;
47 import jalview.bin.Cache;
48 import jalview.datamodel.PDBEntry;
49 import jalview.datamodel.SequenceI;
50 import jalview.gui.AppJmol;
51 import jalview.gui.IProgressIndicator;
52 import jalview.gui.StructureViewer.ViewerType;
53 import jalview.io.DataSourceType;
54 import jalview.io.StructureFile;
55 import jalview.structure.AtomSpec;
56 import jalview.structure.StructureCommand;
57 import jalview.structure.StructureCommandI;
58 import jalview.structure.StructureSelectionManager;
59 import jalview.structures.models.AAStructureBindingModel;
60 import jalview.ws.dbsources.Pdb;
61 import javajs.util.BS;
63 public abstract class JalviewJmolBinding extends AAStructureBindingModel
64 implements JmolStatusListener, JmolSelectionListener,
67 private String lastMessage;
70 * when true, try to search the associated datamodel for sequences that are
71 * associated with any unknown structures in the Jmol view.
73 private boolean associateNewStructs = false;
75 private Vector<String> atomsPicked = new Vector<>();
77 private String lastCommand;
79 private boolean loadedInline;
81 private StringBuffer resetLastRes = new StringBuffer();
83 public Viewer jmolViewer;
85 public JalviewJmolBinding(StructureSelectionManager ssm,
86 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
87 DataSourceType protocol)
89 super(ssm, pdbentry, sequenceIs, protocol);
90 setStructureCommands(new JmolCommands());
92 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
93 * "jalviewJmol", ap.av.applet .getDocumentBase(),
94 * ap.av.applet.getCodeBase(), "", this);
96 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
100 public JalviewJmolBinding(StructureSelectionManager ssm,
101 SequenceI[][] seqs, Viewer theViewer)
105 jmolViewer = theViewer;
106 jmolViewer.setJmolStatusListener(this);
107 jmolViewer.addSelectionListener(this);
108 setStructureCommands(new JmolCommands());
112 * construct a title string for the viewer window based on the data jalview
117 public String getViewerTitle()
119 return getViewerTitle("Jmol", true);
122 private String jmolScript(String script)
124 Cache.log.debug(">>Jmol>> " + script);
125 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
126 Cache.log.debug("<<Jmol<< " + s);
132 public List<String> executeCommand(StructureCommandI command,
139 String cmd = command.getCommand();
141 if (lastCommand == null || !lastCommand.equals(cmd))
143 jmolScript(cmd + "\n");
150 public void createImage(String file, String type, int quality)
152 System.out.println("JMOL CREATE IMAGE");
156 public String createImage(String fileName, String type,
157 Object textOrBytes, int quality)
159 System.out.println("JMOL CREATE IMAGE");
164 public String eval(String strEval)
166 // System.out.println(strEval);
167 // "# 'eval' is implemented only for the applet.";
171 // End StructureListener
172 // //////////////////////////
175 public float[][] functionXY(String functionName, int x, int y)
181 public float[][][] functionXYZ(String functionName, int nx, int ny,
184 // TODO Auto-generated method stub
189 * map between index of model filename returned from getPdbFile and the first
190 * index of models from this file in the viewer. Note - this is not trimmed -
191 * use getPdbFile to get number of unique models.
193 private int _modelFileNameMap[];
196 public synchronized String[] getStructureFiles()
198 if (jmolViewer == null)
200 return new String[0];
203 if (modelFileNames == null)
205 int modelCount = jmolViewer.ms.mc;
206 String filePath = null;
207 List<String> mset = new ArrayList<>();
208 for (int i = 0; i < modelCount; ++i)
211 * defensive check for null as getModelFileName can return null
212 * even when model count ms.mc is > 0
214 filePath = jmolViewer.ms.getModelFileName(i);
215 if (filePath != null && !mset.contains(filePath))
222 modelFileNames = mset.toArray(new String[mset.size()]);
226 return modelFileNames;
230 * map from string to applet
233 public Map<String, Object> getRegistryInfo()
235 // TODO Auto-generated method stub
239 // ///////////////////////////////
240 // JmolStatusListener
242 public void handlePopupMenu(int x, int y)
244 // jmolpopup.show(x, y);
245 // jmolpopup.jpiShow(x, y);
249 * Highlight zero, one or more atoms on the structure
252 public void highlightAtoms(List<AtomSpec> atoms)
256 if (resetLastRes.length() > 0)
258 jmolScript(resetLastRes.toString());
259 resetLastRes.setLength(0);
261 for (AtomSpec atom : atoms)
263 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
264 atom.getChain(), atom.getPdbFile());
270 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
273 String modelId = getModelIdForFile(pdbfile);
274 if (modelId.isEmpty())
281 StringBuilder selection = new StringBuilder(32);
282 StringBuilder cmd = new StringBuilder(64);
283 selection.append("select ").append(String.valueOf(pdbResNum));
284 selection.append(":");
285 if (!chain.equals(" "))
287 selection.append(chain);
289 selection.append(" /").append(modelId);
291 cmd.append(selection).append(";wireframe 100;").append(selection)
292 .append(" and not hetero;").append("spacefill 200;select none");
294 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
295 .append(" and not hetero; spacefill 0;");
297 jmolScript(cmd.toString());
301 private boolean debug = true;
303 private void jmolHistory(boolean enable)
305 jmolScript("History " + ((debug || enable) ? "on" : "off"));
308 public void loadInline(String string)
312 // viewer.loadInline(strModel, isAppend);
314 // construct fake fullPathName and fileName so we can identify the file
316 // Then, construct pass a reader for the string to Jmol.
317 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
318 // fileName, null, reader, false, null, null, 0);
319 jmolViewer.openStringInline(string);
322 protected void mouseOverStructure(int atomIndex, final String strInfo)
325 int alocsep = strInfo.indexOf("^");
326 int mdlSep = strInfo.indexOf("/");
327 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
329 if (chainSeparator == -1)
331 chainSeparator = strInfo.indexOf(".");
332 if (mdlSep > -1 && mdlSep < chainSeparator)
334 chainSeparator1 = chainSeparator;
335 chainSeparator = mdlSep;
338 // handle insertion codes
341 pdbResNum = Integer.parseInt(
342 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
347 pdbResNum = Integer.parseInt(
348 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
352 if (strInfo.indexOf(":") > -1)
354 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
355 strInfo.indexOf("."));
362 String pdbfilename = modelFileNames[0]; // default is first model
365 if (chainSeparator1 == -1)
367 chainSeparator1 = strInfo.indexOf(".", mdlSep);
369 String mdlId = (chainSeparator1 > -1)
370 ? strInfo.substring(mdlSep + 1, chainSeparator1)
371 : strInfo.substring(mdlSep + 1);
374 // recover PDB filename for the model hovered over.
375 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
376 if (_modelFileNameMap != null)
378 int _mp = _modelFileNameMap.length - 1;
380 while (mnumber < _modelFileNameMap[_mp])
384 pdbfilename = modelFileNames[_mp];
388 if (mnumber >= 0 && mnumber < modelFileNames.length)
390 pdbfilename = modelFileNames[mnumber];
393 if (pdbfilename == null)
395 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
399 } catch (Exception e)
405 * highlight position on alignment(s); if some text is returned,
406 * show this as a second line on the structure hover tooltip
408 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
412 // change comma to pipe separator (newline token for Jmol)
413 label = label.replace(',', '|');
414 StringTokenizer toks = new StringTokenizer(strInfo, " ");
415 StringBuilder sb = new StringBuilder();
416 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
417 .append(chainId).append("/1");
418 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
419 .append(toks.nextToken());
420 sb.append("|").append(label).append("\"");
421 executeCommand(new StructureCommand(sb.toString()), false);
425 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
427 if (strInfo.equals(lastMessage))
431 lastMessage = strInfo;
434 System.err.println("Ignoring additional hover info: " + data
435 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
437 mouseOverStructure(atomIndex, strInfo);
441 * { if (history != null && strStatus != null &&
442 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
446 public void notifyAtomPicked(int atomIndex, String strInfo,
450 * this implements the toggle label behaviour copied from the original
451 * structure viewer, mc_view
455 System.err.println("Ignoring additional pick data string " + strData);
457 int chainSeparator = strInfo.indexOf(":");
459 if (chainSeparator == -1)
461 chainSeparator = strInfo.indexOf(".");
464 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
466 String mdlString = "";
467 if ((p = strInfo.indexOf(":")) > -1)
469 picked += strInfo.substring(p, strInfo.indexOf("."));
472 if ((p = strInfo.indexOf("/")) > -1)
474 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
476 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
480 if (!atomsPicked.contains(picked))
482 jmolScript("select " + picked + ";label %n %r:%c");
483 atomsPicked.addElement(picked);
487 jmolViewer.evalString("select " + picked + ";label off");
488 atomsPicked.removeElement(picked);
491 // TODO: in application this happens
493 // if (scriptWindow != null)
495 // scriptWindow.sendConsoleMessage(strInfo);
496 // scriptWindow.sendConsoleMessage("\n");
502 public void notifyCallback(CBK type, Object[] data)
509 notifyFileLoaded((String) data[1], (String) data[2],
510 (String) data[3], (String) data[4],
511 ((Integer) data[5]).intValue());
515 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
517 // also highlight in alignment
518 // deliberate fall through
520 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
524 notifyScriptTermination((String) data[2],
525 ((Integer) data[3]).intValue());
528 sendConsoleEcho((String) data[1]);
532 (data == null) ? ((String) null) : (String) data[1]);
535 // System.err.println("Ignoring error callback.");
546 "Unhandled callback " + type + " " + data[1].toString());
549 } catch (Exception e)
551 System.err.println("Squashed Jmol callback handler error:");
557 public boolean notifyEnabled(CBK callbackPick)
559 switch (callbackPick)
575 // incremented every time a load notification is successfully handled -
576 // lightweight mechanism for other threads to detect when they can start
577 // referrring to new structures.
578 private long loadNotifiesHandled = 0;
580 public long getLoadNotifiesHandled()
582 return loadNotifiesHandled;
585 public void notifyFileLoaded(String fullPathName, String fileName2,
586 String modelName, String errorMsg, int modelParts)
588 if (errorMsg != null)
590 fileLoadingError = errorMsg;
594 // TODO: deal sensibly with models loaded inLine:
595 // modelName will be null, as will fullPathName.
597 // the rest of this routine ignores the arguments, and simply interrogates
598 // the Jmol view to find out what structures it contains, and adds them to
599 // the structure selection manager.
600 fileLoadingError = null;
601 String[] oldmodels = modelFileNames;
602 modelFileNames = null;
603 boolean notifyLoaded = false;
604 String[] modelfilenames = getStructureFiles();
605 if (modelfilenames == null)
607 // Jmol is still loading files!
610 // first check if we've lost any structures
611 if (oldmodels != null && oldmodels.length > 0)
614 for (int i = 0; i < oldmodels.length; i++)
616 for (int n = 0; n < modelfilenames.length; n++)
618 if (modelfilenames[n] == oldmodels[i])
624 if (oldmodels[i] != null)
631 String[] oldmfn = new String[oldm];
633 for (int i = 0; i < oldmodels.length; i++)
635 if (oldmodels[i] != null)
637 oldmfn[oldm++] = oldmodels[i];
640 // deregister the Jmol instance for these structures - we'll add
641 // ourselves again at the end for the current structure set.
642 getSsm().removeStructureViewerListener(this, oldmfn);
646 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
648 String fileName = modelfilenames[modelnum];
649 boolean foundEntry = false;
650 StructureFile pdb = null;
651 String pdbfile = null;
652 // model was probably loaded inline - so check the pdb file hashcode
655 // calculate essential attributes for the pdb data imported inline.
656 // prolly need to resolve modelnumber properly - for now just use our
658 pdbfile = jmolViewer.getData(
659 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
661 // search pdbentries and sequences to find correct pdbentry for this
663 for (int pe = 0; pe < getPdbCount(); pe++)
665 boolean matches = false;
666 addSequence(pe, getSequence()[pe]);
667 if (fileName == null)
670 // see JAL-623 - need method of matching pasted data up
672 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
673 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
674 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
681 File fl = new File(getPdbEntry(pe).getFile());
682 matches = fl.equals(new File(fileName));
686 // TODO: Jmol can in principle retrieve from CLASSLOADER but
689 // to be tested. See mantis bug
690 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
691 DataSourceType protocol = DataSourceType.URL;
696 protocol = DataSourceType.FILE;
698 } catch (Exception e)
703 // Explicitly map to the filename used by Jmol ;
704 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
705 fileName, protocol, getIProgressIndicator());
706 // pdbentry[pe].getFile(), protocol);
712 stashFoundChains(pdb, fileName);
717 if (!foundEntry && associateNewStructs)
719 // this is a foreign pdb file that jalview doesn't know about - add
720 // it to the dataset and try to find a home - either on a matching
721 // sequence or as a new sequence.
722 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
724 // parse pdb file into a chain, etc.
725 // locate best match for pdb in associated views and add mapping to
727 // if properly registered then
733 // so finally, update the jmol bits and pieces
734 // if (jmolpopup != null)
736 // // potential for deadlock here:
737 // // jmolpopup.updateComputedMenus();
739 if (!isLoadingFromArchive())
742 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
744 // register ourselves as a listener and notify the gui that it needs to
746 getSsm().addStructureViewerListener(this);
749 FeatureRenderer fr = getFeatureRenderer(null);
752 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
753 ((AppJmol) getViewer()).getAlignmentPanel().av
754 .applyFeaturesStyle(colours);
757 loadNotifiesHandled++;
759 setLoadingFromArchive(false);
762 protected IProgressIndicator getIProgressIndicator()
767 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
769 notifyAtomPicked(iatom, strMeasure, null);
772 public abstract void notifyScriptTermination(String strStatus,
776 * display a message echoed from the jmol viewer
780 public abstract void sendConsoleEcho(String strEcho); /*
781 * { showConsole(true);
783 * history.append("\n" +
787 // /End JmolStatusListener
788 // /////////////////////////////
792 * status message - usually the response received after a script
795 public abstract void sendConsoleMessage(String strStatus);
798 public void setCallbackFunction(String callbackType,
799 String callbackFunction)
801 System.err.println("Ignoring set-callback request to associate "
802 + callbackType + " with function " + callbackFunction);
806 public void showHelp()
808 showUrl("http://wiki.jmol.org"
809 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
814 * open the URL somehow
818 public abstract void showUrl(String url, String target);
821 * called to show or hide the associated console window container.
825 public abstract void showConsole(boolean show);
827 public static Viewer getJmolData(JmolParser jmolParser)
829 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
830 "-x -o -n", jmolParser);
839 * - when true will initialise jmol's file IO system (should be false
842 * @param documentBase
844 * @param commandOptions
846 public void allocateViewer(Container renderPanel, boolean jmolfileio,
847 String htmlName, URL documentBase, URL codeBase,
848 String commandOptions)
850 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
851 codeBase, commandOptions, null, null);
858 * - when true will initialise jmol's file IO system (should be false
861 * @param documentBase
863 * @param commandOptions
864 * @param consolePanel
865 * - panel to contain Jmol console
866 * @param buttonsToShow
867 * - buttons to show on the console, in order
869 public void allocateViewer(Container renderPanel, boolean jmolfileio,
870 String htmlName, URL documentBase, URL codeBase,
871 String commandOptions, final Container consolePanel,
872 String buttonsToShow)
875 System.err.println("Allocating Jmol Viewer: " + commandOptions);
877 if (commandOptions == null)
881 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
882 (jmolfileio ? new SmarterJmolAdapter() : null),
883 htmlName + ((Object) this).toString(), documentBase, codeBase,
884 commandOptions, this);
886 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
890 console = createJmolConsole(consolePanel, buttonsToShow);
891 } catch (Throwable e)
893 System.err.println("Could not create Jmol application console. "
897 if (consolePanel != null)
899 consolePanel.addComponentListener(this);
905 protected abstract JmolAppConsoleInterface createJmolConsole(
906 Container consolePanel, String buttonsToShow);
908 // BH 2018 -- Jmol console is not working due to problems with styled
911 protected org.jmol.api.JmolAppConsoleInterface console = null;
914 public int[] resizeInnerPanel(String data)
916 // Jalview doesn't honour resize panel requests
923 protected void closeConsole()
929 console.setVisible(false);
932 } catch (Exception x)
941 * ComponentListener method
944 public void componentMoved(ComponentEvent e)
949 * ComponentListener method
952 public void componentResized(ComponentEvent e)
957 * ComponentListener method
960 public void componentShown(ComponentEvent e)
966 * ComponentListener method
969 public void componentHidden(ComponentEvent e)
975 protected String getModelIdForFile(String pdbFile)
977 if (modelFileNames == null)
981 for (int i = 0; i < modelFileNames.length; i++)
983 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
985 return String.valueOf(i + 1);
992 protected ViewerType getViewerType()
994 return ViewerType.JMOL;
998 protected String getModelId(int pdbfnum, String file)
1000 return String.valueOf(pdbfnum + 1);
1004 * Returns ".spt" - the Jmol session file extension
1007 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1010 public String getSessionFileExtension()
1016 public void selectionChanged(BS arg0)
1018 // TODO Auto-generated method stub
1023 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1025 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1029 public String getHelpURL()
1031 return "http://wiki.jmol.org"; // BH 2018