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.datamodel.PDBEntry;
44 import jalview.datamodel.SequenceI;
45 import jalview.gui.IProgressIndicator;
46 import jalview.gui.StructureViewer.ViewerType;
47 import jalview.io.DataSourceType;
48 import jalview.io.StructureFile;
49 import jalview.structure.AtomSpec;
50 import jalview.structure.StructureCommand;
51 import jalview.structure.StructureCommandI;
52 import jalview.structure.StructureSelectionManager;
53 import jalview.structures.models.AAStructureBindingModel;
55 public abstract class JalviewJmolBinding extends AAStructureBindingModel
56 implements JmolStatusListener, JmolSelectionListener,
59 private String lastMessage;
62 * when true, try to search the associated datamodel for sequences that are
63 * associated with any unknown structures in the Jmol view.
65 private boolean associateNewStructs = false;
67 private Vector<String> atomsPicked = new Vector<>();
69 private String lastCommand;
71 private boolean loadedInline;
73 private StringBuffer resetLastRes = new StringBuffer();
75 public Viewer jmolViewer;
77 public JalviewJmolBinding(StructureSelectionManager ssm,
78 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
79 DataSourceType protocol)
81 super(ssm, pdbentry, sequenceIs, protocol);
82 setStructureCommands(new JmolCommands());
84 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
85 * "jalviewJmol", ap.av.applet .getDocumentBase(),
86 * ap.av.applet.getCodeBase(), "", this);
88 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
92 public JalviewJmolBinding(StructureSelectionManager ssm,
93 SequenceI[][] seqs, Viewer theViewer)
97 jmolViewer = theViewer;
98 jmolViewer.setJmolStatusListener(this);
99 jmolViewer.addSelectionListener(this);
100 setStructureCommands(new JmolCommands());
104 * construct a title string for the viewer window based on the data jalview
109 public String getViewerTitle()
111 return getViewerTitle("Jmol", true);
114 public void closeViewer()
116 // remove listeners for all structures in viewer
117 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
118 jmolViewer.dispose();
121 releaseUIResources();
125 public List<String> executeCommand(StructureCommandI command,
132 String cmd = command.getCommand();
134 if (lastCommand == null || !lastCommand.equals(cmd))
136 jmolViewer.evalStringQuiet(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 jmolViewer.evalStringQuiet(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 if (modelFileNames == null)
268 // look up file model number for this pdbfile
270 // may need to adjust for URLencoding here - we don't worry about that yet.
271 while (mdlNum < modelFileNames.length
272 && !pdbfile.equals(modelFileNames[mdlNum]))
276 if (mdlNum == modelFileNames.length)
283 StringBuilder cmd = new StringBuilder(64);
284 cmd.append("select ").append(String.valueOf(pdbResNum)); // +modelNum
286 resetLastRes.append("select ").append(String.valueOf(pdbResNum)); // +modelNum
289 resetLastRes.append(":");
290 if (!chain.equals(" "))
293 resetLastRes.append(chain);
296 cmd.append(" /").append(String.valueOf(mdlNum + 1));
297 resetLastRes.append("/").append(String.valueOf(mdlNum + 1));
299 cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;");
301 resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
302 + " and not hetero; spacefill 0;");
304 cmd.append("spacefill 200;select none");
306 jmolViewer.evalStringQuiet(cmd.toString());
311 private boolean debug = true;
313 private void jmolHistory(boolean enable)
315 jmolViewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
318 public void loadInline(String string)
322 // viewer.loadInline(strModel, isAppend);
324 // construct fake fullPathName and fileName so we can identify the file
326 // Then, construct pass a reader for the string to Jmol.
327 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
328 // fileName, null, reader, false, null, null, 0);
329 jmolViewer.openStringInline(string);
332 protected void mouseOverStructure(int atomIndex, final String strInfo)
335 int alocsep = strInfo.indexOf("^");
336 int mdlSep = strInfo.indexOf("/");
337 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
339 if (chainSeparator == -1)
341 chainSeparator = strInfo.indexOf(".");
342 if (mdlSep > -1 && mdlSep < chainSeparator)
344 chainSeparator1 = chainSeparator;
345 chainSeparator = mdlSep;
348 // handle insertion codes
351 pdbResNum = Integer.parseInt(
352 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
357 pdbResNum = Integer.parseInt(
358 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
362 if (strInfo.indexOf(":") > -1)
364 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
365 strInfo.indexOf("."));
372 String pdbfilename = modelFileNames[0]; // default is first model
375 if (chainSeparator1 == -1)
377 chainSeparator1 = strInfo.indexOf(".", mdlSep);
379 String mdlId = (chainSeparator1 > -1)
380 ? strInfo.substring(mdlSep + 1, chainSeparator1)
381 : strInfo.substring(mdlSep + 1);
384 // recover PDB filename for the model hovered over.
385 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
386 if (_modelFileNameMap != null)
388 int _mp = _modelFileNameMap.length - 1;
390 while (mnumber < _modelFileNameMap[_mp])
394 pdbfilename = modelFileNames[_mp];
398 if (mnumber >= 0 && mnumber < modelFileNames.length)
400 pdbfilename = modelFileNames[mnumber];
403 if (pdbfilename == null)
405 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
409 } catch (Exception e)
415 * highlight position on alignment(s); if some text is returned,
416 * show this as a second line on the structure hover tooltip
418 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
422 // change comma to pipe separator (newline token for Jmol)
423 label = label.replace(',', '|');
424 StringTokenizer toks = new StringTokenizer(strInfo, " ");
425 StringBuilder sb = new StringBuilder();
426 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
427 .append(chainId).append("/1");
428 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
429 .append(toks.nextToken());
430 sb.append("|").append(label).append("\"");
431 executeCommand(new StructureCommand(sb.toString()), false);
435 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
437 if (strInfo.equals(lastMessage))
441 lastMessage = strInfo;
444 System.err.println("Ignoring additional hover info: " + data
445 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
447 mouseOverStructure(atomIndex, strInfo);
451 * { if (history != null && strStatus != null &&
452 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
456 public void notifyAtomPicked(int atomIndex, String strInfo,
460 * this implements the toggle label behaviour copied from the original
461 * structure viewer, MCView
465 System.err.println("Ignoring additional pick data string " + strData);
467 int chainSeparator = strInfo.indexOf(":");
469 if (chainSeparator == -1)
471 chainSeparator = strInfo.indexOf(".");
474 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
476 String mdlString = "";
477 if ((p = strInfo.indexOf(":")) > -1)
479 picked += strInfo.substring(p, strInfo.indexOf("."));
482 if ((p = strInfo.indexOf("/")) > -1)
484 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
486 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
490 if (!atomsPicked.contains(picked))
492 jmolViewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
493 atomsPicked.addElement(picked);
497 jmolViewer.evalString("select " + picked + ";label off");
498 atomsPicked.removeElement(picked);
501 // TODO: in application this happens
503 // if (scriptWindow != null)
505 // scriptWindow.sendConsoleMessage(strInfo);
506 // scriptWindow.sendConsoleMessage("\n");
512 public void notifyCallback(CBK type, Object[] data)
519 notifyFileLoaded((String) data[1], (String) data[2],
520 (String) data[3], (String) data[4],
521 ((Integer) data[5]).intValue());
525 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
527 // also highlight in alignment
528 // deliberate fall through
530 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
534 notifyScriptTermination((String) data[2],
535 ((Integer) data[3]).intValue());
538 sendConsoleEcho((String) data[1]);
542 (data == null) ? ((String) null) : (String) data[1]);
545 // System.err.println("Ignoring error callback.");
556 "Unhandled callback " + type + " " + data[1].toString());
559 } catch (Exception e)
561 System.err.println("Squashed Jmol callback handler error:");
567 public boolean notifyEnabled(CBK callbackPick)
569 switch (callbackPick)
585 // incremented every time a load notification is successfully handled -
586 // lightweight mechanism for other threads to detect when they can start
587 // referrring to new structures.
588 private long loadNotifiesHandled = 0;
590 public long getLoadNotifiesHandled()
592 return loadNotifiesHandled;
595 public void notifyFileLoaded(String fullPathName, String fileName2,
596 String modelName, String errorMsg, int modelParts)
598 if (errorMsg != null)
600 fileLoadingError = errorMsg;
604 // TODO: deal sensibly with models loaded inLine:
605 // modelName will be null, as will fullPathName.
607 // the rest of this routine ignores the arguments, and simply interrogates
608 // the Jmol view to find out what structures it contains, and adds them to
609 // the structure selection manager.
610 fileLoadingError = null;
611 String[] oldmodels = modelFileNames;
612 modelFileNames = null;
613 boolean notifyLoaded = false;
614 String[] modelfilenames = getStructureFiles();
615 // first check if we've lost any structures
616 if (oldmodels != null && oldmodels.length > 0)
619 for (int i = 0; i < oldmodels.length; i++)
621 for (int n = 0; n < modelfilenames.length; n++)
623 if (modelfilenames[n] == oldmodels[i])
629 if (oldmodels[i] != null)
636 String[] oldmfn = new String[oldm];
638 for (int i = 0; i < oldmodels.length; i++)
640 if (oldmodels[i] != null)
642 oldmfn[oldm++] = oldmodels[i];
645 // deregister the Jmol instance for these structures - we'll add
646 // ourselves again at the end for the current structure set.
647 getSsm().removeStructureViewerListener(this, oldmfn);
651 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
653 String fileName = modelfilenames[modelnum];
654 boolean foundEntry = false;
655 StructureFile pdb = null;
656 String pdbfile = null;
657 // model was probably loaded inline - so check the pdb file hashcode
660 // calculate essential attributes for the pdb data imported inline.
661 // prolly need to resolve modelnumber properly - for now just use our
663 pdbfile = jmolViewer.getData(
664 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
666 // search pdbentries and sequences to find correct pdbentry for this
668 for (int pe = 0; pe < getPdbCount(); pe++)
670 boolean matches = false;
671 addSequence(pe, getSequence()[pe]);
672 if (fileName == null)
675 // see JAL-623 - need method of matching pasted data up
677 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
678 pdbfile, DataSourceType.PASTE,
679 getIProgressIndicator());
680 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
687 File fl = new File(getPdbEntry(pe).getFile());
688 matches = fl.equals(new File(fileName));
692 // TODO: Jmol can in principle retrieve from CLASSLOADER but
695 // to be tested. See mantis bug
696 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
697 DataSourceType protocol = DataSourceType.URL;
702 protocol = DataSourceType.FILE;
704 } catch (Exception e)
709 // Explicitly map to the filename used by Jmol ;
710 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
711 fileName, protocol, getIProgressIndicator());
712 // pdbentry[pe].getFile(), protocol);
718 stashFoundChains(pdb, fileName);
723 if (!foundEntry && associateNewStructs)
725 // this is a foreign pdb file that jalview doesn't know about - add
726 // it to the dataset and try to find a home - either on a matching
727 // sequence or as a new sequence.
728 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
730 // parse pdb file into a chain, etc.
731 // locate best match for pdb in associated views and add mapping to
733 // if properly registered then
739 // so finally, update the jmol bits and pieces
740 // if (jmolpopup != null)
742 // // potential for deadlock here:
743 // // jmolpopup.updateComputedMenus();
745 if (!isLoadingFromArchive())
747 jmolViewer.evalStringQuiet(
748 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
750 // register ourselves as a listener and notify the gui that it needs to
752 getSsm().addStructureViewerListener(this);
755 FeatureRenderer fr = getFeatureRenderer(null);
761 loadNotifiesHandled++;
763 setLoadingFromArchive(false);
766 protected IProgressIndicator getIProgressIndicator()
771 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
773 notifyAtomPicked(iatom, strMeasure, null);
776 public abstract void notifyScriptTermination(String strStatus,
780 * display a message echoed from the jmol viewer
784 public abstract void sendConsoleEcho(String strEcho); /*
785 * { showConsole(true);
787 * history.append("\n" +
791 // /End JmolStatusListener
792 // /////////////////////////////
796 * status message - usually the response received after a script
799 public abstract void sendConsoleMessage(String strStatus);
802 public void setCallbackFunction(String callbackType,
803 String callbackFunction)
805 System.err.println("Ignoring set-callback request to associate "
806 + callbackType + " with function " + callbackFunction);
810 public void showHelp()
812 showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
816 * open the URL somehow
820 public abstract void showUrl(String url, String target);
823 * called to show or hide the associated console window container.
827 public abstract void showConsole(boolean show);
832 * - when true will initialise jmol's file IO system (should be false
835 * @param documentBase
837 * @param commandOptions
839 public void allocateViewer(Container renderPanel, boolean jmolfileio,
840 String htmlName, URL documentBase, URL codeBase,
841 String commandOptions)
843 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
844 codeBase, commandOptions, null, null);
851 * - when true will initialise jmol's file IO system (should be false
854 * @param documentBase
856 * @param commandOptions
857 * @param consolePanel
858 * - panel to contain Jmol console
859 * @param buttonsToShow
860 * - buttons to show on the console, in ordr
862 public void allocateViewer(Container renderPanel, boolean jmolfileio,
863 String htmlName, URL documentBase, URL codeBase,
864 String commandOptions, final Container consolePanel,
865 String buttonsToShow)
867 if (commandOptions == null)
871 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
872 (jmolfileio ? new SmarterJmolAdapter() : null),
873 htmlName + ((Object) this).toString(), documentBase, codeBase,
874 commandOptions, this);
876 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
878 console = createJmolConsole(consolePanel, buttonsToShow);
879 if (consolePanel != null)
881 consolePanel.addComponentListener(this);
887 protected abstract JmolAppConsoleInterface createJmolConsole(
888 Container consolePanel, String buttonsToShow);
890 protected org.jmol.api.JmolAppConsoleInterface console = null;
893 public int[] resizeInnerPanel(String data)
895 // Jalview doesn't honour resize panel requests
902 protected void closeConsole()
908 console.setVisible(false);
911 } catch (Exception x)
920 * ComponentListener method
923 public void componentMoved(ComponentEvent e)
928 * ComponentListener method
931 public void componentResized(ComponentEvent e)
936 * ComponentListener method
939 public void componentShown(ComponentEvent e)
945 * ComponentListener method
948 public void componentHidden(ComponentEvent e)
954 protected String getModelIdForFile(String pdbFile)
956 if (modelFileNames == null)
960 for (int i = 0; i < modelFileNames.length; i++)
962 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
964 return String.valueOf(i + 1);
971 protected ViewerType getViewerType()
973 return ViewerType.JMOL;
977 protected String getModelId(int pdbfnum, String file)
979 return String.valueOf(pdbfnum + 1);
983 * Returns ".spt" - the Jmol session file extension
986 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
989 public String getSessionFileExtension()