/*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
*/
package jalview.ext.rbvi.chimera;
+import java.awt.Color;
+import java.net.BindException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+
import jalview.api.AlignmentViewPanel;
import jalview.api.FeatureRenderer;
import jalview.api.SequenceRenderer;
import jalview.datamodel.SequenceI;
import jalview.schemes.ColourSchemeI;
import jalview.schemes.ResidueProperties;
+import jalview.structure.AtomSpec;
import jalview.structure.StructureMapping;
import jalview.structure.StructureMappingcommandSet;
import jalview.structure.StructureSelectionManager;
import jalview.util.Comparison;
import jalview.util.MessageManager;
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
-
public abstract class JalviewChimeraBinding extends AAStructureBindingModel
{
private StructureManager csm;
+ /*
+ * Object through which we talk to Chimera
+ */
private ChimeraManager viewer;
/*
+ * Object which listens to Chimera notifications
+ */
+ private ChimeraListener chimeraListener;
+
+ /*
* set if chimera state is being restored from some source - instructs binding
* not to apply default display style when structure set is updated for first
* time.
private Map<String, String> chainFile;
- private StringBuffer eval = new StringBuffer();
-
public String fileLoadingError;
/*
private String lastCommand;
- private String lastMessage;
-
private boolean loadedInline;
/**
+ * current set of model filenames loaded
+ */
+ String[] modelFileNames = null;
+
+ String lastMousedOverAtomSpec;
+
+ private List<String> lastReply;
+
+ /*
+ * incremented every time a load notification is successfully handled -
+ * lightweight mechanism for other threads to detect when they can start
+ * referring to new structures.
+ */
+ private long loadNotifiesHandled = 0;
+
+ /**
* Open a PDB structure file in Chimera and set up mappings from Jalview.
*
* We check if the PDB model id is already loaded in Chimera, if so don't
/*
* If Chimera doesn't yet have this model, ask it to open it, and retrieve
- * the model names added by Chimera.
+ * the model name(s) added by Chimera.
*/
if (!alreadyOpen)
{
viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
- modelsToMap = viewer.getModelList();
- modelsToMap.removeAll(oldList);
+ List<ChimeraModel> newList = viewer.getModelList();
+ // JAL-1728 newList.removeAll(oldList) does not work
+ for (ChimeraModel cm : newList)
+ {
+ if (cm.getModelName().equals(pe.getId()))
+ {
+ modelsToMap.add(cm);
+ }
+ }
}
chimeraMaps.put(file, modelsToMap);
}
/**
- * current set of model filenames loaded
- */
- String[] modelFileNames = null;
-
-
- StringBuffer resetLastRes = new StringBuffer();
-
- private List<String> lastReply;
-
- /**
* Constructor
*
* @param ssm
}
/**
+ * Start a dedicated HttpServer to listen for Chimera notifications, and tell
+ * it to start listening
+ */
+ public void startChimeraListener()
+ {
+ try
+ {
+ chimeraListener = new ChimeraListener(this);
+ viewer.startListening(chimeraListener.getUri());
+ } catch (BindException e)
+ {
+ System.err.println("Failed to start Chimera listener: "
+ + e.getMessage());
+ }
+ }
+
+ /**
* Constructor
*
* @param ssm
}
/**
- * Close down the Jalview viewer, and (optionally) the associated Chimera
- * window.
+ * Close down the Jalview viewer and listener, and (optionally) the associated
+ * Chimera window.
*/
public void closeViewer(boolean closeChimera)
{
{
viewer.exitChimera();
}
+ if (this.chimeraListener != null)
+ {
+ chimeraListener.shutdown();
+ chimeraListener = null;
+ }
lastCommand = null;
viewer = null;
+
releaseUIResources();
}
}
AlignmentI alignment = alignmentv.getAlignment();
- for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(files, sr, fr, alignment))
+ for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
+ files, sr, fr, alignment))
{
for (String command : cpdbbyseq.commands)
{
String[] files, SequenceRenderer sr, FeatureRenderer fr,
AlignmentI alignment)
{
- return ChimeraCommands
- .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
- fr,
- alignment);
+ return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
+ getSequence(), sr, fr, alignment);
}
/**
}
}
-
+
// End StructureListener
// //////////////////////////
public abstract SequenceRenderer getSequenceRenderer(
AlignmentViewPanel alignment);
- // jmol/ssm only
+ /**
+ * Construct and send a command to highlight an atom.
+ *
+ * <pre>
+ * Done by generating a command like (to 'highlight' position 44)
+ * ~show #0:43.C;show #0:44.C
+ * Note this removes the highlight from the previous position.
+ * </pre>
+ */
public void highlightAtom(int atomIndex, int pdbResNum, String chain,
String pdbfile)
{
List<ChimeraModel> cms = chimeraMaps.get(pdbfile);
if (cms != null)
{
- int mdlNum = cms.get(0).getModelNumber();
-
- viewerCommandHistory(false);
- // viewer.stopListening();
- if (resetLastRes.length() > 0)
+ StringBuilder sb = new StringBuilder();
+ sb.append(" #" + cms.get(0).getModelNumber());
+ sb.append(":" + pdbResNum);
+ if (!chain.equals(" "))
{
- eval.setLength(0);
- eval.append(resetLastRes.toString() + ";");
+ sb.append("." + chain);
}
+ String atomSpec = sb.toString();
- eval.append("display "); // +modelNum
-
- resetLastRes.setLength(0);
- resetLastRes.append("~display ");
+ StringBuilder command = new StringBuilder(32);
+ if (lastMousedOverAtomSpec != null)
{
- eval.append(" #" + (mdlNum));
- resetLastRes.append(" #" + (mdlNum));
+ command.append("~show " + lastMousedOverAtomSpec + ";");
}
- // complete select string
-
- eval.append(":" + pdbResNum);
- resetLastRes.append(":" + pdbResNum);
- if (!chain.equals(" "))
+ viewerCommandHistory(false);
+ command.append("show ").append(atomSpec);
+ String cmd = command.toString();
+ if (cmd.length() > 0)
{
- eval.append("." + chain);
- resetLastRes.append("." + chain);
+ viewer.stopListening(chimeraListener.getUri());
+ viewer.sendChimeraCommand(cmd, false);
+ viewer.startListening(chimeraListener.getUri());
}
-
- viewer.sendChimeraCommand(eval.toString(), false);
viewerCommandHistory(true);
- // viewer.startListening();
+ this.lastMousedOverAtomSpec = atomSpec;
}
}
- private void log(String message)
- {
- System.err.println("## Chimera log: " + message);
- }
-
- private void viewerCommandHistory(boolean enable)
- {
- // log("(Not yet implemented) History "
- // + ((debug || enable) ? "on" : "off"));
- }
-
- public void loadInline(String string)
+ /**
+ * Query Chimera for its current selection, and highlight it on the alignment
+ */
+ public void highlightChimeraSelection()
{
- loadedInline = true;
- // TODO: re JAL-623
- // viewer.loadInline(strModel, isAppend);
- // could do this:
- // construct fake fullPathName and fileName so we can identify the file
- // later.
- // Then, construct pass a reader for the string to Jmol.
- // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
- // fileName, null, reader, false, null, null, 0);
- // viewer.openStringInline(string);
- log("cannot load inline in Chimera, yet");
- }
+ /*
+ * Ask Chimera for its current selection
+ */
+ List<String> selection = viewer.getSelectedResidueSpecs();
- public void mouseOverStructure(int atomIndex, String strInfo)
- {
- // function to parse a mouseOver event from Chimera
- //
- int pdbResNum;
- int alocsep = strInfo.indexOf("^");
- int mdlSep = strInfo.indexOf("/");
- int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
-
- if (chainSeparator == -1)
+ /*
+ * Parse model number, residue and chain for each selected position,
+ * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
+ */
+ List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
+ for (String atomSpec : selection)
{
- chainSeparator = strInfo.indexOf(".");
- if (mdlSep > -1 && mdlSep < chainSeparator)
+ int colonPos = atomSpec.indexOf(":");
+ if (colonPos == -1)
{
- chainSeparator1 = chainSeparator;
- chainSeparator = mdlSep;
+ continue; // malformed
}
- }
- // handle insertion codes
- if (alocsep != -1)
- {
- pdbResNum = Integer.parseInt(strInfo.substring(
- strInfo.indexOf("]") + 1, alocsep));
-
- }
- else
- {
- pdbResNum = Integer.parseInt(strInfo.substring(
- strInfo.indexOf("]") + 1, chainSeparator));
- }
- String chainId;
-
- if (strInfo.indexOf(":") > -1)
- {
- chainId = strInfo.substring(strInfo.indexOf(":") + 1,
- strInfo.indexOf("."));
- }
- else
- {
- chainId = " ";
- }
-
- String pdbfilename = modelFileNames[frameNo]; // default is first or current
- // model
- if (mdlSep > -1)
- {
- if (chainSeparator1 == -1)
- {
- chainSeparator1 = strInfo.indexOf(".", mdlSep);
+
+ int hashPos = atomSpec.indexOf("#");
+ String modelSubmodel = atomSpec.substring(hashPos + 1, colonPos);
+ int dotPos = modelSubmodel.indexOf(".");
+ int modelId = 0;
+ try {
+ modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel
+ : modelSubmodel.substring(0, dotPos));
+ } catch (NumberFormatException e) {
+ // ignore, default to model 0
}
- String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
- chainSeparator1) : strInfo.substring(mdlSep + 1);
- try
+
+ String residueChain = atomSpec.substring(colonPos + 1);
+ dotPos = residueChain.indexOf(".");
+ int pdbResNum = Integer.parseInt(dotPos == -1 ? residueChain
+ : residueChain.substring(0, dotPos));
+
+ String chainId = dotPos == -1 ? "" : residueChain
+ .substring(dotPos + 1);
+
+ /*
+ * Work out the pdbfilename from the model number
+ */
+ String pdbfilename = modelFileNames[frameNo];
+ findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
{
- // recover PDB filename for the model hovered over.
- int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
- .intValue() - 1;
- while (mnumber < _modelFileNameMap[_mp])
- {
- _mp--;
- }
- pdbfilename = modelFileNames[_mp];
- if (pdbfilename == null)
+ for (ChimeraModel cm : chimeraMaps.get(pdbfile))
{
- // pdbfilename = new File(viewer.getModelFileName(mnumber))
- // .getAbsolutePath();
+ if (cm.getModelNumber() == modelId)
+ {
+ pdbfilename = pdbfile;
+ break findfileloop;
+ }
}
-
- } catch (Exception e)
- {
}
- ;
- }
- if (lastMessage == null || !lastMessage.equals(strInfo))
- {
- getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
+ atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0));
}
- lastMessage = strInfo;
+ /*
+ * Broadcast the selection (which may be empty, if the user just cleared all
+ * selections)
+ */
+ getSsm().mouseOverStructure(atomSpecs);
}
- public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
+ private void log(String message)
{
- /**
- * this implements the toggle label behaviour copied from the original
- * structure viewer, MCView
- */
- if (strData != null)
- {
- System.err.println("Ignoring additional pick data string " + strData);
- }
- // rewrite these selections for chimera (DNA, RNA and protein)
- int chainSeparator = strInfo.indexOf(":");
- int p = 0;
- if (chainSeparator == -1)
- {
- chainSeparator = strInfo.indexOf(".");
- }
-
- String picked = strInfo.substring(strInfo.indexOf("]") + 1,
- chainSeparator);
- String mdlString = "";
- if ((p = strInfo.indexOf(":")) > -1)
- {
- picked += strInfo.substring(p + 1, strInfo.indexOf("."));
- }
-
- if ((p = strInfo.indexOf("/")) > -1)
- {
- mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
- }
- picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
- + mdlString + "))";
- viewerCommandHistory(false);
-
- if (!atomsPicked.contains(picked))
- {
- viewer.select(picked);
- atomsPicked.add(picked);
- }
- else
- {
- viewer.select("not " + picked);
- atomsPicked.remove(picked);
- }
- viewerCommandHistory(true);
- // TODO: in application this happens
- //
- // if (scriptWindow != null)
- // {
- // scriptWindow.sendConsoleMessage(strInfo);
- // scriptWindow.sendConsoleMessage("\n");
- // }
-
+ System.err.println("## Chimera log: " + message);
}
- // incremented every time a load notification is successfully handled -
- // lightweight mechanism for other threads to detect when they can start
- // referring to new structures.
- private long loadNotifiesHandled = 0;
-
- public long getLoadNotifiesHandled()
+ private void viewerCommandHistory(boolean enable)
{
- return loadNotifiesHandled;
+ // log("(Not yet implemented) History "
+ // + ((debug || enable) ? "on" : "off"));
}
- public void notifyFileLoaded(String fullPathName, String fileName2,
- String modelName, String errorMsg, int modelParts)
+ public long getLoadNotifiesHandled()
{
- if (errorMsg != null)
- {
- fileLoadingError = errorMsg;
- refreshGUI();
- return;
- }
- // TODO: deal sensibly with models loaded inLine:
- // modelName will be null, as will fullPathName.
-
- // the rest of this routine ignores the arguments, and simply interrogates
- // the Jmol view to find out what structures it contains, and adds them to
- // the structure selection manager.
- fileLoadingError = null;
- String[] oldmodels = modelFileNames;
- modelFileNames = null;
- chainNames = new ArrayList<String>();
- chainFile = new HashMap<String, String>();
- boolean notifyLoaded = false;
- String[] modelfilenames = getPdbFile();
- // first check if we've lost any structures
- if (oldmodels != null && oldmodels.length > 0)
- {
- int oldm = 0;
- for (int i = 0; i < oldmodels.length; i++)
- {
- for (int n = 0; n < modelfilenames.length; n++)
- {
- if (modelfilenames[n] == oldmodels[i])
- {
- oldmodels[i] = null;
- break;
- }
- }
- if (oldmodels[i] != null)
- {
- oldm++;
- }
- }
- if (oldm > 0)
- {
- String[] oldmfn = new String[oldm];
- oldm = 0;
- for (int i = 0; i < oldmodels.length; i++)
- {
- if (oldmodels[i] != null)
- {
- oldmfn[oldm++] = oldmodels[i];
- }
- }
- // deregister the Jmol instance for these structures - we'll add
- // ourselves again at the end for the current structure set.
- getSsm().removeStructureViewerListener(this, oldmfn);
- }
- }
-
- // register ourselves as a listener and notify the gui that it needs to
- // update itself.
- getSsm().addStructureViewerListener(this);
-
- if (notifyLoaded)
- {
- FeatureRenderer fr = getFeatureRenderer(null);
- if (fr != null)
- {
- fr.featuresAdded();
- }
- refreshGUI();
- loadNotifiesHandled++;
- }
- setLoadingFromArchive(false);
+ return loadNotifiesHandled;
}
public void setJalviewColourScheme(ColourSchemeI cs)
return;
}
- String res;
int index;
Color col;
// Chimera expects RBG values in the range 0-1
final double normalise = 255D;
viewerCommandHistory(false);
// TODO: Switch between nucleotide or aa selection expressions
- Enumeration en = ResidueProperties.aa3Hash.keys();
StringBuilder command = new StringBuilder(128);
command.append("color white;");
- while (en.hasMoreElements())
+ for (String res : ResidueProperties.aa3Hash.keySet())
{
- res = en.nextElement().toString();
- index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
+ index = ResidueProperties.aa3Hash.get(res).intValue();
if (index > 20)
{
continue;