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 javax.swing.SwingUtilities;
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;
62 public abstract class JalviewJmolBinding extends AAStructureBindingModel
63 implements JmolStatusListener, JmolSelectionListener,
66 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(), ap.av.applet.getCodeBase(),
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,
140 String cmd = command.getCommand();
142 if (lastCommand == null || !lastCommand.equals(cmd))
144 jmolScript(cmd + "\n");
151 public void createImage(String file, String type, int quality)
153 System.out.println("JMOL CREATE IMAGE");
157 public String createImage(String fileName, String type,
158 Object textOrBytes, int quality)
160 System.out.println("JMOL CREATE IMAGE");
165 public String eval(String strEval)
167 // System.out.println(strEval);
168 // "# 'eval' is implemented only for the applet.";
172 // End StructureListener
173 // //////////////////////////
176 public float[][] functionXY(String functionName, int x, int y)
182 public float[][][] functionXYZ(String functionName, int nx, int ny,
185 // TODO Auto-generated method stub
191 * map between index of model filename returned from getPdbFile and the first
192 * index of models from this file in the viewer. Note - this is not trimmed -
193 * use getPdbFile to get number of unique models.
195 private int _modelFileNameMap[];
198 public synchronized String[] getStructureFiles()
200 if (jmolViewer == null)
202 return new String[0];
205 if (modelFileNames == null)
207 int modelCount = jmolViewer.ms.mc;
208 String filePath = null;
209 List<String> mset = new ArrayList<>();
210 for (int i = 0; i < modelCount; ++i)
213 * defensive check for null as getModelFileName can return null even when model
216 filePath = jmolViewer.ms.getModelFileName(i);
217 if (filePath != null && !mset.contains(filePath))
224 modelFileNames = mset.toArray(new String[mset.size()]);
228 return modelFileNames;
232 * map from string to applet
235 public Map<String, Object> getRegistryInfo()
237 // TODO Auto-generated method stub
241 // ///////////////////////////////
242 // JmolStatusListener
244 public void handlePopupMenu(int x, int y)
246 // jmolpopup.show(x, y);
247 // jmolpopup.jpiShow(x, y);
251 * Highlight zero, one or more atoms on the structure
254 public void highlightAtoms(List<AtomSpec> atoms)
258 if (resetLastRes.length() > 0)
260 jmolScript(resetLastRes.toString());
261 resetLastRes.setLength(0);
263 for (AtomSpec atom : atoms)
265 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
266 atom.getChain(), atom.getPdbFile());
272 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
275 String modelId = getModelIdForFile(pdbfile);
276 if (modelId.isEmpty())
283 StringBuilder selection = new StringBuilder(32);
284 StringBuilder cmd = new StringBuilder(64);
285 selection.append("select ").append(String.valueOf(pdbResNum));
286 selection.append(":");
287 if (!chain.equals(" "))
289 selection.append(chain);
291 selection.append(" /").append(modelId);
293 cmd.append(selection).append(";wireframe 100;").append(selection)
294 .append(" and not hetero;").append("spacefill 200;select none");
296 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
297 .append(" and not hetero; spacefill 0;");
299 jmolScript(cmd.toString());
304 private boolean debug = true;
306 private void jmolHistory(boolean enable)
308 jmolScript("History " + ((debug || enable) ? "on" : "off"));
311 public void loadInline(String string)
315 // viewer.loadInline(strModel, isAppend);
317 // construct fake fullPathName and fileName so we can identify the file
319 // Then, construct pass a reader for the string to Jmol.
320 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
321 // fileName, null, reader, false, null, null, 0);
322 jmolViewer.openStringInline(string);
325 protected void mouseOverStructure(int atomIndex, final String strInfo)
328 int alocsep = strInfo.indexOf("^");
329 int mdlSep = strInfo.indexOf("/");
330 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
332 if (chainSeparator == -1)
334 chainSeparator = strInfo.indexOf(".");
335 if (mdlSep > -1 && mdlSep < chainSeparator)
337 chainSeparator1 = chainSeparator;
338 chainSeparator = mdlSep;
341 // handle insertion codes
344 pdbResNum = Integer.parseInt(
345 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
350 pdbResNum = Integer.parseInt(
351 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
355 if (strInfo.indexOf(":") > -1)
357 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
358 strInfo.indexOf("."));
365 String pdbfilename = modelFileNames[0]; // default is first model
368 if (chainSeparator1 == -1)
370 chainSeparator1 = strInfo.indexOf(".", mdlSep);
372 String mdlId = (chainSeparator1 > -1)
373 ? strInfo.substring(mdlSep + 1, chainSeparator1)
374 : strInfo.substring(mdlSep + 1);
377 // recover PDB filename for the model hovered over.
378 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
379 if (_modelFileNameMap != null)
381 int _mp = _modelFileNameMap.length - 1;
383 while (mnumber < _modelFileNameMap[_mp])
387 pdbfilename = modelFileNames[_mp];
391 if (mnumber >= 0 && mnumber < modelFileNames.length)
393 pdbfilename = modelFileNames[mnumber];
396 if (pdbfilename == null)
398 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
402 } catch (Exception e)
408 * highlight position on alignment(s); if some text is returned, show this as a
409 * second line on the structure hover tooltip
411 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
415 // change comma to pipe separator (newline token for Jmol)
416 label = label.replace(',', '|');
417 StringTokenizer toks = new StringTokenizer(strInfo, " ");
418 StringBuilder sb = new StringBuilder();
419 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
420 .append(chainId).append("/1");
421 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
422 .append(toks.nextToken());
423 sb.append("|").append(label).append("\"");
424 executeCommand(new StructureCommand(sb.toString()), false);
428 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
430 if (strInfo.equals(lastMessage))
434 lastMessage = strInfo;
437 System.err.println("Ignoring additional hover info: " + data
438 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
440 mouseOverStructure(atomIndex, strInfo);
444 * { if (history != null && strStatus != null &&
445 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
449 public void notifyAtomPicked(int atomIndex, String strInfo,
453 * this implements the toggle label behaviour copied from the original
454 * structure viewer, mc_view
458 System.err.println("Ignoring additional pick data string " + strData);
460 int chainSeparator = strInfo.indexOf(":");
462 if (chainSeparator == -1)
464 chainSeparator = strInfo.indexOf(".");
467 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
469 String mdlString = "";
470 if ((p = strInfo.indexOf(":")) > -1)
472 picked += strInfo.substring(p, strInfo.indexOf("."));
475 if ((p = strInfo.indexOf("/")) > -1)
477 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
479 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
483 if (!atomsPicked.contains(picked))
485 jmolScript("select " + picked + ";label %n %r:%c");
486 atomsPicked.addElement(picked);
490 jmolViewer.evalString("select " + picked + ";label off");
491 atomsPicked.removeElement(picked);
494 // TODO: in application this happens
496 // if (scriptWindow != null)
498 // scriptWindow.sendConsoleMessage(strInfo);
499 // scriptWindow.sendConsoleMessage("\n");
505 public void notifyCallback(CBK type, Object[] data)
508 * ensure processed in AWT thread to avoid risk of deadlocks
510 SwingUtilities.invokeLater(new Runnable()
516 processCallback(type, data);
522 * Processes one callback notification from Jmol
527 protected void processCallback(CBK type, Object[] data)
534 notifyFileLoaded((String) data[1], (String) data[2],
535 (String) data[3], (String) data[4],
536 ((Integer) data[5]).intValue());
540 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
542 // also highlight in alignment
543 // deliberate fall through
545 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
549 notifyScriptTermination((String) data[2],
550 ((Integer) data[3]).intValue());
553 sendConsoleEcho((String) data[1]);
557 (data == null) ? ((String) null) : (String) data[1]);
560 // System.err.println("Ignoring error callback.");
571 "Unhandled callback " + type + " " + data[1].toString());
574 } catch (Exception e)
576 System.err.println("Squashed Jmol callback handler error:");
582 public boolean notifyEnabled(CBK callbackPick)
584 switch (callbackPick)
600 // incremented every time a load notification is successfully handled -
601 // lightweight mechanism for other threads to detect when they can start
602 // referrring to new structures.
603 private long loadNotifiesHandled = 0;
605 public long getLoadNotifiesHandled()
607 return loadNotifiesHandled;
610 public void notifyFileLoaded(String fullPathName, String fileName2,
611 String modelName, String errorMsg, int modelParts)
613 if (errorMsg != null)
615 fileLoadingError = errorMsg;
619 // TODO: deal sensibly with models loaded inLine:
620 // modelName will be null, as will fullPathName.
622 // the rest of this routine ignores the arguments, and simply interrogates
623 // the Jmol view to find out what structures it contains, and adds them to
624 // the structure selection manager.
625 fileLoadingError = null;
626 String[] oldmodels = modelFileNames;
627 modelFileNames = null;
628 boolean notifyLoaded = false;
629 String[] modelfilenames = getStructureFiles();
630 if (modelfilenames == null)
632 // Jmol is still loading files!
635 // first check if we've lost any structures
636 if (oldmodels != null && oldmodels.length > 0)
639 for (int i = 0; i < oldmodels.length; i++)
641 for (int n = 0; n < modelfilenames.length; n++)
643 if (modelfilenames[n] == oldmodels[i])
649 if (oldmodels[i] != null)
656 String[] oldmfn = new String[oldm];
658 for (int i = 0; i < oldmodels.length; i++)
660 if (oldmodels[i] != null)
662 oldmfn[oldm++] = oldmodels[i];
665 // deregister the Jmol instance for these structures - we'll add
666 // ourselves again at the end for the current structure set.
667 getSsm().removeStructureViewerListener(this, oldmfn);
671 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
673 String fileName = modelfilenames[modelnum];
674 boolean foundEntry = false;
675 StructureFile pdb = null;
676 String pdbfile = null;
677 // model was probably loaded inline - so check the pdb file hashcode
680 // calculate essential attributes for the pdb data imported inline.
681 // prolly need to resolve modelnumber properly - for now just use our
683 pdbfile = jmolViewer.getData(
684 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
686 // search pdbentries and sequences to find correct pdbentry for this
688 for (int pe = 0; pe < getPdbCount(); pe++)
690 boolean matches = false;
691 addSequence(pe, getSequence()[pe]);
692 if (fileName == null)
695 // see JAL-623 - need method of matching pasted data up
697 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
698 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
699 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
706 File fl = new File(getPdbEntry(pe).getFile());
707 matches = fl.equals(new File(fileName));
711 // TODO: Jmol can in principle retrieve from CLASSLOADER but
714 // to be tested. See mantis bug
715 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
716 DataSourceType protocol = DataSourceType.URL;
721 protocol = DataSourceType.FILE;
723 } catch (Exception e)
728 // Explicitly map to the filename used by Jmol ;
729 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
730 fileName, protocol, getIProgressIndicator());
731 // pdbentry[pe].getFile(), protocol);
737 stashFoundChains(pdb, fileName);
742 if (!foundEntry && associateNewStructs)
744 // this is a foreign pdb file that jalview doesn't know about - add
745 // it to the dataset and try to find a home - either on a matching
746 // sequence or as a new sequence.
747 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
749 // parse pdb file into a chain, etc.
750 // locate best match for pdb in associated views and add mapping to
752 // if properly registered then
758 // so finally, update the jmol bits and pieces
759 // if (jmolpopup != null)
761 // // potential for deadlock here:
762 // // jmolpopup.updateComputedMenus();
764 if (!isLoadingFromArchive())
767 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
769 // register ourselves as a listener and notify the gui that it needs to
771 getSsm().addStructureViewerListener(this);
774 FeatureRenderer fr = getFeatureRenderer(null);
777 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
778 ((AppJmol) getViewer()).getAlignmentPanel().av
779 .applyFeaturesStyle(colours);
782 loadNotifiesHandled++;
784 setLoadingFromArchive(false);
788 protected IProgressIndicator getIProgressIndicator()
793 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
795 notifyAtomPicked(iatom, strMeasure, null);
798 public abstract void notifyScriptTermination(String strStatus,
802 * display a message echoed from the jmol viewer
806 public abstract void sendConsoleEcho(String strEcho); /*
807 * { showConsole(true);
809 * history.append("\n" + strEcho); }
812 // /End JmolStatusListener
813 // /////////////////////////////
817 * status message - usually the response received after a script
820 public abstract void sendConsoleMessage(String strStatus);
823 public void setCallbackFunction(String callbackType,
824 String callbackFunction)
826 System.err.println("Ignoring set-callback request to associate "
827 + callbackType + " with function " + callbackFunction);
832 public void showHelp()
834 showUrl("http://wiki.jmol.org"
835 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
840 * open the URL somehow
844 public abstract void showUrl(String url, String target);
847 * called to show or hide the associated console window container.
851 public abstract void showConsole(boolean show);
853 public static Viewer getJmolData(JmolParser jmolParser)
855 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
856 "-x -o -n", jmolParser);
865 * - when true will initialise jmol's file IO system (should be false
868 * @param documentBase
870 * @param commandOptions
872 public void allocateViewer(Container renderPanel, boolean jmolfileio,
873 String htmlName, URL documentBase, URL codeBase,
874 String commandOptions)
876 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
877 codeBase, commandOptions, null, null);
884 * - when true will initialise jmol's file IO system (should be false
887 * @param documentBase
889 * @param commandOptions
890 * @param consolePanel
891 * - panel to contain Jmol console
892 * @param buttonsToShow
893 * - buttons to show on the console, in order
895 public void allocateViewer(Container renderPanel, boolean jmolfileio,
896 String htmlName, URL documentBase, URL codeBase,
897 String commandOptions, final Container consolePanel,
898 String buttonsToShow)
901 System.err.println("Allocating Jmol Viewer: " + commandOptions);
903 if (commandOptions == null)
907 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
908 (jmolfileio ? new SmarterJmolAdapter() : null),
909 htmlName + ((Object) this).toString(), documentBase, codeBase,
910 commandOptions, this);
912 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
916 console = createJmolConsole(consolePanel, buttonsToShow);
917 } catch (Throwable e)
919 System.err.println("Could not create Jmol application console. "
923 if (consolePanel != null)
925 consolePanel.addComponentListener(this);
931 protected abstract JmolAppConsoleInterface createJmolConsole(
932 Container consolePanel, String buttonsToShow);
934 // BH 2018 -- Jmol console is not working due to problems with styled
937 protected org.jmol.api.JmolAppConsoleInterface console = null;
940 public int[] resizeInnerPanel(String data)
942 // Jalview doesn't honour resize panel requests
949 protected void closeConsole()
955 console.setVisible(false);
958 } catch (Exception x)
967 * ComponentListener method
970 public void componentMoved(ComponentEvent e)
975 * ComponentListener method
978 public void componentResized(ComponentEvent e)
983 * ComponentListener method
986 public void componentShown(ComponentEvent e)
992 * ComponentListener method
995 public void componentHidden(ComponentEvent e)
1001 protected String getModelIdForFile(String pdbFile)
1003 if (modelFileNames == null)
1007 for (int i = 0; i < modelFileNames.length; i++)
1009 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1011 return String.valueOf(i + 1);
1018 protected ViewerType getViewerType()
1020 return ViewerType.JMOL;
1024 protected String getModelId(int pdbfnum, String file)
1026 return String.valueOf(pdbfnum + 1);
1030 * Returns ".spt" - the Jmol session file extension
1033 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1036 public String getSessionFileExtension()
1042 public void selectionChanged(BS arg0)
1044 // TODO Auto-generated method stub
1049 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1051 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1055 public String getHelpURL()
1057 return "http://wiki.jmol.org"; // BH 2018