*/
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.ColumnSelection;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
+import jalview.httpserver.AbstractRequestHandler;
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.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
{
+ // Chimera clause to exclude alternate locations in atom selection
+ private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
private static final boolean debug = false;
private StructureManager csm;
+ /*
+ * Object through which we talk to Chimera
+ */
private ChimeraManager viewer;
/*
+ * Object which listens to Chimera notifications
+ */
+ private AbstractRequestHandler 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 String lastCommand;
- private String lastMessage;
-
private boolean loadedInline;
/**
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.
*
/*
* 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);
}
/**
+ * 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();
}
// TODO: handle sub-models
command.append(selcom[pdbfnum]);
command.append("@" + atomSpec[pdbfnum]);
+ // JAL-1757 exclude alternative CA locations
+ command.append(NO_ALTLOCS);
command.append(" #" + refStructure /* +".1" */);
command.append(selcom[refStructure]);
command.append("@" + atomSpec[refStructure]);
+ command.append(NO_ALTLOCS);
}
if (selectioncom.length() > 0)
{
*
* <pre>
* Done by generating a command like (to 'highlight' position 44)
- * ~select #0:43.C;select #0:44.C
- * Note this removes the selection from the previous position.
+ * ~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 cmd = command.toString();
if (cmd.length() > 0)
{
+ viewer.stopListening(chimeraListener.getUri());
viewer.sendChimeraCommand(cmd, false);
+ viewer.startListening(chimeraListener.getUri());
}
viewerCommandHistory(true);
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])
+ for (ChimeraModel cm : chimeraMaps.get(pdbfile))
{
- _mp--;
- }
- pdbfilename = modelFileNames[_mp];
- if (pdbfilename == null)
- {
- // 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;
}
- 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
StringBuilder command = new StringBuilder(128);
- command.append("color white;");
- for (String res : ResidueProperties.aa3Hash.keySet())
- {
- index = ResidueProperties.aa3Hash.get(res).intValue();
- if (index > 20)
- {
- continue;
- }
- col = cs.findColour(ResidueProperties.aa[index].charAt(0));
+ List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
+ false);
+ for (String res : residueSet)
+ {
+ Color col = cs.findColour(res.charAt(0));
command.append("color " + col.getRed() / normalise + ","
+ col.getGreen() / normalise + "," + col.getBlue()
/ normalise + " ::" + res + ";");