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.Arrays;
30 import java.util.HashMap;
31 import java.util.List;
33 import java.util.StringTokenizer;
34 import java.util.Vector;
36 import javax.swing.SwingUtilities;
38 import org.jmol.adapter.smarter.SmarterJmolAdapter;
39 import org.jmol.api.JmolAppConsoleInterface;
40 import org.jmol.api.JmolSelectionListener;
41 import org.jmol.api.JmolStatusListener;
42 import org.jmol.api.JmolViewer;
43 import org.jmol.c.CBK;
44 import org.jmol.viewer.Viewer;
46 import jalview.api.AlignmentViewPanel;
47 import jalview.api.FeatureRenderer;
48 import jalview.api.FeatureSettingsModelI;
49 import jalview.api.SequenceRenderer;
50 import jalview.bin.Console;
51 import jalview.datamodel.PDBEntry;
52 import jalview.datamodel.SequenceI;
53 import jalview.gui.AppJmol;
54 import jalview.gui.IProgressIndicator;
55 import jalview.gui.StructureViewer.ViewerType;
56 import jalview.io.DataSourceType;
57 import jalview.io.StructureFile;
58 import jalview.structure.AtomSpec;
59 import jalview.structure.StructureCommand;
60 import jalview.structure.StructureCommandI;
61 import jalview.structure.StructureSelectionManager;
62 import jalview.structures.models.AAStructureBindingModel;
63 import jalview.ws.dbsources.Pdb;
64 import javajs.util.BS;
66 public abstract class JalviewJmolBinding extends AAStructureBindingModel
67 implements JmolStatusListener, JmolSelectionListener,
70 private String lastMessage;
73 * when true, try to search the associated datamodel for sequences that are
74 * associated with any unknown structures in the Jmol view.
76 private boolean associateNewStructs = false;
78 private Vector<String> atomsPicked = new Vector<>();
80 private String lastCommand;
82 private boolean loadedInline;
84 private StringBuffer resetLastRes = new StringBuffer();
86 public Viewer jmolViewer;
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);
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 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
172 public String createImage(String fileName, String type,
173 Object textOrBytes, int quality)
175 jalview.bin.Console.outPrintln("JMOL CREATE IMAGE");
180 public String eval(String strEval)
182 // jalview.bin.Console.outPrintln(strEval);
183 // "# 'eval' is implemented only for the applet.";
187 // End StructureListener
188 // //////////////////////////
190 ////////////////////////////
195 public List<String> getHetatmNames()
197 HashMap<String,String> hetlist=new HashMap();
198 for (int mc=0;mc<jmolViewer.ms.mc; mc++)
200 Map<String,String> hets = jmolViewer.ms.getHeteroList(mc);
203 hetlist.putAll(hets);
206 return Arrays.asList(hetlist.keySet().toArray(new String[0]));
209 ////////////////////////////
212 public float[][] functionXY(String functionName, int x, int y)
218 public float[][][] functionXYZ(String functionName, int nx, int ny,
221 // TODO Auto-generated method stub
226 * map between index of model filename returned from getPdbFile and the first
227 * index of models from this file in the viewer. Note - this is not trimmed -
228 * use getPdbFile to get number of unique models.
230 private int _modelFileNameMap[];
233 public synchronized String[] getStructureFiles()
235 if (jmolViewer == null)
237 return new String[0];
240 if (modelFileNames == null)
242 int modelCount = jmolViewer.ms.mc;
243 String filePath = null;
244 List<String> mset = new ArrayList<>();
245 for (int i = 0; i < modelCount; ++i)
248 * defensive check for null as getModelFileName can return null even when model
251 filePath = jmolViewer.ms.getModelFileName(i);
252 if (filePath != null && !mset.contains(filePath))
259 modelFileNames = mset.toArray(new String[mset.size()]);
263 return modelFileNames;
267 * map from string to applet
270 public Map<String, Object> getRegistryInfo()
272 // TODO Auto-generated method stub
276 // ///////////////////////////////
277 // JmolStatusListener
279 public void handlePopupMenu(int x, int y)
281 // jmolpopup.show(x, y);
282 // jmolpopup.jpiShow(x, y);
286 * Highlight zero, one or more atoms on the structure
289 public void highlightAtoms(List<AtomSpec> atoms)
293 if (resetLastRes.length() > 0)
295 jmolScript(resetLastRes.toString());
296 resetLastRes.setLength(0);
298 StringBuilder highlightCommands=null;
299 for (AtomSpec atom : atoms)
301 StringBuilder thisAtom = highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
302 atom.getChain(), atom.getPdbFile());
303 if (thisAtom!=null) {
304 if (highlightCommands==null)
306 highlightCommands=thisAtom;
308 highlightCommands.append(thisAtom);
312 if (highlightCommands!=null)
315 jmolScript(highlightCommands.toString());
318 // Highlight distances between atoms with a 'measure' command - not yet
320 // if (atoms.size() >= 2)
322 // StringBuilder sb = new StringBuilder();
323 // for (int a = 0; a < atoms.size(); a++)
325 // AtomSpec speca = atoms.get(a);
326 // String a_model = getModelIdForFile(speca.getPdbFile());
327 // for (int b = a + 1; b < atoms.size(); b++)
329 // AtomSpec specb = atoms.get(b);
330 // String b_model = getModelIdForFile(speca.getPdbFile());
331 // sb.append("measure ALL (" + speca.getAtomIndex() + " and */"
332 // + a_model + ") (" + specb.getAtomIndex() + " and */"
333 // + b_model + ");");
336 // jmolHistory(false, useScriptWait);
337 // jmolScript(sb.toString(), useScriptWait);
338 // jmolHistory(true, useScriptWait);
346 private StringBuilder highlightAtom(int atomIndex, int pdbResNum, String chain,
349 String modelId = getModelIdForFile(pdbfile);
350 if (modelId.isEmpty())
355 StringBuilder selection = new StringBuilder(32);
356 StringBuilder cmd = new StringBuilder(64);
357 selection.append("select ").append(String.valueOf(pdbResNum));
358 selection.append(":");
359 if (!chain.equals(" "))
361 selection.append(chain);
363 selection.append(" /").append(modelId);
365 cmd.append(selection).append(";wireframe 100;").append(selection)
366 .append(" and not hetero;").append("spacefill 200;select none");
368 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
369 .append(" and not hetero; spacefill 0;");
374 private boolean debug = true;
376 private void jmolHistory(boolean enable)
378 jmolHistory(enable, false);
381 private void jmolHistory(boolean enable, boolean useScriptWait)
383 jmolScript("History " + ((debug || enable) ? "on" : "off"),
387 public void loadInline(String string)
391 // viewer.loadInline(strModel, isAppend);
393 // construct fake fullPathName and fileName so we can identify the file
395 // Then, construct pass a reader for the string to Jmol.
396 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
397 // fileName, null, reader, false, null, null, 0);
398 jmolViewer.openStringInline(string);
401 protected void mouseOverStructure(int atomIndex, final String strInfo)
404 int alocsep = strInfo.indexOf("^");
405 int mdlSep = strInfo.indexOf("/");
406 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
408 if (chainSeparator == -1)
410 chainSeparator = strInfo.indexOf(".");
411 if (mdlSep > -1 && mdlSep < chainSeparator)
413 chainSeparator1 = chainSeparator;
414 chainSeparator = mdlSep;
417 // handle insertion codes
420 pdbResNum = Integer.parseInt(
421 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
426 pdbResNum = Integer.parseInt(
427 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
431 if (strInfo.indexOf(":") > -1)
433 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
434 strInfo.indexOf("."));
441 String pdbfilename = modelFileNames[0]; // default is first model
444 if (chainSeparator1 == -1)
446 chainSeparator1 = strInfo.indexOf(".", mdlSep);
448 String mdlId = (chainSeparator1 > -1)
449 ? strInfo.substring(mdlSep + 1, chainSeparator1)
450 : strInfo.substring(mdlSep + 1);
453 // recover PDB filename for the model hovered over.
454 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
455 if (_modelFileNameMap != null)
457 int _mp = _modelFileNameMap.length - 1;
459 while (mnumber < _modelFileNameMap[_mp])
463 pdbfilename = modelFileNames[_mp];
467 if (mnumber >= 0 && mnumber < modelFileNames.length)
469 pdbfilename = modelFileNames[mnumber];
472 if (pdbfilename == null)
474 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
478 } catch (Exception e)
484 * highlight position on alignment(s); if some text is returned, show this as a
485 * second line on the structure hover tooltip
487 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
491 // change comma to pipe separator (newline token for Jmol)
492 label = label.replace(',', '|');
493 StringTokenizer toks = new StringTokenizer(strInfo, " ");
494 StringBuilder sb = new StringBuilder();
495 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
496 .append(chainId).append("/1");
497 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
498 .append(toks.nextToken());
499 sb.append("|").append(label).append("\"");
500 executeCommand(new StructureCommand(sb.toString()), false);
504 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
506 if (strInfo.equals(lastMessage))
510 lastMessage = strInfo;
513 jalview.bin.Console.errPrintln("Ignoring additional hover info: " + data
514 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
516 mouseOverStructure(atomIndex, strInfo);
520 * { if (history != null && strStatus != null &&
521 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
525 public void notifyAtomPicked(int atomIndex, String strInfo,
529 * this implements the toggle label behaviour copied from the original
530 * structure viewer, mc_view
534 jalview.bin.Console.errPrintln("Ignoring additional pick data string " + strData);
536 int chainSeparator = strInfo.indexOf(":");
538 if (chainSeparator == -1)
540 chainSeparator = strInfo.indexOf(".");
543 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
545 String mdlString = "";
546 if ((p = strInfo.indexOf(":")) > -1)
548 picked += strInfo.substring(p, strInfo.indexOf("."));
551 if ((p = strInfo.indexOf("/")) > -1)
553 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
555 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
559 if (!atomsPicked.contains(picked))
561 jmolScript("select " + picked + ";label %n %r:%c");
562 atomsPicked.addElement(picked);
566 jmolViewer.evalString("select " + picked + ";label off");
567 atomsPicked.removeElement(picked);
570 // TODO: in application this happens
572 // if (scriptWindow != null)
574 // scriptWindow.sendConsoleMessage(strInfo);
575 // scriptWindow.sendConsoleMessage("\n");
581 public void notifyCallback(CBK type, Object[] data)
584 * ensure processed in AWT thread to avoid risk of deadlocks
586 SwingUtilities.invokeLater(new Runnable()
592 processCallback(type, data);
598 * Processes one callback notification from Jmol
603 protected void processCallback(CBK type, Object[] data)
610 notifyFileLoaded((String) data[1], (String) data[2],
611 (String) data[3], (String) data[4],
612 ((Integer) data[5]).intValue());
616 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
618 // also highlight in alignment
619 // deliberate fall through
621 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
625 notifyScriptTermination((String) data[2],
626 ((Integer) data[3]).intValue());
629 sendConsoleEcho((String) data[1]);
633 (data == null) ? ((String) null) : (String) data[1]);
636 // jalview.bin.Console.errPrintln("Ignoring error callback.");
646 jalview.bin.Console.errPrintln(
647 "Unhandled callback " + type + " " + data[1].toString());
650 } catch (Exception e)
652 jalview.bin.Console.errPrintln("Squashed Jmol callback handler error:");
658 public boolean notifyEnabled(CBK callbackPick)
660 switch (callbackPick)
676 // incremented every time a load notification is successfully handled -
677 // lightweight mechanism for other threads to detect when they can start
678 // referrring to new structures.
679 private long loadNotifiesHandled = 0;
681 public long getLoadNotifiesHandled()
683 return loadNotifiesHandled;
686 public void notifyFileLoaded(String fullPathName, String fileName2,
687 String modelName, String errorMsg, int modelParts)
689 if (errorMsg != null)
691 fileLoadingError = errorMsg;
695 // TODO: deal sensibly with models loaded inLine:
696 // modelName will be null, as will fullPathName.
698 // the rest of this routine ignores the arguments, and simply interrogates
699 // the Jmol view to find out what structures it contains, and adds them to
700 // the structure selection manager.
701 fileLoadingError = null;
702 String[] oldmodels = modelFileNames;
703 modelFileNames = null;
704 boolean notifyLoaded = false;
705 String[] modelfilenames = getStructureFiles();
706 if (modelfilenames == null)
708 // Jmol is still loading files!
711 // first check if we've lost any structures
712 if (oldmodels != null && oldmodels.length > 0)
715 for (int i = 0; i < oldmodels.length; i++)
717 for (int n = 0; n < modelfilenames.length; n++)
719 if (modelfilenames[n] == oldmodels[i])
725 if (oldmodels[i] != null)
732 String[] oldmfn = new String[oldm];
734 for (int i = 0; i < oldmodels.length; i++)
736 if (oldmodels[i] != null)
738 oldmfn[oldm++] = oldmodels[i];
741 // deregister the Jmol instance for these structures - we'll add
742 // ourselves again at the end for the current structure set.
743 getSsm().removeStructureViewerListener(this, oldmfn);
747 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
749 String fileName = modelfilenames[modelnum];
750 boolean foundEntry = false;
751 StructureFile pdb = null;
752 String pdbfile = null;
753 // model was probably loaded inline - so check the pdb file hashcode
756 // calculate essential attributes for the pdb data imported inline.
757 // prolly need to resolve modelnumber properly - for now just use our
759 pdbfile = jmolViewer.getData(
760 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
762 // search pdbentries and sequences to find correct pdbentry for this
764 for (int pe = 0; pe < getPdbCount(); pe++)
766 boolean matches = false;
767 addSequence(pe, getSequence()[pe]);
768 if (fileName == null)
771 // see JAL-623 - need method of matching pasted data up
773 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
774 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
775 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
782 File fl = new File(getPdbEntry(pe).getFile());
783 matches = fl.equals(new File(fileName));
787 // TODO: Jmol can in principle retrieve from CLASSLOADER but
790 // to be tested. See mantis bug
791 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
792 DataSourceType protocol = DataSourceType.URL;
797 protocol = DataSourceType.FILE;
799 } catch (Exception e)
804 // Explicitly map to the filename used by Jmol ;
805 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
806 fileName, protocol, getIProgressIndicator());
807 // pdbentry[pe].getFile(), protocol);
813 stashFoundChains(pdb, fileName);
818 if (!foundEntry && associateNewStructs)
820 // this is a foreign pdb file that jalview doesn't know about - add
821 // it to the dataset and try to find a home - either on a matching
822 // sequence or as a new sequence.
823 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
825 // parse pdb file into a chain, etc.
826 // locate best match for pdb in associated views and add mapping to
828 // if properly registered then
834 // so finally, update the jmol bits and pieces
835 // if (jmolpopup != null)
837 // // potential for deadlock here:
838 // // jmolpopup.updateComputedMenus();
840 if (!isLoadingFromArchive())
843 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
845 // register ourselves as a listener and notify the gui that it needs to
847 getSsm().addStructureViewerListener(this);
850 FeatureRenderer fr = getFeatureRenderer(null);
853 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
854 ((AppJmol) getViewer()).getAlignmentPanel().av
855 .applyFeaturesStyle(colours);
858 loadNotifiesHandled++;
860 setLoadingFromArchive(false);
863 protected IProgressIndicator getIProgressIndicator()
868 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
870 notifyAtomPicked(iatom, strMeasure, null);
873 public abstract void notifyScriptTermination(String strStatus,
877 * display a message echoed from the jmol viewer
881 public abstract void sendConsoleEcho(String strEcho); /*
882 * { showConsole(true);
884 * history.append("\n" + strEcho); }
887 // /End JmolStatusListener
888 // /////////////////////////////
892 * status message - usually the response received after a script
895 public abstract void sendConsoleMessage(String strStatus);
898 public void setCallbackFunction(String callbackType,
899 String callbackFunction)
901 jalview.bin.Console.errPrintln("Ignoring set-callback request to associate "
902 + callbackType + " with function " + callbackFunction);
906 public void showHelp()
908 showUrl("http://wiki.jmol.org"
909 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
914 * open the URL somehow
918 public abstract void showUrl(String url, String target);
921 * called to show or hide the associated console window container.
925 public abstract void showConsole(boolean show);
927 public static Viewer getJmolData(JmolParser jmolParser)
929 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
930 "-x -o -n", jmolParser);
939 * - when true will initialise jmol's file IO system (should be false
942 * @param documentBase
944 * @param commandOptions
946 public void allocateViewer(Container renderPanel, boolean jmolfileio,
947 String htmlName, URL documentBase, URL codeBase,
948 String commandOptions)
950 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
951 codeBase, commandOptions, null, null);
958 * - when true will initialise jmol's file IO system (should be false
961 * @param documentBase
963 * @param commandOptions
964 * @param consolePanel
965 * - panel to contain Jmol console
966 * @param buttonsToShow
967 * - buttons to show on the console, in order
969 public void allocateViewer(Container renderPanel, boolean jmolfileio,
970 String htmlName, URL documentBase, URL codeBase,
971 String commandOptions, final Container consolePanel,
972 String buttonsToShow)
975 jalview.bin.Console.errPrintln("Allocating Jmol Viewer: " + commandOptions);
977 if (commandOptions == null)
981 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
982 (jmolfileio ? new SmarterJmolAdapter() : null),
983 htmlName + ((Object) this).toString(), documentBase, codeBase,
984 commandOptions, this);
986 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
990 console = createJmolConsole(consolePanel, buttonsToShow);
991 } catch (Throwable e)
993 jalview.bin.Console.errPrintln("Could not create Jmol application console. "
997 if (consolePanel != null)
999 consolePanel.addComponentListener(this);
1005 protected abstract JmolAppConsoleInterface createJmolConsole(
1006 Container consolePanel, String buttonsToShow);
1008 // BH 2018 -- Jmol console is not working due to problems with styled
1011 protected org.jmol.api.JmolAppConsoleInterface console = null;
1014 public int[] resizeInnerPanel(String data)
1016 // Jalview doesn't honour resize panel requests
1023 protected void closeConsole()
1025 if (console != null)
1029 console.setVisible(false);
1032 } catch (Exception x)
1041 * ComponentListener method
1044 public void componentMoved(ComponentEvent e)
1049 * ComponentListener method
1052 public void componentResized(ComponentEvent e)
1057 * ComponentListener method
1060 public void componentShown(ComponentEvent e)
1066 * ComponentListener method
1069 public void componentHidden(ComponentEvent e)
1075 protected String getModelIdForFile(String pdbFile)
1077 if (modelFileNames == null)
1081 for (int i = 0; i < modelFileNames.length; i++)
1083 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1085 return String.valueOf(i + 1);
1092 protected ViewerType getViewerType()
1094 return ViewerType.JMOL;
1098 protected String getModelId(int pdbfnum, String file)
1100 return String.valueOf(pdbfnum + 1);
1104 * Returns ".spt" - the Jmol session file extension
1107 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1110 public String getSessionFileExtension()
1116 public void selectionChanged(BS arg0)
1118 // TODO Auto-generated method stub
1123 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1125 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1129 public String getHelpURL()
1131 return "http://wiki.jmol.org"; // BH 2018