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 org.jmol.adapter.smarter.SmarterJmolAdapter;
35 import org.jmol.api.JmolAppConsoleInterface;
36 import org.jmol.api.JmolSelectionListener;
37 import org.jmol.api.JmolStatusListener;
38 import org.jmol.api.JmolViewer;
39 import org.jmol.c.CBK;
40 import org.jmol.viewer.Viewer;
42 import jalview.api.FeatureRenderer;
43 import jalview.bin.Cache;
44 import jalview.datamodel.PDBEntry;
45 import jalview.datamodel.SequenceI;
46 import jalview.gui.IProgressIndicator;
47 import jalview.gui.StructureViewer.ViewerType;
48 import jalview.io.DataSourceType;
49 import jalview.io.StructureFile;
50 import jalview.structure.AtomSpec;
51 import jalview.structure.StructureCommand;
52 import jalview.structure.StructureCommandI;
53 import jalview.structure.StructureSelectionManager;
54 import jalview.structures.models.AAStructureBindingModel;
56 public abstract class JalviewJmolBinding extends AAStructureBindingModel
57 implements JmolStatusListener, JmolSelectionListener,
60 private String lastMessage;
63 * when true, try to search the associated datamodel for sequences that are
64 * associated with any unknown structures in the Jmol view.
66 private boolean associateNewStructs = false;
68 private Vector<String> atomsPicked = new Vector<>();
70 private String lastCommand;
72 private boolean loadedInline;
74 private StringBuffer resetLastRes = new StringBuffer();
76 public Viewer jmolViewer;
78 public JalviewJmolBinding(StructureSelectionManager ssm,
79 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
80 DataSourceType protocol)
82 super(ssm, pdbentry, sequenceIs, protocol);
83 setStructureCommands(new JmolCommands());
85 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
86 * "jalviewJmol", ap.av.applet .getDocumentBase(),
87 * ap.av.applet.getCodeBase(), "", this);
89 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
93 public JalviewJmolBinding(StructureSelectionManager ssm,
94 SequenceI[][] seqs, Viewer theViewer)
98 jmolViewer = theViewer;
99 jmolViewer.setJmolStatusListener(this);
100 jmolViewer.addSelectionListener(this);
101 setStructureCommands(new JmolCommands());
105 * construct a title string for the viewer window based on the data jalview
110 public String getViewerTitle()
112 return getViewerTitle("Jmol", true);
115 private String jmolScript(String script)
117 Cache.log.debug(">>Jmol>> " + script);
118 String s = jmolViewer.scriptWait(script);
119 Cache.log.debug("<<Jmol<< " + s);
125 public List<String> executeCommand(StructureCommandI command,
132 String cmd = command.getCommand();
134 if (lastCommand == null || !lastCommand.equals(cmd))
136 jmolScript(cmd + "\n");
143 public void createImage(String file, String type, int quality)
145 System.out.println("JMOL CREATE IMAGE");
149 public String createImage(String fileName, String type,
150 Object textOrBytes, int quality)
152 System.out.println("JMOL CREATE IMAGE");
157 public String eval(String strEval)
159 // System.out.println(strEval);
160 // "# 'eval' is implemented only for the applet.";
164 // End StructureListener
165 // //////////////////////////
168 public float[][] functionXY(String functionName, int x, int y)
174 public float[][][] functionXYZ(String functionName, int nx, int ny,
177 // TODO Auto-generated method stub
182 * map between index of model filename returned from getPdbFile and the first
183 * index of models from this file in the viewer. Note - this is not trimmed -
184 * use getPdbFile to get number of unique models.
186 private int _modelFileNameMap[];
189 public synchronized String[] getStructureFiles()
191 if (jmolViewer == null)
193 return new String[0];
196 if (modelFileNames == null)
198 int modelCount = jmolViewer.ms.mc;
199 String filePath = null;
200 List<String> mset = new ArrayList<>();
201 for (int i = 0; i < modelCount; ++i)
204 * defensive check for null as getModelFileName can return null
205 * even when model count ms.mc is > 0
207 filePath = jmolViewer.ms.getModelFileName(i);
208 if (filePath != null && !mset.contains(filePath))
215 modelFileNames = mset.toArray(new String[mset.size()]);
219 return modelFileNames;
223 * map from string to applet
226 public Map<String, Object> getRegistryInfo()
228 // TODO Auto-generated method stub
232 // ///////////////////////////////
233 // JmolStatusListener
235 public void handlePopupMenu(int x, int y)
237 // jmolpopup.show(x, y);
238 // jmolpopup.jpiShow(x, y);
242 * Highlight zero, one or more atoms on the structure
245 public void highlightAtoms(List<AtomSpec> atoms)
249 if (resetLastRes.length() > 0)
251 jmolScript(resetLastRes.toString());
252 resetLastRes.setLength(0);
254 for (AtomSpec atom : atoms)
256 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
257 atom.getChain(), atom.getPdbFile());
263 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
266 String modelId = getModelIdForFile(pdbfile);
267 if (modelId.isEmpty())
274 StringBuilder selection = new StringBuilder(32);
275 StringBuilder cmd = new StringBuilder(64);
276 selection.append("select ").append(String.valueOf(pdbResNum));
277 selection.append(":");
278 if (!chain.equals(" "))
280 selection.append(chain);
282 selection.append(" /").append(modelId);
284 cmd.append(selection).append(";wireframe 100;").append(selection)
285 .append(" and not hetero;").append("spacefill 200;select none");
287 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
288 .append(" and not hetero; spacefill 0;");
290 jmolScript(cmd.toString());
294 private boolean debug = true;
296 private void jmolHistory(boolean enable)
298 jmolScript("History " + ((debug || enable) ? "on" : "off"));
301 public void loadInline(String string)
305 // viewer.loadInline(strModel, isAppend);
307 // construct fake fullPathName and fileName so we can identify the file
309 // Then, construct pass a reader for the string to Jmol.
310 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
311 // fileName, null, reader, false, null, null, 0);
312 jmolViewer.openStringInline(string);
315 protected void mouseOverStructure(int atomIndex, final String strInfo)
318 int alocsep = strInfo.indexOf("^");
319 int mdlSep = strInfo.indexOf("/");
320 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
322 if (chainSeparator == -1)
324 chainSeparator = strInfo.indexOf(".");
325 if (mdlSep > -1 && mdlSep < chainSeparator)
327 chainSeparator1 = chainSeparator;
328 chainSeparator = mdlSep;
331 // handle insertion codes
334 pdbResNum = Integer.parseInt(
335 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
340 pdbResNum = Integer.parseInt(
341 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
345 if (strInfo.indexOf(":") > -1)
347 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
348 strInfo.indexOf("."));
355 String pdbfilename = modelFileNames[0]; // default is first model
358 if (chainSeparator1 == -1)
360 chainSeparator1 = strInfo.indexOf(".", mdlSep);
362 String mdlId = (chainSeparator1 > -1)
363 ? strInfo.substring(mdlSep + 1, chainSeparator1)
364 : strInfo.substring(mdlSep + 1);
367 // recover PDB filename for the model hovered over.
368 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
369 if (_modelFileNameMap != null)
371 int _mp = _modelFileNameMap.length - 1;
373 while (mnumber < _modelFileNameMap[_mp])
377 pdbfilename = modelFileNames[_mp];
381 if (mnumber >= 0 && mnumber < modelFileNames.length)
383 pdbfilename = modelFileNames[mnumber];
386 if (pdbfilename == null)
388 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
392 } catch (Exception e)
398 * highlight position on alignment(s); if some text is returned,
399 * show this as a second line on the structure hover tooltip
401 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
405 // change comma to pipe separator (newline token for Jmol)
406 label = label.replace(',', '|');
407 StringTokenizer toks = new StringTokenizer(strInfo, " ");
408 StringBuilder sb = new StringBuilder();
409 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
410 .append(chainId).append("/1");
411 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
412 .append(toks.nextToken());
413 sb.append("|").append(label).append("\"");
414 executeCommand(new StructureCommand(sb.toString()), false);
418 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
420 if (strInfo.equals(lastMessage))
424 lastMessage = strInfo;
427 System.err.println("Ignoring additional hover info: " + data
428 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
430 mouseOverStructure(atomIndex, strInfo);
434 * { if (history != null && strStatus != null &&
435 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
439 public void notifyAtomPicked(int atomIndex, String strInfo,
443 * this implements the toggle label behaviour copied from the original
444 * structure viewer, mc_view
448 System.err.println("Ignoring additional pick data string " + strData);
450 int chainSeparator = strInfo.indexOf(":");
452 if (chainSeparator == -1)
454 chainSeparator = strInfo.indexOf(".");
457 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
459 String mdlString = "";
460 if ((p = strInfo.indexOf(":")) > -1)
462 picked += strInfo.substring(p, strInfo.indexOf("."));
465 if ((p = strInfo.indexOf("/")) > -1)
467 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
469 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
473 if (!atomsPicked.contains(picked))
475 jmolScript("select " + picked + ";label %n %r:%c");
476 atomsPicked.addElement(picked);
480 jmolViewer.evalString("select " + picked + ";label off");
481 atomsPicked.removeElement(picked);
484 // TODO: in application this happens
486 // if (scriptWindow != null)
488 // scriptWindow.sendConsoleMessage(strInfo);
489 // scriptWindow.sendConsoleMessage("\n");
495 public void notifyCallback(CBK type, Object[] data)
502 notifyFileLoaded((String) data[1], (String) data[2],
503 (String) data[3], (String) data[4],
504 ((Integer) data[5]).intValue());
508 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
510 // also highlight in alignment
511 // deliberate fall through
513 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
517 notifyScriptTermination((String) data[2],
518 ((Integer) data[3]).intValue());
521 sendConsoleEcho((String) data[1]);
525 (data == null) ? ((String) null) : (String) data[1]);
528 // System.err.println("Ignoring error callback.");
539 "Unhandled callback " + type + " " + data[1].toString());
542 } catch (Exception e)
544 System.err.println("Squashed Jmol callback handler error:");
550 public boolean notifyEnabled(CBK callbackPick)
552 switch (callbackPick)
568 // incremented every time a load notification is successfully handled -
569 // lightweight mechanism for other threads to detect when they can start
570 // referrring to new structures.
571 private long loadNotifiesHandled = 0;
573 public long getLoadNotifiesHandled()
575 return loadNotifiesHandled;
578 public void notifyFileLoaded(String fullPathName, String fileName2,
579 String modelName, String errorMsg, int modelParts)
581 if (errorMsg != null)
583 fileLoadingError = errorMsg;
587 // TODO: deal sensibly with models loaded inLine:
588 // modelName will be null, as will fullPathName.
590 // the rest of this routine ignores the arguments, and simply interrogates
591 // the Jmol view to find out what structures it contains, and adds them to
592 // the structure selection manager.
593 fileLoadingError = null;
594 String[] oldmodels = modelFileNames;
595 modelFileNames = null;
596 boolean notifyLoaded = false;
597 String[] modelfilenames = getStructureFiles();
598 // first check if we've lost any structures
599 if (oldmodels != null && oldmodels.length > 0)
602 for (int i = 0; i < oldmodels.length; i++)
604 for (int n = 0; n < modelfilenames.length; n++)
606 if (modelfilenames[n] == oldmodels[i])
612 if (oldmodels[i] != null)
619 String[] oldmfn = new String[oldm];
621 for (int i = 0; i < oldmodels.length; i++)
623 if (oldmodels[i] != null)
625 oldmfn[oldm++] = oldmodels[i];
628 // deregister the Jmol instance for these structures - we'll add
629 // ourselves again at the end for the current structure set.
630 getSsm().removeStructureViewerListener(this, oldmfn);
634 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
636 String fileName = modelfilenames[modelnum];
637 boolean foundEntry = false;
638 StructureFile pdb = null;
639 String pdbfile = null;
640 // model was probably loaded inline - so check the pdb file hashcode
643 // calculate essential attributes for the pdb data imported inline.
644 // prolly need to resolve modelnumber properly - for now just use our
646 pdbfile = jmolViewer.getData(
647 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
649 // search pdbentries and sequences to find correct pdbentry for this
651 for (int pe = 0; pe < getPdbCount(); pe++)
653 boolean matches = false;
654 addSequence(pe, getSequence()[pe]);
655 if (fileName == null)
658 // see JAL-623 - need method of matching pasted data up
660 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
661 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
662 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
669 File fl = new File(getPdbEntry(pe).getFile());
670 matches = fl.equals(new File(fileName));
674 // TODO: Jmol can in principle retrieve from CLASSLOADER but
677 // to be tested. See mantis bug
678 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
679 DataSourceType protocol = DataSourceType.URL;
684 protocol = DataSourceType.FILE;
686 } catch (Exception e)
691 // Explicitly map to the filename used by Jmol ;
692 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
693 fileName, protocol, getIProgressIndicator());
694 // pdbentry[pe].getFile(), protocol);
700 stashFoundChains(pdb, fileName);
705 if (!foundEntry && associateNewStructs)
707 // this is a foreign pdb file that jalview doesn't know about - add
708 // it to the dataset and try to find a home - either on a matching
709 // sequence or as a new sequence.
710 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
712 // parse pdb file into a chain, etc.
713 // locate best match for pdb in associated views and add mapping to
715 // if properly registered then
721 // so finally, update the jmol bits and pieces
722 // if (jmolpopup != null)
724 // // potential for deadlock here:
725 // // jmolpopup.updateComputedMenus();
727 if (!isLoadingFromArchive())
730 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
732 // register ourselves as a listener and notify the gui that it needs to
734 getSsm().addStructureViewerListener(this);
737 FeatureRenderer fr = getFeatureRenderer(null);
743 loadNotifiesHandled++;
745 setLoadingFromArchive(false);
748 protected IProgressIndicator getIProgressIndicator()
753 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
755 notifyAtomPicked(iatom, strMeasure, null);
758 public abstract void notifyScriptTermination(String strStatus,
762 * display a message echoed from the jmol viewer
766 public abstract void sendConsoleEcho(String strEcho); /*
767 * { showConsole(true);
769 * history.append("\n" +
773 // /End JmolStatusListener
774 // /////////////////////////////
778 * status message - usually the response received after a script
781 public abstract void sendConsoleMessage(String strStatus);
784 public void setCallbackFunction(String callbackType,
785 String callbackFunction)
787 System.err.println("Ignoring set-callback request to associate "
788 + callbackType + " with function " + callbackFunction);
792 public void showHelp()
794 showUrl("http://wiki.jmol.org"
795 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
800 * open the URL somehow
804 public abstract void showUrl(String url, String target);
807 * called to show or hide the associated console window container.
811 public abstract void showConsole(boolean show);
813 public static Viewer getJmolData(JmolParser jmolParser)
815 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
816 "-x -o -n", jmolParser);
825 * - when true will initialise jmol's file IO system (should be false
828 * @param documentBase
830 * @param commandOptions
832 public void allocateViewer(Container renderPanel, boolean jmolfileio,
833 String htmlName, URL documentBase, URL codeBase,
834 String commandOptions)
836 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
837 codeBase, commandOptions, null, null);
844 * - when true will initialise jmol's file IO system (should be false
847 * @param documentBase
849 * @param commandOptions
850 * @param consolePanel
851 * - panel to contain Jmol console
852 * @param buttonsToShow
853 * - buttons to show on the console, in order
855 public void allocateViewer(Container renderPanel, boolean jmolfileio,
856 String htmlName, URL documentBase, URL codeBase,
857 String commandOptions, final Container consolePanel,
858 String buttonsToShow)
861 System.err.println("Allocating Jmol Viewer: " + commandOptions);
863 if (commandOptions == null)
867 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
868 (jmolfileio ? new SmarterJmolAdapter() : null),
869 htmlName + ((Object) this).toString(), documentBase, codeBase,
870 commandOptions, this);
872 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
876 console = createJmolConsole(consolePanel, buttonsToShow);
877 } catch (Throwable e)
879 System.err.println("Could not create Jmol application console. "
883 if (consolePanel != null)
885 consolePanel.addComponentListener(this);
891 protected abstract JmolAppConsoleInterface createJmolConsole(
892 Container consolePanel, String buttonsToShow);
894 // BH 2018 -- Jmol console is not working due to problems with styled
897 protected org.jmol.api.JmolAppConsoleInterface console = null;
900 public int[] resizeInnerPanel(String data)
902 // Jalview doesn't honour resize panel requests
909 protected void closeConsole()
915 console.setVisible(false);
918 } catch (Exception x)
927 * ComponentListener method
930 public void componentMoved(ComponentEvent e)
935 * ComponentListener method
938 public void componentResized(ComponentEvent e)
943 * ComponentListener method
946 public void componentShown(ComponentEvent e)
952 * ComponentListener method
955 public void componentHidden(ComponentEvent e)
961 protected String getModelIdForFile(String pdbFile)
963 if (modelFileNames == null)
967 for (int i = 0; i < modelFileNames.length; i++)
969 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
971 return String.valueOf(i + 1);
978 protected ViewerType getViewerType()
980 return ViewerType.JMOL;
984 protected String getModelId(int pdbfnum, String file)
986 return String.valueOf(pdbfnum + 1);
990 * Returns ".spt" - the Jmol session file extension
993 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
996 public String getSessionFileExtension()