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);
119 * prepare the view for a given set of models/chains. chainList contains strings
120 * of the form 'pdbfilename:Chaincode'
122 * @deprecated now only used by applet code
125 public void centerViewer()
127 StringBuilder cmd = new StringBuilder(128);
129 for (String lbl : chainsToHide)
135 mlength = lbl.indexOf(":", p);
136 } while (p < mlength && mlength < (lbl.length() - 2));
137 // TODO: lookup each pdb id and recover proper model number for it.
138 cmd.append(":" + lbl.substring(mlength + 1) + " /"
139 + (getModelIdForFile(getFileForChain(lbl))) + " or ");
141 if (cmd.length() > 0)
143 cmd.setLength(cmd.length() - 4);
145 // todo: revised command is untested - but this method is obsolete anyway
146 String command = "select *;hide " + cmd + ";cartoon;center " + cmd;
147 executeCommand(new StructureCommand(command), false);
150 private String jmolScript(String script)
152 Cache.log.debug(">>Jmol>> " + script);
153 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
154 Cache.log.debug("<<Jmol<< " + s);
159 public List<String> executeCommand(StructureCommandI command,
166 String cmd = command.getCommand();
168 if (lastCommand == null || !lastCommand.equals(cmd))
170 jmolScript(cmd + "\n");
177 public void createImage(String file, String type, int quality)
179 System.out.println("JMOL CREATE IMAGE");
183 public String createImage(String fileName, String type,
184 Object textOrBytes, int quality)
186 System.out.println("JMOL CREATE IMAGE");
191 public String eval(String strEval)
193 // System.out.println(strEval);
194 // "# 'eval' is implemented only for the applet.";
198 // End StructureListener
199 // //////////////////////////
202 public float[][] functionXY(String functionName, int x, int y)
208 public float[][][] functionXYZ(String functionName, int nx, int ny,
211 // TODO Auto-generated method stub
216 * map between index of model filename returned from getPdbFile and the first
217 * index of models from this file in the viewer. Note - this is not trimmed -
218 * use getPdbFile to get number of unique models.
220 private int _modelFileNameMap[];
223 public synchronized String[] getStructureFiles()
225 if (modelFileNames != null)
227 return modelFileNames;
229 if (jmolViewer == null)
231 return new String[0];
234 List<String> mset = new ArrayList<>();
235 int modelCount = jmolViewer.ms.mc;
236 String filePath = null;
237 for (int i = 0; i < modelCount; ++i)
240 * defensive check for null as getModelFileName can return null
241 * even when model count ms.mc is > 0
243 filePath = jmolViewer.ms.getModelFileName(i);
244 if (filePath != null && !mset.contains(filePath))
252 modelFileNames = mset.toArray(new String[mset.size()]);
255 return modelFileNames;
259 * map from string to applet
262 public Map<String, Object> getRegistryInfo()
264 // TODO Auto-generated method stub
268 // ///////////////////////////////
269 // JmolStatusListener
271 public void handlePopupMenu(int x, int y)
273 // jmolpopup.show(x, y);
274 // jmolpopup.jpiShow(x, y);
278 * Highlight zero, one or more atoms on the structure
281 public void highlightAtoms(List<AtomSpec> atoms)
285 if (resetLastRes.length() > 0)
287 jmolScript(resetLastRes.toString());
288 resetLastRes.setLength(0);
290 for (AtomSpec atom : atoms)
292 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
293 atom.getChain(), atom.getPdbFile());
299 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
302 String modelId = getModelIdForFile(pdbfile);
303 if (modelId.isEmpty())
310 StringBuilder selection = new StringBuilder(32);
311 StringBuilder cmd = new StringBuilder(64);
312 selection.append("select ").append(String.valueOf(pdbResNum));
313 selection.append(":");
314 if (!chain.equals(" "))
316 selection.append(chain);
318 selection.append(" /").append(modelId);
320 cmd.append(selection).append(";wireframe 100;").append(selection)
321 .append(" and not hetero;").append("spacefill 200;select none");
323 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
324 .append(" and not hetero; spacefill 0;");
326 jmolScript(cmd.toString());
330 private boolean debug = true;
332 private void jmolHistory(boolean enable)
334 jmolScript("History " + ((debug || enable) ? "on" : "off"));
337 public void loadInline(String string)
341 // viewer.loadInline(strModel, isAppend);
343 // construct fake fullPathName and fileName so we can identify the file
345 // Then, construct pass a reader for the string to Jmol.
346 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
347 // fileName, null, reader, false, null, null, 0);
348 jmolViewer.openStringInline(string);
351 protected void mouseOverStructure(int atomIndex, final String strInfo)
354 int alocsep = strInfo.indexOf("^");
355 int mdlSep = strInfo.indexOf("/");
356 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
358 if (chainSeparator == -1)
360 chainSeparator = strInfo.indexOf(".");
361 if (mdlSep > -1 && mdlSep < chainSeparator)
363 chainSeparator1 = chainSeparator;
364 chainSeparator = mdlSep;
367 // handle insertion codes
370 pdbResNum = Integer.parseInt(
371 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
376 pdbResNum = Integer.parseInt(
377 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
381 if (strInfo.indexOf(":") > -1)
383 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
384 strInfo.indexOf("."));
391 String pdbfilename = modelFileNames[0]; // default is first model
394 if (chainSeparator1 == -1)
396 chainSeparator1 = strInfo.indexOf(".", mdlSep);
398 String mdlId = (chainSeparator1 > -1)
399 ? strInfo.substring(mdlSep + 1, chainSeparator1)
400 : strInfo.substring(mdlSep + 1);
403 // recover PDB filename for the model hovered over.
404 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
405 if (_modelFileNameMap != null)
407 int _mp = _modelFileNameMap.length - 1;
409 while (mnumber < _modelFileNameMap[_mp])
413 pdbfilename = modelFileNames[_mp];
417 if (mnumber >= 0 && mnumber < modelFileNames.length)
419 pdbfilename = modelFileNames[mnumber];
422 if (pdbfilename == null)
424 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
428 } catch (Exception e)
434 * highlight position on alignment(s); if some text is returned,
435 * show this as a second line on the structure hover tooltip
437 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
441 // change comma to pipe separator (newline token for Jmol)
442 label = label.replace(',', '|');
443 StringTokenizer toks = new StringTokenizer(strInfo, " ");
444 StringBuilder sb = new StringBuilder();
445 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
446 .append(chainId).append("/1");
447 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
448 .append(toks.nextToken());
449 sb.append("|").append(label).append("\"");
450 executeCommand(new StructureCommand(sb.toString()), false);
454 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
456 if (strInfo.equals(lastMessage))
460 lastMessage = strInfo;
463 System.err.println("Ignoring additional hover info: " + data
464 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
466 mouseOverStructure(atomIndex, strInfo);
470 * { if (history != null && strStatus != null &&
471 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
475 public void notifyAtomPicked(int atomIndex, String strInfo,
479 * this implements the toggle label behaviour copied from the original
480 * structure viewer, mc_view
484 System.err.println("Ignoring additional pick data string " + strData);
486 int chainSeparator = strInfo.indexOf(":");
488 if (chainSeparator == -1)
490 chainSeparator = strInfo.indexOf(".");
493 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
495 String mdlString = "";
496 if ((p = strInfo.indexOf(":")) > -1)
498 picked += strInfo.substring(p, strInfo.indexOf("."));
501 if ((p = strInfo.indexOf("/")) > -1)
503 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
505 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
509 if (!atomsPicked.contains(picked))
511 jmolScript("select " + picked + ";label %n %r:%c");
512 atomsPicked.addElement(picked);
516 jmolViewer.evalString("select " + picked + ";label off");
517 atomsPicked.removeElement(picked);
520 // TODO: in application this happens
522 // if (scriptWindow != null)
524 // scriptWindow.sendConsoleMessage(strInfo);
525 // scriptWindow.sendConsoleMessage("\n");
531 public void notifyCallback(CBK type, Object[] data)
538 notifyFileLoaded((String) data[1], (String) data[2],
539 (String) data[3], (String) data[4],
540 ((Integer) data[5]).intValue());
544 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
546 // also highlight in alignment
547 // deliberate fall through
549 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
553 notifyScriptTermination((String) data[2],
554 ((Integer) data[3]).intValue());
557 sendConsoleEcho((String) data[1]);
561 (data == null) ? ((String) null) : (String) data[1]);
564 // System.err.println("Ignoring error callback.");
575 "Unhandled callback " + type + " " + data[1].toString());
578 } catch (Exception e)
580 System.err.println("Squashed Jmol callback handler error:");
586 public boolean notifyEnabled(CBK callbackPick)
588 switch (callbackPick)
604 // incremented every time a load notification is successfully handled -
605 // lightweight mechanism for other threads to detect when they can start
606 // referrring to new structures.
607 private long loadNotifiesHandled = 0;
609 public long getLoadNotifiesHandled()
611 return loadNotifiesHandled;
614 public void notifyFileLoaded(String fullPathName, String fileName2,
615 String modelName, String errorMsg, int modelParts)
617 if (errorMsg != null)
619 fileLoadingError = errorMsg;
623 // TODO: deal sensibly with models loaded inLine:
624 // modelName will be null, as will fullPathName.
626 // the rest of this routine ignores the arguments, and simply interrogates
627 // the Jmol view to find out what structures it contains, and adds them to
628 // the structure selection manager.
629 fileLoadingError = null;
630 String[] oldmodels = modelFileNames;
631 modelFileNames = null;
632 boolean notifyLoaded = false;
633 String[] modelfilenames = getStructureFiles();
634 if (modelfilenames == null)
636 // Jmol is still loading files!
639 // first check if we've lost any structures
640 if (oldmodels != null && oldmodels.length > 0)
643 for (int i = 0; i < oldmodels.length; i++)
645 for (int n = 0; n < modelfilenames.length; n++)
647 if (modelfilenames[n] == oldmodels[i])
653 if (oldmodels[i] != null)
660 String[] oldmfn = new String[oldm];
662 for (int i = 0; i < oldmodels.length; i++)
664 if (oldmodels[i] != null)
666 oldmfn[oldm++] = oldmodels[i];
669 // deregister the Jmol instance for these structures - we'll add
670 // ourselves again at the end for the current structure set.
671 getSsm().removeStructureViewerListener(this, oldmfn);
675 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
677 String fileName = modelfilenames[modelnum];
678 boolean foundEntry = false;
679 StructureFile pdb = null;
680 String pdbfile = null;
681 // model was probably loaded inline - so check the pdb file hashcode
684 // calculate essential attributes for the pdb data imported inline.
685 // prolly need to resolve modelnumber properly - for now just use our
687 pdbfile = jmolViewer.getData(
688 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
690 // search pdbentries and sequences to find correct pdbentry for this
692 for (int pe = 0; pe < getPdbCount(); pe++)
694 boolean matches = false;
695 addSequence(pe, getSequence()[pe]);
696 if (fileName == null)
699 // see JAL-623 - need method of matching pasted data up
701 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
702 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
703 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
710 File fl = new File(getPdbEntry(pe).getFile());
711 matches = fl.equals(new File(fileName));
715 // TODO: Jmol can in principle retrieve from CLASSLOADER but
718 // to be tested. See mantis bug
719 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
720 DataSourceType protocol = DataSourceType.URL;
725 protocol = DataSourceType.FILE;
727 } catch (Exception e)
732 // Explicitly map to the filename used by Jmol ;
733 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
734 fileName, protocol, getIProgressIndicator());
735 // pdbentry[pe].getFile(), protocol);
741 stashFoundChains(pdb, fileName);
746 if (!foundEntry && associateNewStructs)
748 // this is a foreign pdb file that jalview doesn't know about - add
749 // it to the dataset and try to find a home - either on a matching
750 // sequence or as a new sequence.
751 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
753 // parse pdb file into a chain, etc.
754 // locate best match for pdb in associated views and add mapping to
756 // if properly registered then
762 // so finally, update the jmol bits and pieces
763 // if (jmolpopup != null)
765 // // potential for deadlock here:
766 // // jmolpopup.updateComputedMenus();
768 if (!isLoadingFromArchive())
771 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
773 // register ourselves as a listener and notify the gui that it needs to
775 getSsm().addStructureViewerListener(this);
778 FeatureRenderer fr = getFeatureRenderer(null);
784 loadNotifiesHandled++;
786 setLoadingFromArchive(false);
789 protected IProgressIndicator getIProgressIndicator()
794 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
796 notifyAtomPicked(iatom, strMeasure, null);
799 public abstract void notifyScriptTermination(String strStatus,
803 * display a message echoed from the jmol viewer
807 public abstract void sendConsoleEcho(String strEcho); /*
808 * { showConsole(true);
810 * history.append("\n" +
814 // /End JmolStatusListener
815 // /////////////////////////////
819 * status message - usually the response received after a script
822 public abstract void sendConsoleMessage(String strStatus);
825 public void setCallbackFunction(String callbackType,
826 String callbackFunction)
828 System.err.println("Ignoring set-callback request to associate "
829 + callbackType + " with function " + callbackFunction);
833 public void showHelp()
835 showUrl("http://wiki.jmol.org"
836 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
841 * open the URL somehow
845 public abstract void showUrl(String url, String target);
848 * called to show or hide the associated console window container.
852 public abstract void showConsole(boolean show);
854 public static Viewer getJmolData(JmolParser jmolParser)
856 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
857 "-x -o -n", jmolParser);
866 * - when true will initialise jmol's file IO system (should be false
869 * @param documentBase
871 * @param commandOptions
873 public void allocateViewer(Container renderPanel, boolean jmolfileio,
874 String htmlName, URL documentBase, URL codeBase,
875 String commandOptions)
877 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
878 codeBase, commandOptions, null, null);
885 * - when true will initialise jmol's file IO system (should be false
888 * @param documentBase
890 * @param commandOptions
891 * @param consolePanel
892 * - panel to contain Jmol console
893 * @param buttonsToShow
894 * - buttons to show on the console, in order
896 public void allocateViewer(Container renderPanel, boolean jmolfileio,
897 String htmlName, URL documentBase, URL codeBase,
898 String commandOptions, final Container consolePanel,
899 String buttonsToShow)
902 System.err.println("Allocating Jmol Viewer: " + commandOptions);
904 if (commandOptions == null)
908 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
909 (jmolfileio ? new SmarterJmolAdapter() : null),
910 htmlName + ((Object) this).toString(), documentBase, codeBase,
911 commandOptions, this);
913 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
917 console = createJmolConsole(consolePanel, buttonsToShow);
918 } catch (Throwable e)
920 System.err.println("Could not create Jmol application console. "
924 if (consolePanel != null)
926 consolePanel.addComponentListener(this);
932 protected abstract JmolAppConsoleInterface createJmolConsole(
933 Container consolePanel, String buttonsToShow);
935 // BH 2018 -- Jmol console is not working due to problems with styled
938 protected org.jmol.api.JmolAppConsoleInterface console = null;
941 public int[] resizeInnerPanel(String data)
943 // Jalview doesn't honour resize panel requests
950 protected void closeConsole()
956 console.setVisible(false);
959 } catch (Exception x)
968 * ComponentListener method
971 public void componentMoved(ComponentEvent e)
976 * ComponentListener method
979 public void componentResized(ComponentEvent e)
984 * ComponentListener method
987 public void componentShown(ComponentEvent e)
993 * ComponentListener method
996 public void componentHidden(ComponentEvent e)
1002 * Answers a Jmol syntax style structure model specification. Model number 0, 1,
1003 * 2... is formatted as "1.1", "2.1", "3.1" etc.
1006 public String getModelSpec(int model)
1008 return String.valueOf(model + 1) + ".1";
1012 protected String getModelIdForFile(String pdbFile)
1014 if (modelFileNames == null)
1018 for (int i = 0; i < modelFileNames.length; i++)
1020 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1022 return String.valueOf(i + 1);
1029 protected ViewerType getViewerType()
1031 return ViewerType.JMOL;
1035 protected String getModelId(int pdbfnum, String file)
1037 return String.valueOf(pdbfnum + 1);
1041 * Returns ".spt" - the Jmol session file extension
1044 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1047 public String getSessionFileExtension()
1053 public void selectionChanged(BS arg0)
1055 // TODO Auto-generated method stub
1060 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1062 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1066 public String getHelpURL()
1068 return "http://wiki.jmol.org"; // BH 2018