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.evalStringQuiet(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))
213 modelFileNames = mset.toArray(new String[mset.size()]);
216 return modelFileNames;
220 * map from string to applet
223 public Map<String, Object> getRegistryInfo()
225 // TODO Auto-generated method stub
229 // ///////////////////////////////
230 // JmolStatusListener
232 public void handlePopupMenu(int x, int y)
234 // jmolpopup.show(x, y);
235 // jmolpopup.jpiShow(x, y);
239 * Highlight zero, one or more atoms on the structure
242 public void highlightAtoms(List<AtomSpec> atoms)
246 if (resetLastRes.length() > 0)
248 jmolScript(resetLastRes.toString());
249 resetLastRes.setLength(0);
251 for (AtomSpec atom : atoms)
253 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
254 atom.getChain(), atom.getPdbFile());
260 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
263 String modelId = getModelIdForFile(pdbfile);
264 if (modelId.isEmpty())
271 StringBuilder selection = new StringBuilder(32);
272 StringBuilder cmd = new StringBuilder(64);
273 selection.append("select ").append(String.valueOf(pdbResNum));
274 selection.append(":");
275 if (!chain.equals(" "))
277 selection.append(chain);
279 selection.append(" /").append(modelId);
281 cmd.append(selection).append(";wireframe 100;").append(selection)
282 .append(" and not hetero;").append("spacefill 200;select none");
284 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
285 .append(" and not hetero; spacefill 0;");
287 jmolScript(cmd.toString());
291 private boolean debug = true;
293 private void jmolHistory(boolean enable)
295 jmolScript("History " + ((debug || enable) ? "on" : "off"));
298 public void loadInline(String string)
302 // viewer.loadInline(strModel, isAppend);
304 // construct fake fullPathName and fileName so we can identify the file
306 // Then, construct pass a reader for the string to Jmol.
307 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
308 // fileName, null, reader, false, null, null, 0);
309 jmolViewer.openStringInline(string);
312 protected void mouseOverStructure(int atomIndex, final String strInfo)
315 int alocsep = strInfo.indexOf("^");
316 int mdlSep = strInfo.indexOf("/");
317 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
319 if (chainSeparator == -1)
321 chainSeparator = strInfo.indexOf(".");
322 if (mdlSep > -1 && mdlSep < chainSeparator)
324 chainSeparator1 = chainSeparator;
325 chainSeparator = mdlSep;
328 // handle insertion codes
331 pdbResNum = Integer.parseInt(
332 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
337 pdbResNum = Integer.parseInt(
338 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
342 if (strInfo.indexOf(":") > -1)
344 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
345 strInfo.indexOf("."));
352 String pdbfilename = modelFileNames[0]; // default is first model
355 if (chainSeparator1 == -1)
357 chainSeparator1 = strInfo.indexOf(".", mdlSep);
359 String mdlId = (chainSeparator1 > -1)
360 ? strInfo.substring(mdlSep + 1, chainSeparator1)
361 : strInfo.substring(mdlSep + 1);
364 // recover PDB filename for the model hovered over.
365 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
366 if (_modelFileNameMap != null)
368 int _mp = _modelFileNameMap.length - 1;
370 while (mnumber < _modelFileNameMap[_mp])
374 pdbfilename = modelFileNames[_mp];
378 if (mnumber >= 0 && mnumber < modelFileNames.length)
380 pdbfilename = modelFileNames[mnumber];
383 if (pdbfilename == null)
385 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
389 } catch (Exception e)
395 * highlight position on alignment(s); if some text is returned,
396 * show this as a second line on the structure hover tooltip
398 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
402 // change comma to pipe separator (newline token for Jmol)
403 label = label.replace(',', '|');
404 StringTokenizer toks = new StringTokenizer(strInfo, " ");
405 StringBuilder sb = new StringBuilder();
406 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
407 .append(chainId).append("/1");
408 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
409 .append(toks.nextToken());
410 sb.append("|").append(label).append("\"");
411 executeCommand(new StructureCommand(sb.toString()), false);
415 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
417 if (strInfo.equals(lastMessage))
421 lastMessage = strInfo;
424 System.err.println("Ignoring additional hover info: " + data
425 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
427 mouseOverStructure(atomIndex, strInfo);
431 * { if (history != null && strStatus != null &&
432 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
436 public void notifyAtomPicked(int atomIndex, String strInfo,
440 * this implements the toggle label behaviour copied from the original
441 * structure viewer, mc_view
445 System.err.println("Ignoring additional pick data string " + strData);
447 int chainSeparator = strInfo.indexOf(":");
449 if (chainSeparator == -1)
451 chainSeparator = strInfo.indexOf(".");
454 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
456 String mdlString = "";
457 if ((p = strInfo.indexOf(":")) > -1)
459 picked += strInfo.substring(p, strInfo.indexOf("."));
462 if ((p = strInfo.indexOf("/")) > -1)
464 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
466 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
470 if (!atomsPicked.contains(picked))
472 jmolScript("select " + picked + ";label %n %r:%c");
473 atomsPicked.addElement(picked);
477 jmolViewer.evalString("select " + picked + ";label off");
478 atomsPicked.removeElement(picked);
481 // TODO: in application this happens
483 // if (scriptWindow != null)
485 // scriptWindow.sendConsoleMessage(strInfo);
486 // scriptWindow.sendConsoleMessage("\n");
492 public void notifyCallback(CBK type, Object[] data)
499 notifyFileLoaded((String) data[1], (String) data[2],
500 (String) data[3], (String) data[4],
501 ((Integer) data[5]).intValue());
505 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
507 // also highlight in alignment
508 // deliberate fall through
510 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
514 notifyScriptTermination((String) data[2],
515 ((Integer) data[3]).intValue());
518 sendConsoleEcho((String) data[1]);
522 (data == null) ? ((String) null) : (String) data[1]);
525 // System.err.println("Ignoring error callback.");
536 "Unhandled callback " + type + " " + data[1].toString());
539 } catch (Exception e)
541 System.err.println("Squashed Jmol callback handler error:");
547 public boolean notifyEnabled(CBK callbackPick)
549 switch (callbackPick)
565 // incremented every time a load notification is successfully handled -
566 // lightweight mechanism for other threads to detect when they can start
567 // referrring to new structures.
568 private long loadNotifiesHandled = 0;
570 public long getLoadNotifiesHandled()
572 return loadNotifiesHandled;
575 public void notifyFileLoaded(String fullPathName, String fileName2,
576 String modelName, String errorMsg, int modelParts)
578 if (errorMsg != null)
580 fileLoadingError = errorMsg;
584 // TODO: deal sensibly with models loaded inLine:
585 // modelName will be null, as will fullPathName.
587 // the rest of this routine ignores the arguments, and simply interrogates
588 // the Jmol view to find out what structures it contains, and adds them to
589 // the structure selection manager.
590 fileLoadingError = null;
591 String[] oldmodels = modelFileNames;
592 modelFileNames = null;
593 boolean notifyLoaded = false;
594 String[] modelfilenames = getStructureFiles();
595 // first check if we've lost any structures
596 if (oldmodels != null && oldmodels.length > 0)
599 for (int i = 0; i < oldmodels.length; i++)
601 for (int n = 0; n < modelfilenames.length; n++)
603 if (modelfilenames[n] == oldmodels[i])
609 if (oldmodels[i] != null)
616 String[] oldmfn = new String[oldm];
618 for (int i = 0; i < oldmodels.length; i++)
620 if (oldmodels[i] != null)
622 oldmfn[oldm++] = oldmodels[i];
625 // deregister the Jmol instance for these structures - we'll add
626 // ourselves again at the end for the current structure set.
627 getSsm().removeStructureViewerListener(this, oldmfn);
631 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
633 String fileName = modelfilenames[modelnum];
634 boolean foundEntry = false;
635 StructureFile pdb = null;
636 String pdbfile = null;
637 // model was probably loaded inline - so check the pdb file hashcode
640 // calculate essential attributes for the pdb data imported inline.
641 // prolly need to resolve modelnumber properly - for now just use our
643 pdbfile = jmolViewer.getData(
644 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
646 // search pdbentries and sequences to find correct pdbentry for this
648 for (int pe = 0; pe < getPdbCount(); pe++)
650 boolean matches = false;
651 addSequence(pe, getSequence()[pe]);
652 if (fileName == null)
655 // see JAL-623 - need method of matching pasted data up
657 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
658 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
659 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
666 File fl = new File(getPdbEntry(pe).getFile());
667 matches = fl.equals(new File(fileName));
671 // TODO: Jmol can in principle retrieve from CLASSLOADER but
674 // to be tested. See mantis bug
675 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
676 DataSourceType protocol = DataSourceType.URL;
681 protocol = DataSourceType.FILE;
683 } catch (Exception e)
688 // Explicitly map to the filename used by Jmol ;
689 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
690 fileName, protocol, getIProgressIndicator());
691 // pdbentry[pe].getFile(), protocol);
697 stashFoundChains(pdb, fileName);
702 if (!foundEntry && associateNewStructs)
704 // this is a foreign pdb file that jalview doesn't know about - add
705 // it to the dataset and try to find a home - either on a matching
706 // sequence or as a new sequence.
707 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
709 // parse pdb file into a chain, etc.
710 // locate best match for pdb in associated views and add mapping to
712 // if properly registered then
718 // so finally, update the jmol bits and pieces
719 // if (jmolpopup != null)
721 // // potential for deadlock here:
722 // // jmolpopup.updateComputedMenus();
724 if (!isLoadingFromArchive())
727 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
729 // register ourselves as a listener and notify the gui that it needs to
731 getSsm().addStructureViewerListener(this);
734 FeatureRenderer fr = getFeatureRenderer(null);
740 loadNotifiesHandled++;
742 setLoadingFromArchive(false);
745 protected IProgressIndicator getIProgressIndicator()
750 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
752 notifyAtomPicked(iatom, strMeasure, null);
755 public abstract void notifyScriptTermination(String strStatus,
759 * display a message echoed from the jmol viewer
763 public abstract void sendConsoleEcho(String strEcho); /*
764 * { showConsole(true);
766 * history.append("\n" +
770 // /End JmolStatusListener
771 // /////////////////////////////
775 * status message - usually the response received after a script
778 public abstract void sendConsoleMessage(String strStatus);
781 public void setCallbackFunction(String callbackType,
782 String callbackFunction)
784 System.err.println("Ignoring set-callback request to associate "
785 + callbackType + " with function " + callbackFunction);
789 public void showHelp()
791 showUrl("http://wiki.jmol.org"
792 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
797 * open the URL somehow
801 public abstract void showUrl(String url, String target);
804 * called to show or hide the associated console window container.
808 public abstract void showConsole(boolean show);
810 public static Viewer getJmolData(JmolParser jmolParser)
812 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
813 "-x -o -n", jmolParser);
822 * - when true will initialise jmol's file IO system (should be false
825 * @param documentBase
827 * @param commandOptions
829 public void allocateViewer(Container renderPanel, boolean jmolfileio,
830 String htmlName, URL documentBase, URL codeBase,
831 String commandOptions)
833 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
834 codeBase, commandOptions, null, null);
841 * - when true will initialise jmol's file IO system (should be false
844 * @param documentBase
846 * @param commandOptions
847 * @param consolePanel
848 * - panel to contain Jmol console
849 * @param buttonsToShow
850 * - buttons to show on the console, in order
852 public void allocateViewer(Container renderPanel, boolean jmolfileio,
853 String htmlName, URL documentBase, URL codeBase,
854 String commandOptions, final Container consolePanel,
855 String buttonsToShow)
858 System.err.println("Allocating Jmol Viewer: " + commandOptions);
860 if (commandOptions == null)
864 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
865 (jmolfileio ? new SmarterJmolAdapter() : null),
866 htmlName + ((Object) this).toString(), documentBase, codeBase,
867 commandOptions, this);
869 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
873 console = createJmolConsole(consolePanel, buttonsToShow);
874 } catch (Throwable e)
876 System.err.println("Could not create Jmol application console. "
880 if (consolePanel != null)
882 consolePanel.addComponentListener(this);
888 protected abstract JmolAppConsoleInterface createJmolConsole(
889 Container consolePanel, String buttonsToShow);
891 // BH 2018 -- Jmol console is not working due to problems with styled
894 protected org.jmol.api.JmolAppConsoleInterface console = null;
897 public int[] resizeInnerPanel(String data)
899 // Jalview doesn't honour resize panel requests
906 protected void closeConsole()
912 console.setVisible(false);
915 } catch (Exception x)
924 * ComponentListener method
927 public void componentMoved(ComponentEvent e)
932 * ComponentListener method
935 public void componentResized(ComponentEvent e)
940 * ComponentListener method
943 public void componentShown(ComponentEvent e)
949 * ComponentListener method
952 public void componentHidden(ComponentEvent e)
958 protected String getModelIdForFile(String pdbFile)
960 if (modelFileNames == null)
964 for (int i = 0; i < modelFileNames.length; i++)
966 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
968 return String.valueOf(i + 1);
975 protected ViewerType getViewerType()
977 return ViewerType.JMOL;
981 protected String getModelId(int pdbfnum, String file)
983 return String.valueOf(pdbfnum + 1);
987 * Returns ".spt" - the Jmol session file extension
990 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
993 public String getSessionFileExtension()