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);
115 public List<String> executeCommand(StructureCommandI command,
122 String cmd = command.getCommand();
124 if (lastCommand == null || !lastCommand.equals(cmd))
126 jmolViewer.evalStringQuiet(cmd + "\n");
133 public void createImage(String file, String type, int quality)
135 System.out.println("JMOL CREATE IMAGE");
139 public String createImage(String fileName, String type,
140 Object textOrBytes, int quality)
142 System.out.println("JMOL CREATE IMAGE");
147 public String eval(String strEval)
149 // System.out.println(strEval);
150 // "# 'eval' is implemented only for the applet.";
154 // End StructureListener
155 // //////////////////////////
158 public float[][] functionXY(String functionName, int x, int y)
164 public float[][][] functionXYZ(String functionName, int nx, int ny,
167 // TODO Auto-generated method stub
172 * map between index of model filename returned from getPdbFile and the first
173 * index of models from this file in the viewer. Note - this is not trimmed -
174 * use getPdbFile to get number of unique models.
176 private int _modelFileNameMap[];
179 public synchronized String[] getStructureFiles()
181 if (jmolViewer == null)
183 return new String[0];
186 if (modelFileNames == null)
188 int modelCount = jmolViewer.ms.mc;
189 String filePath = null;
190 List<String> mset = new ArrayList<>();
191 for (int i = 0; i < modelCount; ++i)
194 * defensive check for null as getModelFileName can return null
195 * even when model count ms.mc is > 0
197 filePath = jmolViewer.ms.getModelFileName(i);
198 if (filePath != null && !mset.contains(filePath))
203 modelFileNames = mset.toArray(new String[mset.size()]);
206 return modelFileNames;
210 * map from string to applet
213 public Map<String, Object> getRegistryInfo()
215 // TODO Auto-generated method stub
219 // ///////////////////////////////
220 // JmolStatusListener
222 public void handlePopupMenu(int x, int y)
224 // jmolpopup.show(x, y);
225 // jmolpopup.jpiShow(x, y);
229 * Highlight zero, one or more atoms on the structure
232 public void highlightAtoms(List<AtomSpec> atoms)
236 if (resetLastRes.length() > 0)
238 jmolViewer.evalStringQuiet(resetLastRes.toString());
239 resetLastRes.setLength(0);
241 for (AtomSpec atom : atoms)
243 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
244 atom.getChain(), atom.getPdbFile());
250 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
253 String modelId = getModelIdForFile(pdbfile);
254 if (modelId.isEmpty())
261 StringBuilder selection = new StringBuilder(32);
262 StringBuilder cmd = new StringBuilder(64);
263 selection.append("select ").append(String.valueOf(pdbResNum));
264 selection.append(":");
265 if (!chain.equals(" "))
267 selection.append(chain);
269 selection.append(" /").append(modelId);
271 cmd.append(selection).append(";wireframe 100;").append(selection)
272 .append(" and not hetero;").append("spacefill 200;select none");
274 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
275 .append(" and not hetero; spacefill 0;");
277 jmolViewer.evalStringQuiet(cmd.toString());
281 private boolean debug = true;
283 private void jmolHistory(boolean enable)
285 jmolViewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
288 public void loadInline(String string)
292 // viewer.loadInline(strModel, isAppend);
294 // construct fake fullPathName and fileName so we can identify the file
296 // Then, construct pass a reader for the string to Jmol.
297 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
298 // fileName, null, reader, false, null, null, 0);
299 jmolViewer.openStringInline(string);
302 protected void mouseOverStructure(int atomIndex, final String strInfo)
305 int alocsep = strInfo.indexOf("^");
306 int mdlSep = strInfo.indexOf("/");
307 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
309 if (chainSeparator == -1)
311 chainSeparator = strInfo.indexOf(".");
312 if (mdlSep > -1 && mdlSep < chainSeparator)
314 chainSeparator1 = chainSeparator;
315 chainSeparator = mdlSep;
318 // handle insertion codes
321 pdbResNum = Integer.parseInt(
322 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
327 pdbResNum = Integer.parseInt(
328 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
332 if (strInfo.indexOf(":") > -1)
334 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
335 strInfo.indexOf("."));
342 String pdbfilename = modelFileNames[0]; // default is first model
345 if (chainSeparator1 == -1)
347 chainSeparator1 = strInfo.indexOf(".", mdlSep);
349 String mdlId = (chainSeparator1 > -1)
350 ? strInfo.substring(mdlSep + 1, chainSeparator1)
351 : strInfo.substring(mdlSep + 1);
354 // recover PDB filename for the model hovered over.
355 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
356 if (_modelFileNameMap != null)
358 int _mp = _modelFileNameMap.length - 1;
360 while (mnumber < _modelFileNameMap[_mp])
364 pdbfilename = modelFileNames[_mp];
368 if (mnumber >= 0 && mnumber < modelFileNames.length)
370 pdbfilename = modelFileNames[mnumber];
373 if (pdbfilename == null)
375 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
379 } catch (Exception e)
385 * highlight position on alignment(s); if some text is returned,
386 * show this as a second line on the structure hover tooltip
388 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
392 // change comma to pipe separator (newline token for Jmol)
393 label = label.replace(',', '|');
394 StringTokenizer toks = new StringTokenizer(strInfo, " ");
395 StringBuilder sb = new StringBuilder();
396 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
397 .append(chainId).append("/1");
398 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
399 .append(toks.nextToken());
400 sb.append("|").append(label).append("\"");
401 executeCommand(new StructureCommand(sb.toString()), false);
405 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
407 if (strInfo.equals(lastMessage))
411 lastMessage = strInfo;
414 System.err.println("Ignoring additional hover info: " + data
415 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
417 mouseOverStructure(atomIndex, strInfo);
421 * { if (history != null && strStatus != null &&
422 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
426 public void notifyAtomPicked(int atomIndex, String strInfo,
430 * this implements the toggle label behaviour copied from the original
431 * structure viewer, MCView
435 System.err.println("Ignoring additional pick data string " + strData);
437 int chainSeparator = strInfo.indexOf(":");
439 if (chainSeparator == -1)
441 chainSeparator = strInfo.indexOf(".");
444 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
446 String mdlString = "";
447 if ((p = strInfo.indexOf(":")) > -1)
449 picked += strInfo.substring(p, strInfo.indexOf("."));
452 if ((p = strInfo.indexOf("/")) > -1)
454 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
456 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
460 if (!atomsPicked.contains(picked))
462 jmolViewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
463 atomsPicked.addElement(picked);
467 jmolViewer.evalString("select " + picked + ";label off");
468 atomsPicked.removeElement(picked);
471 // TODO: in application this happens
473 // if (scriptWindow != null)
475 // scriptWindow.sendConsoleMessage(strInfo);
476 // scriptWindow.sendConsoleMessage("\n");
482 public void notifyCallback(CBK type, Object[] data)
489 notifyFileLoaded((String) data[1], (String) data[2],
490 (String) data[3], (String) data[4],
491 ((Integer) data[5]).intValue());
495 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
497 // also highlight in alignment
498 // deliberate fall through
500 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
504 notifyScriptTermination((String) data[2],
505 ((Integer) data[3]).intValue());
508 sendConsoleEcho((String) data[1]);
512 (data == null) ? ((String) null) : (String) data[1]);
515 // System.err.println("Ignoring error callback.");
526 "Unhandled callback " + type + " " + data[1].toString());
529 } catch (Exception e)
531 System.err.println("Squashed Jmol callback handler error:");
537 public boolean notifyEnabled(CBK callbackPick)
539 switch (callbackPick)
555 // incremented every time a load notification is successfully handled -
556 // lightweight mechanism for other threads to detect when they can start
557 // referrring to new structures.
558 private long loadNotifiesHandled = 0;
560 public long getLoadNotifiesHandled()
562 return loadNotifiesHandled;
565 public void notifyFileLoaded(String fullPathName, String fileName2,
566 String modelName, String errorMsg, int modelParts)
568 if (errorMsg != null)
570 fileLoadingError = errorMsg;
574 // TODO: deal sensibly with models loaded inLine:
575 // modelName will be null, as will fullPathName.
577 // the rest of this routine ignores the arguments, and simply interrogates
578 // the Jmol view to find out what structures it contains, and adds them to
579 // the structure selection manager.
580 fileLoadingError = null;
581 String[] oldmodels = modelFileNames;
582 modelFileNames = null;
583 boolean notifyLoaded = false;
584 String[] modelfilenames = getStructureFiles();
585 // first check if we've lost any structures
586 if (oldmodels != null && oldmodels.length > 0)
589 for (int i = 0; i < oldmodels.length; i++)
591 for (int n = 0; n < modelfilenames.length; n++)
593 if (modelfilenames[n] == oldmodels[i])
599 if (oldmodels[i] != null)
606 String[] oldmfn = new String[oldm];
608 for (int i = 0; i < oldmodels.length; i++)
610 if (oldmodels[i] != null)
612 oldmfn[oldm++] = oldmodels[i];
615 // deregister the Jmol instance for these structures - we'll add
616 // ourselves again at the end for the current structure set.
617 getSsm().removeStructureViewerListener(this, oldmfn);
621 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
623 String fileName = modelfilenames[modelnum];
624 boolean foundEntry = false;
625 StructureFile pdb = null;
626 String pdbfile = null;
627 // model was probably loaded inline - so check the pdb file hashcode
630 // calculate essential attributes for the pdb data imported inline.
631 // prolly need to resolve modelnumber properly - for now just use our
633 pdbfile = jmolViewer.getData(
634 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
636 // search pdbentries and sequences to find correct pdbentry for this
638 for (int pe = 0; pe < getPdbCount(); pe++)
640 boolean matches = false;
641 addSequence(pe, getSequence()[pe]);
642 if (fileName == null)
645 // see JAL-623 - need method of matching pasted data up
647 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
648 pdbfile, DataSourceType.PASTE,
649 getIProgressIndicator());
650 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
657 File fl = new File(getPdbEntry(pe).getFile());
658 matches = fl.equals(new File(fileName));
662 // TODO: Jmol can in principle retrieve from CLASSLOADER but
665 // to be tested. See mantis bug
666 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
667 DataSourceType protocol = DataSourceType.URL;
672 protocol = DataSourceType.FILE;
674 } catch (Exception e)
679 // Explicitly map to the filename used by Jmol ;
680 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
681 fileName, protocol, getIProgressIndicator());
682 // pdbentry[pe].getFile(), protocol);
688 stashFoundChains(pdb, fileName);
693 if (!foundEntry && associateNewStructs)
695 // this is a foreign pdb file that jalview doesn't know about - add
696 // it to the dataset and try to find a home - either on a matching
697 // sequence or as a new sequence.
698 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
700 // parse pdb file into a chain, etc.
701 // locate best match for pdb in associated views and add mapping to
703 // if properly registered then
709 // so finally, update the jmol bits and pieces
710 // if (jmolpopup != null)
712 // // potential for deadlock here:
713 // // jmolpopup.updateComputedMenus();
715 if (!isLoadingFromArchive())
717 jmolViewer.evalStringQuiet(
718 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
720 // register ourselves as a listener and notify the gui that it needs to
722 getSsm().addStructureViewerListener(this);
725 FeatureRenderer fr = getFeatureRenderer(null);
731 loadNotifiesHandled++;
733 setLoadingFromArchive(false);
736 protected IProgressIndicator getIProgressIndicator()
741 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
743 notifyAtomPicked(iatom, strMeasure, null);
746 public abstract void notifyScriptTermination(String strStatus,
750 * display a message echoed from the jmol viewer
754 public abstract void sendConsoleEcho(String strEcho); /*
755 * { showConsole(true);
757 * history.append("\n" +
761 // /End JmolStatusListener
762 // /////////////////////////////
766 * status message - usually the response received after a script
769 public abstract void sendConsoleMessage(String strStatus);
772 public void setCallbackFunction(String callbackType,
773 String callbackFunction)
775 System.err.println("Ignoring set-callback request to associate "
776 + callbackType + " with function " + callbackFunction);
780 public void showHelp()
782 showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
786 * open the URL somehow
790 public abstract void showUrl(String url, String target);
793 * called to show or hide the associated console window container.
797 public abstract void showConsole(boolean show);
802 * - when true will initialise jmol's file IO system (should be false
805 * @param documentBase
807 * @param commandOptions
809 public void allocateViewer(Container renderPanel, boolean jmolfileio,
810 String htmlName, URL documentBase, URL codeBase,
811 String commandOptions)
813 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
814 codeBase, commandOptions, null, null);
821 * - when true will initialise jmol's file IO system (should be false
824 * @param documentBase
826 * @param commandOptions
827 * @param consolePanel
828 * - panel to contain Jmol console
829 * @param buttonsToShow
830 * - buttons to show on the console, in ordr
832 public void allocateViewer(Container renderPanel, boolean jmolfileio,
833 String htmlName, URL documentBase, URL codeBase,
834 String commandOptions, final Container consolePanel,
835 String buttonsToShow)
837 if (commandOptions == null)
841 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
842 (jmolfileio ? new SmarterJmolAdapter() : null),
843 htmlName + ((Object) this).toString(), documentBase, codeBase,
844 commandOptions, this);
846 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
848 console = createJmolConsole(consolePanel, buttonsToShow);
849 if (consolePanel != null)
851 consolePanel.addComponentListener(this);
857 protected abstract JmolAppConsoleInterface createJmolConsole(
858 Container consolePanel, String buttonsToShow);
860 protected org.jmol.api.JmolAppConsoleInterface console = null;
863 public int[] resizeInnerPanel(String data)
865 // Jalview doesn't honour resize panel requests
872 protected void closeConsole()
878 console.setVisible(false);
881 } catch (Exception x)
890 * ComponentListener method
893 public void componentMoved(ComponentEvent e)
898 * ComponentListener method
901 public void componentResized(ComponentEvent e)
906 * ComponentListener method
909 public void componentShown(ComponentEvent e)
915 * ComponentListener method
918 public void componentHidden(ComponentEvent e)
924 protected String getModelIdForFile(String pdbFile)
926 if (modelFileNames == null)
930 for (int i = 0; i < modelFileNames.length; i++)
932 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
934 return String.valueOf(i + 1);
941 protected ViewerType getViewerType()
943 return ViewerType.JMOL;
947 protected String getModelId(int pdbfnum, String file)
949 return String.valueOf(pdbfnum + 1);
953 * Returns ".spt" - the Jmol session file extension
956 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
959 public String getSessionFileExtension()