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.AlignmentViewPanel;
43 import jalview.api.FeatureRenderer;
44 import jalview.api.SequenceRenderer;
45 import jalview.bin.Cache;
46 import jalview.datamodel.PDBEntry;
47 import jalview.datamodel.SequenceI;
48 import jalview.gui.IProgressIndicator;
49 import jalview.gui.StructureViewer.ViewerType;
50 import jalview.io.DataSourceType;
51 import jalview.io.StructureFile;
52 import jalview.structure.AtomSpec;
53 import jalview.structure.StructureCommand;
54 import jalview.structure.StructureCommandI;
55 import jalview.structure.StructureSelectionManager;
56 import jalview.structures.models.AAStructureBindingModel;
57 import javajs.util.BS;
59 public abstract class JalviewJmolBinding extends AAStructureBindingModel
60 implements JmolStatusListener, JmolSelectionListener,
63 private String lastMessage;
66 * when true, try to search the associated datamodel for sequences that are
67 * associated with any unknown structures in the Jmol view.
69 private boolean associateNewStructs = false;
71 private Vector<String> atomsPicked = new Vector<>();
73 private String lastCommand;
75 private boolean loadedInline;
77 private StringBuffer resetLastRes = new StringBuffer();
79 public Viewer jmolViewer;
81 public JalviewJmolBinding(StructureSelectionManager ssm,
82 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
83 DataSourceType protocol)
85 super(ssm, pdbentry, sequenceIs, protocol);
86 setStructureCommands(new JmolCommands());
88 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
89 * "jalviewJmol", ap.av.applet .getDocumentBase(),
90 * ap.av.applet.getCodeBase(), "", this);
92 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
96 public JalviewJmolBinding(StructureSelectionManager ssm,
97 SequenceI[][] seqs, Viewer theViewer)
101 jmolViewer = theViewer;
102 jmolViewer.setJmolStatusListener(this);
103 jmolViewer.addSelectionListener(this);
104 setStructureCommands(new JmolCommands());
108 * construct a title string for the viewer window based on the data jalview
113 public String getViewerTitle()
115 return getViewerTitle("Jmol", true);
118 private String jmolScript(String script)
120 Cache.log.debug(">>Jmol>> " + script);
121 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
122 Cache.log.debug("<<Jmol<< " + s);
128 public List<String> executeCommand(StructureCommandI command,
135 String cmd = command.getCommand();
137 if (lastCommand == null || !lastCommand.equals(cmd))
139 jmolScript(cmd + "\n");
146 public void createImage(String file, String type, int quality)
148 System.out.println("JMOL CREATE IMAGE");
152 public String createImage(String fileName, String type,
153 Object textOrBytes, int quality)
155 System.out.println("JMOL CREATE IMAGE");
160 public String eval(String strEval)
162 // System.out.println(strEval);
163 // "# 'eval' is implemented only for the applet.";
167 // End StructureListener
168 // //////////////////////////
171 public float[][] functionXY(String functionName, int x, int y)
177 public float[][][] functionXYZ(String functionName, int nx, int ny,
180 // TODO Auto-generated method stub
185 * map between index of model filename returned from getPdbFile and the first
186 * index of models from this file in the viewer. Note - this is not trimmed -
187 * use getPdbFile to get number of unique models.
189 private int _modelFileNameMap[];
192 public synchronized String[] getStructureFiles()
194 if (jmolViewer == null)
196 return new String[0];
199 if (modelFileNames == null)
201 int modelCount = jmolViewer.ms.mc;
202 String filePath = null;
203 List<String> mset = new ArrayList<>();
204 for (int i = 0; i < modelCount; ++i)
207 * defensive check for null as getModelFileName can return null
208 * even when model count ms.mc is > 0
210 filePath = jmolViewer.ms.getModelFileName(i);
211 if (filePath != null && !mset.contains(filePath))
218 modelFileNames = mset.toArray(new String[mset.size()]);
222 return modelFileNames;
226 * map from string to applet
229 public Map<String, Object> getRegistryInfo()
231 // TODO Auto-generated method stub
235 // ///////////////////////////////
236 // JmolStatusListener
238 public void handlePopupMenu(int x, int y)
240 // jmolpopup.show(x, y);
241 // jmolpopup.jpiShow(x, y);
245 * Highlight zero, one or more atoms on the structure
248 public void highlightAtoms(List<AtomSpec> atoms)
252 if (resetLastRes.length() > 0)
254 jmolScript(resetLastRes.toString());
255 resetLastRes.setLength(0);
257 for (AtomSpec atom : atoms)
259 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
260 atom.getChain(), atom.getPdbFile());
266 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
269 String modelId = getModelIdForFile(pdbfile);
270 if (modelId.isEmpty())
277 StringBuilder selection = new StringBuilder(32);
278 StringBuilder cmd = new StringBuilder(64);
279 selection.append("select ").append(String.valueOf(pdbResNum));
280 selection.append(":");
281 if (!chain.equals(" "))
283 selection.append(chain);
285 selection.append(" /").append(modelId);
287 cmd.append(selection).append(";wireframe 100;").append(selection)
288 .append(" and not hetero;").append("spacefill 200;select none");
290 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
291 .append(" and not hetero; spacefill 0;");
293 jmolScript(cmd.toString());
297 private boolean debug = true;
299 private void jmolHistory(boolean enable)
301 jmolScript("History " + ((debug || enable) ? "on" : "off"));
304 public void loadInline(String string)
308 // viewer.loadInline(strModel, isAppend);
310 // construct fake fullPathName and fileName so we can identify the file
312 // Then, construct pass a reader for the string to Jmol.
313 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
314 // fileName, null, reader, false, null, null, 0);
315 jmolViewer.openStringInline(string);
318 protected void mouseOverStructure(int atomIndex, final String strInfo)
321 int alocsep = strInfo.indexOf("^");
322 int mdlSep = strInfo.indexOf("/");
323 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
325 if (chainSeparator == -1)
327 chainSeparator = strInfo.indexOf(".");
328 if (mdlSep > -1 && mdlSep < chainSeparator)
330 chainSeparator1 = chainSeparator;
331 chainSeparator = mdlSep;
334 // handle insertion codes
337 pdbResNum = Integer.parseInt(
338 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
343 pdbResNum = Integer.parseInt(
344 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
348 if (strInfo.indexOf(":") > -1)
350 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
351 strInfo.indexOf("."));
358 String pdbfilename = modelFileNames[0]; // default is first model
361 if (chainSeparator1 == -1)
363 chainSeparator1 = strInfo.indexOf(".", mdlSep);
365 String mdlId = (chainSeparator1 > -1)
366 ? strInfo.substring(mdlSep + 1, chainSeparator1)
367 : strInfo.substring(mdlSep + 1);
370 // recover PDB filename for the model hovered over.
371 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
372 if (_modelFileNameMap != null)
374 int _mp = _modelFileNameMap.length - 1;
376 while (mnumber < _modelFileNameMap[_mp])
380 pdbfilename = modelFileNames[_mp];
384 if (mnumber >= 0 && mnumber < modelFileNames.length)
386 pdbfilename = modelFileNames[mnumber];
389 if (pdbfilename == null)
391 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
395 } catch (Exception e)
401 * highlight position on alignment(s); if some text is returned,
402 * show this as a second line on the structure hover tooltip
404 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
408 // change comma to pipe separator (newline token for Jmol)
409 label = label.replace(',', '|');
410 StringTokenizer toks = new StringTokenizer(strInfo, " ");
411 StringBuilder sb = new StringBuilder();
412 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
413 .append(chainId).append("/1");
414 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
415 .append(toks.nextToken());
416 sb.append("|").append(label).append("\"");
417 executeCommand(new StructureCommand(sb.toString()), false);
421 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
423 if (strInfo.equals(lastMessage))
427 lastMessage = strInfo;
430 System.err.println("Ignoring additional hover info: " + data
431 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
433 mouseOverStructure(atomIndex, strInfo);
437 * { if (history != null && strStatus != null &&
438 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
442 public void notifyAtomPicked(int atomIndex, String strInfo,
446 * this implements the toggle label behaviour copied from the original
447 * structure viewer, mc_view
451 System.err.println("Ignoring additional pick data string " + strData);
453 int chainSeparator = strInfo.indexOf(":");
455 if (chainSeparator == -1)
457 chainSeparator = strInfo.indexOf(".");
460 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
462 String mdlString = "";
463 if ((p = strInfo.indexOf(":")) > -1)
465 picked += strInfo.substring(p, strInfo.indexOf("."));
468 if ((p = strInfo.indexOf("/")) > -1)
470 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
472 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
476 if (!atomsPicked.contains(picked))
478 jmolScript("select " + picked + ";label %n %r:%c");
479 atomsPicked.addElement(picked);
483 jmolViewer.evalString("select " + picked + ";label off");
484 atomsPicked.removeElement(picked);
487 // TODO: in application this happens
489 // if (scriptWindow != null)
491 // scriptWindow.sendConsoleMessage(strInfo);
492 // scriptWindow.sendConsoleMessage("\n");
498 public void notifyCallback(CBK type, Object[] data)
505 notifyFileLoaded((String) data[1], (String) data[2],
506 (String) data[3], (String) data[4],
507 ((Integer) data[5]).intValue());
511 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
513 // also highlight in alignment
514 // deliberate fall through
516 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
520 notifyScriptTermination((String) data[2],
521 ((Integer) data[3]).intValue());
524 sendConsoleEcho((String) data[1]);
528 (data == null) ? ((String) null) : (String) data[1]);
531 // System.err.println("Ignoring error callback.");
542 "Unhandled callback " + type + " " + data[1].toString());
545 } catch (Exception e)
547 System.err.println("Squashed Jmol callback handler error:");
553 public boolean notifyEnabled(CBK callbackPick)
555 switch (callbackPick)
571 // incremented every time a load notification is successfully handled -
572 // lightweight mechanism for other threads to detect when they can start
573 // referrring to new structures.
574 private long loadNotifiesHandled = 0;
576 public long getLoadNotifiesHandled()
578 return loadNotifiesHandled;
581 public void notifyFileLoaded(String fullPathName, String fileName2,
582 String modelName, String errorMsg, int modelParts)
584 if (errorMsg != null)
586 fileLoadingError = errorMsg;
590 // TODO: deal sensibly with models loaded inLine:
591 // modelName will be null, as will fullPathName.
593 // the rest of this routine ignores the arguments, and simply interrogates
594 // the Jmol view to find out what structures it contains, and adds them to
595 // the structure selection manager.
596 fileLoadingError = null;
597 String[] oldmodels = modelFileNames;
598 modelFileNames = null;
599 boolean notifyLoaded = false;
600 String[] modelfilenames = getStructureFiles();
601 if (modelfilenames == null)
603 // Jmol is still loading files!
606 // first check if we've lost any structures
607 if (oldmodels != null && oldmodels.length > 0)
610 for (int i = 0; i < oldmodels.length; i++)
612 for (int n = 0; n < modelfilenames.length; n++)
614 if (modelfilenames[n] == oldmodels[i])
620 if (oldmodels[i] != null)
627 String[] oldmfn = new String[oldm];
629 for (int i = 0; i < oldmodels.length; i++)
631 if (oldmodels[i] != null)
633 oldmfn[oldm++] = oldmodels[i];
636 // deregister the Jmol instance for these structures - we'll add
637 // ourselves again at the end for the current structure set.
638 getSsm().removeStructureViewerListener(this, oldmfn);
642 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
644 String fileName = modelfilenames[modelnum];
645 boolean foundEntry = false;
646 StructureFile pdb = null;
647 String pdbfile = null;
648 // model was probably loaded inline - so check the pdb file hashcode
651 // calculate essential attributes for the pdb data imported inline.
652 // prolly need to resolve modelnumber properly - for now just use our
654 pdbfile = jmolViewer.getData(
655 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
657 // search pdbentries and sequences to find correct pdbentry for this
659 for (int pe = 0; pe < getPdbCount(); pe++)
661 boolean matches = false;
662 addSequence(pe, getSequence()[pe]);
663 if (fileName == null)
666 // see JAL-623 - need method of matching pasted data up
668 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
669 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
670 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
677 File fl = new File(getPdbEntry(pe).getFile());
678 matches = fl.equals(new File(fileName));
682 // TODO: Jmol can in principle retrieve from CLASSLOADER but
685 // to be tested. See mantis bug
686 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
687 DataSourceType protocol = DataSourceType.URL;
692 protocol = DataSourceType.FILE;
694 } catch (Exception e)
699 // Explicitly map to the filename used by Jmol ;
700 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
701 fileName, protocol, getIProgressIndicator());
702 // pdbentry[pe].getFile(), protocol);
708 stashFoundChains(pdb, fileName);
713 if (!foundEntry && associateNewStructs)
715 // this is a foreign pdb file that jalview doesn't know about - add
716 // it to the dataset and try to find a home - either on a matching
717 // sequence or as a new sequence.
718 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
720 // parse pdb file into a chain, etc.
721 // locate best match for pdb in associated views and add mapping to
723 // if properly registered then
729 // so finally, update the jmol bits and pieces
730 // if (jmolpopup != null)
732 // // potential for deadlock here:
733 // // jmolpopup.updateComputedMenus();
735 if (!isLoadingFromArchive())
738 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
740 // register ourselves as a listener and notify the gui that it needs to
742 getSsm().addStructureViewerListener(this);
745 FeatureRenderer fr = getFeatureRenderer(null);
751 loadNotifiesHandled++;
753 setLoadingFromArchive(false);
756 protected IProgressIndicator getIProgressIndicator()
761 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
763 notifyAtomPicked(iatom, strMeasure, null);
766 public abstract void notifyScriptTermination(String strStatus,
770 * display a message echoed from the jmol viewer
774 public abstract void sendConsoleEcho(String strEcho); /*
775 * { showConsole(true);
777 * history.append("\n" +
781 // /End JmolStatusListener
782 // /////////////////////////////
786 * status message - usually the response received after a script
789 public abstract void sendConsoleMessage(String strStatus);
792 public void setCallbackFunction(String callbackType,
793 String callbackFunction)
795 System.err.println("Ignoring set-callback request to associate "
796 + callbackType + " with function " + callbackFunction);
800 public void showHelp()
802 showUrl("http://wiki.jmol.org"
803 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
808 * open the URL somehow
812 public abstract void showUrl(String url, String target);
815 * called to show or hide the associated console window container.
819 public abstract void showConsole(boolean show);
821 public static Viewer getJmolData(JmolParser jmolParser)
823 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
824 "-x -o -n", jmolParser);
833 * - when true will initialise jmol's file IO system (should be false
836 * @param documentBase
838 * @param commandOptions
840 public void allocateViewer(Container renderPanel, boolean jmolfileio,
841 String htmlName, URL documentBase, URL codeBase,
842 String commandOptions)
844 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
845 codeBase, commandOptions, null, null);
852 * - when true will initialise jmol's file IO system (should be false
855 * @param documentBase
857 * @param commandOptions
858 * @param consolePanel
859 * - panel to contain Jmol console
860 * @param buttonsToShow
861 * - buttons to show on the console, in order
863 public void allocateViewer(Container renderPanel, boolean jmolfileio,
864 String htmlName, URL documentBase, URL codeBase,
865 String commandOptions, final Container consolePanel,
866 String buttonsToShow)
869 System.err.println("Allocating Jmol Viewer: " + commandOptions);
871 if (commandOptions == null)
875 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
876 (jmolfileio ? new SmarterJmolAdapter() : null),
877 htmlName + ((Object) this).toString(), documentBase, codeBase,
878 commandOptions, this);
880 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
884 console = createJmolConsole(consolePanel, buttonsToShow);
885 } catch (Throwable e)
887 System.err.println("Could not create Jmol application console. "
891 if (consolePanel != null)
893 consolePanel.addComponentListener(this);
899 protected abstract JmolAppConsoleInterface createJmolConsole(
900 Container consolePanel, String buttonsToShow);
902 // BH 2018 -- Jmol console is not working due to problems with styled
905 protected org.jmol.api.JmolAppConsoleInterface console = null;
908 public int[] resizeInnerPanel(String data)
910 // Jalview doesn't honour resize panel requests
917 protected void closeConsole()
923 console.setVisible(false);
926 } catch (Exception x)
935 * ComponentListener method
938 public void componentMoved(ComponentEvent e)
943 * ComponentListener method
946 public void componentResized(ComponentEvent e)
951 * ComponentListener method
954 public void componentShown(ComponentEvent e)
960 * ComponentListener method
963 public void componentHidden(ComponentEvent e)
969 protected String getModelIdForFile(String pdbFile)
971 if (modelFileNames == null)
975 for (int i = 0; i < modelFileNames.length; i++)
977 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
979 return String.valueOf(i + 1);
986 protected ViewerType getViewerType()
988 return ViewerType.JMOL;
992 protected String getModelId(int pdbfnum, String file)
994 return String.valueOf(pdbfnum + 1);
998 * Returns ".spt" - the Jmol session file extension
1001 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1004 public String getSessionFileExtension()
1010 public void selectionChanged(BS arg0)
1012 // TODO Auto-generated method stub
1017 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1019 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1023 public String getHelpURL()
1025 return "http://wiki.jmol.org"; // BH 2018