From: gmungoc Date: Tue, 23 Jun 2020 15:55:42 +0000 (+0100) Subject: JAL-3518 separation of ChimeraXManager, pull up of closeViewer etc X-Git-Tag: Develop-2_11_2_0-d20201215~24^2~44 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=4994aa94fd62af0058f2db96f0ea6c4ca1abe80b;p=jalview.git JAL-3518 separation of ChimeraXManager, pull up of closeViewer etc --- diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java index 31f5dc8..ea48cb6 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java @@ -333,10 +333,7 @@ public class ChimeraManager public void stopListening() { - // TODO send this command when viewer connection is closed in Jalview - String command = isChimeraX - ? "info notify stop models jalview; info notify stop selection jalview" - : "listen stop models ; listen stop selection "; + String command = "listen stop models ; listen stop selection "; sendChimeraCommand(command, false); } @@ -350,19 +347,13 @@ public class ChimeraManager /* * listen for model changes */ - String command = isChimeraX - ? ("info notify start models prefix ModelChanged jalview url " - + uri) - : ("listen start models url " + uri); + String command = "listen start models url " + uri; sendChimeraCommand(command, false); /* * listen for selection changes */ - command = isChimeraX - ? ("info notify start selection jalview prefix SelectionChanged url " - + uri) - : ("listen start select prefix SelectionChanged url " + uri); + command = "listen start select prefix SelectionChanged url " + uri; sendChimeraCommand(command, false); } @@ -438,10 +429,7 @@ public class ChimeraManager { List selectedResidues = new ArrayList<>(); - // in fact 'listinfo' (undocumented) works in ChimeraX - String command = (isChimeraX - ? "info" - : "list") + " selection level residue"; + String command = "list selection level residue"; List chimeraReply = sendChimeraCommand(command, true); if (chimeraReply != null) { @@ -498,7 +486,7 @@ public class ChimeraManager { List modelList = new ArrayList<>(); String command = "list models type " - + (isChimeraX ? "AtomicStructure" : "molecule"); + + (isChimeraX() ? "AtomicStructure" : "molecule"); List list = sendChimeraCommand(command, true); if (list != null) { @@ -586,7 +574,6 @@ public class ChimeraManager { // ensure symbolic links are resolved chimeraPath = Paths.get(chimeraPath).toRealPath().toString(); - isChimeraX = chimeraPath.toLowerCase().contains("chimerax"); File path = new File(chimeraPath); // uncomment the next line to simulate Chimera not installed // path = new File(chimeraPath + "x"); @@ -599,16 +586,7 @@ public class ChimeraManager args.add(chimeraPath); // shows Chimera output window but suppresses REST responses: // args.add("--debug"); - if (isChimeraX()) - { - args.add("--cmd"); - args.add("remote rest start"); - } - else - { - args.add("--start"); - args.add("RESTServer"); - } + addLaunchArguments(args); ProcessBuilder pb = new ProcessBuilder(args); chimera = pb.start(); error = ""; @@ -640,6 +618,18 @@ public class ChimeraManager } /** + * Adds command-line arguments to start the REST server + *

+ * Method extracted for Jalview to allow override in ChimeraXManager + * @param args + */ + protected void addLaunchArguments(List args) + { + args.add("--start"); + args.add("RESTServer"); + } + + /** * Read and return the port number returned in the reply to --start RESTServer */ private int getPortNumber() @@ -751,7 +741,7 @@ public class ChimeraManager public List getAttrList() { List attributes = new ArrayList<>(); - String command = (isChimeraX ? "info " : "list ") + "resattr"; + String command = (isChimeraX() ? "info " : "list ") + "resattr"; final List reply = sendChimeraCommand(command, true); if (reply != null) { @@ -811,8 +801,6 @@ public class ChimeraManager private volatile boolean busy = false; - private boolean isChimeraX; - /** * Send a command to Chimera. * @@ -873,7 +861,7 @@ public class ChimeraManager { String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run"; List commands = new ArrayList<>(1); - String method = isChimeraX() ? "GET" : "POST"; + String method = getHttpRequestMethod(); if ("GET".equals(method)) { command = command.replace(" ", "+").replace("#", "%23") @@ -914,6 +902,15 @@ public class ChimeraManager } /** + * Returns "POST" as the HTTP request method to use for REST service calls to Chimera + * @return + */ + protected String getHttpRequestMethod() + { + return "POST"; + } + + /** * Send a command to stdin of Chimera process, and optionally read any * responses. * @@ -964,11 +961,6 @@ public class ChimeraManager public boolean isChimeraX() { - return isChimeraX; - } - - public void setChimeraX(boolean b) - { - isChimeraX = b; + return false; } } diff --git a/src/jalview/ext/pymol/PymolCommands.java b/src/jalview/ext/pymol/PymolCommands.java index 7e5ba2d..be01533 100644 --- a/src/jalview/ext/pymol/PymolCommands.java +++ b/src/jalview/ext/pymol/PymolCommands.java @@ -22,6 +22,8 @@ import jalview.structure.StructureCommandsBase; */ public class PymolCommands extends StructureCommandsBase { + private static final StructureCommand CLOSE_PYMOL = new StructureCommand("quit"); + private static final StructureCommand COLOUR_BY_CHAIN = new StructureCommand("spectrum", "chain"); private static final List COLOR_BY_CHARGE = new ArrayList<>(); @@ -321,7 +323,7 @@ public class PymolCommands extends StructureCommandsBase public StructureCommandI closeViewer() { // https://pymolwiki.org/index.php/Quit - return new StructureCommand("quit"); + return CLOSE_PYMOL; } } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index dd7b446..857dbcc 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -40,6 +40,14 @@ import jalview.util.ColorUtils; */ public class ChimeraCommands extends StructureCommandsBase { + private static final StructureCommand CLOSE_CHIMERA = new StructureCommand("stop really"); + + private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand("listen stop selection"); + + private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand("listen stop models"); + + private static final StructureCommand GET_SELECTION = new StructureCommand("list selection level residue"); + private static final StructureCommand SHOW_BACKBONE = new StructureCommand( "~display all;~ribbon;chain @CA|P"); @@ -410,7 +418,33 @@ public class ChimeraCommands extends StructureCommandsBase public StructureCommandI closeViewer() { // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/stop.html - return new StructureCommand("stop really"); + return CLOSE_CHIMERA; + } + + @Override + public List startNotifications(String uri) + { + // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/listen.html + List cmds = new ArrayList<>(); + cmds.add(new StructureCommand("listen start models url " + uri)); + cmds.add(new StructureCommand("listen start select prefix SelectionChanged url " + uri)); + return cmds; + } + + @Override + public List stopNotifications() + { + // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/listen.html + List cmds = new ArrayList<>(); + cmds.add(STOP_NOTIFY_MODELS); + cmds.add(STOP_NOTIFY_SELECTION); + return cmds; + } + + @Override + public StructureCommandI getSelectedResidues() + { + return GET_SELECTION; } } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java index 889b1bc..f1a8b5f 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java @@ -21,6 +21,7 @@ package jalview.ext.rbvi.chimera; import java.awt.Color; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -34,6 +35,14 @@ import jalview.util.ColorUtils; */ public class ChimeraXCommands extends ChimeraCommands { + private static final StructureCommand CLOSE_CHIMERAX = new StructureCommand("exit"); + + private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand("info notify stop selection jalview"); + + private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand("info notify stop models jalview"); + + private static final StructureCommand GET_SELECTION = new StructureCommand("info selection level residue"); + private static final StructureCommand SHOW_BACKBONE = new StructureCommand( "~display all;~ribbon;show @CA|P atoms"); @@ -228,6 +237,32 @@ public class ChimeraXCommands extends ChimeraCommands public StructureCommandI closeViewer() { // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/exit.html - return new StructureCommand("exit"); + return CLOSE_CHIMERAX; + } + + @Override + public List startNotifications(String uri) + { + // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#notify + List cmds = new ArrayList<>(); + cmds.add(new StructureCommand("info notify start models prefix ModelChanged jalview url " + uri)); + cmds.add(new StructureCommand("info notify start selection jalview prefix SelectionChanged url " + uri)); + return cmds; + } + + @Override + public List stopNotifications() + { + // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#notify + List cmds = new ArrayList<>(); + cmds.add(STOP_NOTIFY_MODELS); + cmds.add(STOP_NOTIFY_SELECTION); + return cmds; + } + + @Override + public StructureCommandI getSelectedResidues() + { + return GET_SELECTION; } } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraXManager.java b/src/jalview/ext/rbvi/chimera/ChimeraXManager.java new file mode 100644 index 0000000..9d89ac7 --- /dev/null +++ b/src/jalview/ext/rbvi/chimera/ChimeraXManager.java @@ -0,0 +1,50 @@ +package jalview.ext.rbvi.chimera; + +import java.util.List; + +import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager; +import ext.edu.ucsf.rbvi.strucviz2.StructureManager; + +/** + * A class to help Jalview start, stop and send commands to ChimeraX. + *

+ * Much of the functionality is common with Chimera, so for convenience we + * extend ChimeraManager, however note this class is not based on the + * Cytoscape class at + * {@code https://github.com/RBVI/structureVizX/blob/master/src/main/java/edu/ucsf/rbvi/structureVizX/internal/model/ChimeraManager.java}. + * + * @author gmcarstairs + * + */ +public class ChimeraXManager extends ChimeraManager +{ + + public ChimeraXManager(StructureManager structureManager) + { + super(structureManager); + } + + public boolean isChimeraX() + { + return true; + } + + /** + * Returns "POST" as the HTTP request method to use for REST service calls to ChimeraX + * @return + */ + protected String getHttpRequestMethod() + { + return "GET"; + } + + /** + * Adds command-line arguments to start the REST server + */ + protected void addLaunchArguments(List args) + { + args.add("--cmd"); + args.add("remote rest start"); + } + +} diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index bc4eef4..b20f135 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -78,6 +78,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel String lastHighlightCommand; /** + * Returns a model of the structure positions described by the Chimera format atomspec + * @param atomSpec + * @return + */ + protected AtomSpec parseAtomSpec(String atomSpec) + { + return AtomSpec.fromChimeraAtomspec(atomSpec); + } + + /** * 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 reopen @@ -171,9 +181,9 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel DataSourceType protocol) { super(ssm, pdbentry, sequenceIs, protocol); - chimeraManager = new ChimeraManager(new StructureManager(true)); - chimeraManager.setChimeraX(ViewerType.CHIMERAX.equals(getViewerType())); - setStructureCommands(new ChimeraCommands()); + boolean chimeraX = ViewerType.CHIMERAX.equals(getViewerType()); + chimeraManager = chimeraX ? new ChimeraXManager(new StructureManager(true)) : new ChimeraManager(new StructureManager(true)); + setStructureCommands(chimeraX ? new ChimeraXCommands() : new ChimeraCommands()); } @Override @@ -191,7 +201,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel try { chimeraListener = new ChimeraListener(this); - chimeraManager.startListening(chimeraListener.getUri()); + startListening(chimeraListener.getUri()); } catch (BindException e) { System.err.println( @@ -212,6 +222,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel chimeraListener.shutdown(); chimeraListener = null; } + + /* + * the following call should not be needed but is temporarily included, + * to avoid a stack trace error in Chimera after "stop really" is sent + */ + if (closeChimera) + { + chimeraManager.getChimeraProcess().destroy(); + } + chimeraManager.clearOnChimeraExit(); chimeraManager = null; } @@ -420,14 +440,35 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel /* * Ask Chimera for its current selection */ - List selection = chimeraManager.getSelectedResidueSpecs(); + StructureCommandI command = getCommandGenerator().getSelectedResidues(); + List chimeraReply = executeCommand(command, true); + List selectedResidues = new ArrayList<>(); + if (chimeraReply != null) + { + /* + * expect 0, 1 or more lines of the format either + * Chimera: + * residue id #0:43.A type GLY + * ChimeraX: + * residue id /A:89 name THR index 88 + * We are only interested in the atomspec (third token of the reply) + */ + for (String inputLine : chimeraReply) + { + String[] inputLineParts = inputLine.split("\\s+"); + if (inputLineParts.length >= 5) + { + selectedResidues.add(inputLineParts[2]); + } + } + } /* * 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 atomSpecs = convertStructureResiduesToAlignment( - selection); + selectedResidues); /* * Broadcast the selection (which may be empty, if the user just cleared all @@ -437,7 +478,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Converts a list of Chimera atomspecs to a list of AtomSpec representing the + * Converts a list of Chimera(X) atomspecs to a list of AtomSpec representing the * corresponding residues (if any) in Jalview * * @param structureSelection @@ -446,13 +487,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel protected List convertStructureResiduesToAlignment( List structureSelection) { - boolean chimeraX = chimeraManager.isChimeraX(); List atomSpecs = new ArrayList<>(); for (String atomSpec : structureSelection) { try { - AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec, chimeraX); + AtomSpec spec = parseAtomSpec(atomSpec); String pdbfilename = getPdbFileForModel(spec.getModelNumber()); spec.setPdbFile(pdbfilename); atomSpecs.add(spec); @@ -645,7 +685,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel { boolean featureAdded = false; String featureGroup = getViewerFeatureGroup(); - boolean chimeraX = chimeraManager.isChimeraX(); for (String residue : residues) { @@ -669,7 +708,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel try { - spec = AtomSpec.fromChimeraAtomspec(atomSpec, chimeraX); + spec = parseAtomSpec(atomSpec); } catch (IllegalArgumentException e) { System.err.println("Problem parsing atomspec " + atomSpec); diff --git a/src/jalview/gui/JalviewChimeraBindingModel.java b/src/jalview/gui/JalviewChimeraBindingModel.java index 49655a4..cef92a8 100644 --- a/src/jalview/gui/JalviewChimeraBindingModel.java +++ b/src/jalview/gui/JalviewChimeraBindingModel.java @@ -20,17 +20,18 @@ */ package jalview.gui; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; + import jalview.api.AlignmentViewPanel; import jalview.api.structures.JalviewStructureDisplayI; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.ext.rbvi.chimera.JalviewChimeraBinding; import jalview.io.DataSourceType; +import jalview.structure.AtomSpec; import jalview.structure.StructureSelectionManager; -import javax.swing.JComponent; -import javax.swing.SwingUtilities; - public class JalviewChimeraBindingModel extends JalviewChimeraBinding { public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame, @@ -51,7 +52,7 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding @Override public void refreshGUI() { - javax.swing.SwingUtilities.invokeLater(new Runnable() + SwingUtilities.invokeLater(new Runnable() { @Override public void run() diff --git a/src/jalview/gui/JalviewChimeraXBindingModel.java b/src/jalview/gui/JalviewChimeraXBindingModel.java index c685f0f..5b7a928 100644 --- a/src/jalview/gui/JalviewChimeraXBindingModel.java +++ b/src/jalview/gui/JalviewChimeraXBindingModel.java @@ -10,12 +10,12 @@ import jalview.datamodel.SequenceI; import jalview.ext.rbvi.chimera.ChimeraXCommands; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; +import jalview.structure.AtomSpec; import jalview.structure.StructureCommand; import jalview.structure.StructureSelectionManager; public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel { - public static final String CHIMERAX_SESSION_EXTENSION = ".cxs"; public JalviewChimeraXBindingModel(ChimeraViewFrame chimeraViewFrame, @@ -88,4 +88,14 @@ public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel return String.valueOf(pdbfnum + 1); } + /** + * Returns a model of the structure positions described by the ChimeraX format atomspec + * @param atomSpec + * @return + */ + protected AtomSpec parseAtomSpec(String atomSpec) + { + return AtomSpec.fromChimeraXAtomspec(atomSpec); + } + } diff --git a/src/jalview/structure/AtomSpec.java b/src/jalview/structure/AtomSpec.java index 8b8161f..f404d35 100644 --- a/src/jalview/structure/AtomSpec.java +++ b/src/jalview/structure/AtomSpec.java @@ -46,21 +46,17 @@ public class AtomSpec *

    * Chimera format: 
    *    #1.2:12-20.A     model 1, submodel 2, chain A, atoms 12-20
-   * ChimeraX format:
-   *    #1.2/A:12-20
    * 
* * @param spec - * @param chimeraX * @return * @throw IllegalArgumentException if the spec cannot be parsed, or represents * more than one residue * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html - * @see http://rbvi.ucsf.edu/chimerax/docs/user/commands/atomspec.html */ - public static AtomSpec fromChimeraAtomspec(String spec, boolean chimeraX) + public static AtomSpec fromChimeraAtomspec(String spec) { - int modelSeparatorPos = spec.indexOf(chimeraX ? "/" : ":"); + int modelSeparatorPos = spec.indexOf(":"); if (modelSeparatorPos == -1) { throw new IllegalArgumentException(spec); @@ -92,9 +88,8 @@ public class AtomSpec * ChimeraX: chain:atoms */ String atomsAndChain = spec.substring(modelSeparatorPos + 1); - String[] tokens = atomsAndChain.split(chimeraX ? "\\:" : "\\."); - String atoms = tokens.length == 1 ? atomsAndChain - : (chimeraX ? tokens[1] : tokens[0]); + String[] tokens = atomsAndChain.split("\\."); + String atoms = tokens.length == 1 ? atomsAndChain : (tokens[0]); int resNum = 0; try { @@ -105,8 +100,7 @@ public class AtomSpec throw new IllegalArgumentException(spec); } - String chainId = tokens.length == 1 ? "" - : (chimeraX ? tokens[0] : tokens[1]); + String chainId = tokens.length == 1 ? "" : (tokens[1]); return new AtomSpec(modelId, chainId, resNum, 0); } @@ -179,4 +173,70 @@ public class AtomSpec return "pdbFile: " + pdbFile + ", chain: " + chain + ", res: " + pdbResNum + ", atom: " + atomIndex; } + + /** + * Parses a ChimeraX atomspec to construct an AtomSpec model (with + * null pdb file name) + * + *
+   * ChimeraX format:
+   *    #1.2/A:12-20     model 1, submodel 2, chain A, atoms 12-20
+   * 
+ * + * @param spec + * @return + * @throw IllegalArgumentException if the spec cannot be parsed, or represents + * more than one residue + * @see http://rbvi.ucsf.edu/chimerax/docs/user/commands/atomspec.html + */ + public static AtomSpec fromChimeraXAtomspec(String spec) + { + int modelSeparatorPos = spec.indexOf("/"); + if (modelSeparatorPos == -1) + { + throw new IllegalArgumentException(spec); + } + + int hashPos = spec.indexOf("#"); + if (hashPos == -1 && modelSeparatorPos != 0) + { + // # is missing but something precedes : - reject + throw new IllegalArgumentException(spec); + } + + String modelSubmodel = spec.substring(hashPos + 1, modelSeparatorPos); + int modelId = 0; + try + { + int subModelPos = modelSubmodel.indexOf("."); + modelId = Integer.valueOf( + subModelPos > 0 ? modelSubmodel.substring(0, subModelPos) + : modelSubmodel); + } catch (NumberFormatException e) + { + // ignore, default to model 0 + } + + /* + * now process what follows the model, either + * Chimera: atoms.chain + * ChimeraX: chain:atoms + */ + String atomsAndChain = spec.substring(modelSeparatorPos + 1); + String[] tokens = atomsAndChain.split("\\:"); + String atoms = tokens.length == 1 ? atomsAndChain : (tokens[1]); + int resNum = 0; + try + { + resNum = Integer.parseInt(atoms); + } catch (NumberFormatException e) + { + // could be a range e.g. #1:4-7.B + throw new IllegalArgumentException(spec); + } + + String chainId = tokens.length == 1 ? "" : (tokens[0]); + + return new AtomSpec(modelId, chainId, resNum, 0); + } } diff --git a/src/jalview/structure/StructureCommandsBase.java b/src/jalview/structure/StructureCommandsBase.java index 3c29fd4..774327c 100644 --- a/src/jalview/structure/StructureCommandsBase.java +++ b/src/jalview/structure/StructureCommandsBase.java @@ -223,4 +223,22 @@ public abstract class StructureCommandsBase implements StructureCommandsI // default does nothing, override where this is implemented return null; } + + @Override + public List startNotifications(String uri) + { + return null; + } + + @Override + public List stopNotifications() + { + return null; + } + + @Override + public StructureCommandI getSelectedResidues() + { + return null; + } } diff --git a/src/jalview/structure/StructureCommandsI.java b/src/jalview/structure/StructureCommandsI.java index 3a47f83..c224187 100644 --- a/src/jalview/structure/StructureCommandsI.java +++ b/src/jalview/structure/StructureCommandsI.java @@ -169,7 +169,35 @@ public interface StructureCommandsI /** * Returns a command to ask the viewer to close down + * * @return */ StructureCommandI closeViewer(); + + /** + * Returns one or more commands to ask the viewer to notify model or selection + * changes to the given uri. Returns null if this is not supported by the + * structure viewer. + * + * @param uri + * @return + */ + List startNotifications(String uri); + + /** + * Returns one or more commands to ask the viewer to stop notifying model or + * selection changes. Returns null if this is not supported by the structure + * viewer. + * + * @return + */ + List stopNotifications(); + + /** + * Returns a command to ask the viewer for its current residue selection, or + * null if no such command is supported + * + * @return + */ + StructureCommandI getSelectedResidues(); } diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java index 05cfd5a..f69a423 100644 --- a/src/jalview/structures/models/AAStructureBindingModel.java +++ b/src/jalview/structures/models/AAStructureBindingModel.java @@ -1585,6 +1585,8 @@ public abstract class AAStructureBindingModel externalViewerMonitor = null; } + stopListening(); + if (forceClose) { StructureCommandI cmd = getCommandGenerator().closeViewer(); @@ -1900,4 +1902,35 @@ public abstract class AAStructureBindingModel }); externalViewerMonitor.start(); } + + /** + * If supported by the external structure viewer, sends it commands to notify + * model or selection changes to the specified URL (where Jalview has started + * a listener) + * + * @param uri + */ + protected void startListening(String uri) + { + List commands = getCommandGenerator() + .startNotifications(uri); + if (commands != null) + { + executeCommands(commands, false, null); + } + } + + /** + * If supported by the external structure viewer, sends it commands to stop + * notifying model or selection changes + */ + protected void stopListening() + { + List commands = getCommandGenerator() + .stopNotifications(); + if (commands != null) + { + executeCommands(commands, false, null); + } + } } diff --git a/test/jalview/ext/pymol/PymolCommandsTest.java b/test/jalview/ext/pymol/PymolCommandsTest.java index 9759ff3..f6bad92 100644 --- a/test/jalview/ext/pymol/PymolCommandsTest.java +++ b/test/jalview/ext/pymol/PymolCommandsTest.java @@ -341,4 +341,10 @@ public class PymolCommandsTest "p.jv_side_chain_binding_='metal 'ion!'"); assertEquals(commands.get(0), expected3); } + + @Test(groups = "Functional") + public void testCloseViewer() + { + assertEquals(testee.closeViewer(), new StructureCommand("quit")); + } } diff --git a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java index b040687..15d744b 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java @@ -33,6 +33,7 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; import jalview.structure.StructureCommandI; public class ChimeraCommandsTest @@ -347,4 +348,35 @@ public class ChimeraCommandsTest assertEquals(testee.setAttribute("jv_kd", "27.3", model).getCommand(), "setattr res jv_kd '27.3' #1:89-92.A|#2:8-9.B,12-20.B"); } + + @Test(groups = "Functional") + public void testCloseViewer() + { + assertEquals(testee.closeViewer(), new StructureCommand("stop really")); + } + + @Test(groups = "Functional") + public void testGetSelectedResidues() + { + assertEquals(testee.getSelectedResidues(), + new StructureCommand("list selection level residue")); + } + + @Test(groups = "Functional") + public void testStartNotifications() + { + List cmds = testee.startNotifications("to here"); + assertEquals(cmds.size(), 2); + assertEquals(cmds.get(0), new StructureCommand("listen start models url to here")); + assertEquals(cmds.get(1), new StructureCommand("listen start select prefix SelectionChanged url to here")); + } + + @Test(groups = "Functional") + public void testStopNotifications() + { + List cmds = testee.stopNotifications(); + assertEquals(cmds.size(), 2); + assertEquals(cmds.get(0), new StructureCommand("listen stop models")); + assertEquals(cmds.get(1), new StructureCommand("listen stop selection")); + } } diff --git a/test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java index cb951c6..c1fa528 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java @@ -33,6 +33,7 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; import jalview.structure.StructureCommandI; public class ChimeraXCommandsTest @@ -322,4 +323,35 @@ public class ChimeraXCommandsTest .getCommand(), "setattr #1/A:89-92|#2/B:8-9,12-20 res jv_kd '27.3' create true"); } + + @Test(groups = "Functional") + public void testCloseViewer() + { + assertEquals(testee.closeViewer(), new StructureCommand("exit")); + } + + @Test(groups = "Functional") + public void testGetSelectedResidues() + { + assertEquals(testee.getSelectedResidues(), + new StructureCommand("info selection level residue")); + } + + @Test(groups = "Functional") + public void testStartNotifications() + { + List cmds = testee.startNotifications("to here"); + assertEquals(cmds.size(), 2); + assertEquals(cmds.get(0), new StructureCommand("info notify start models prefix ModelChanged jalview url to here")); + assertEquals(cmds.get(1), new StructureCommand("info notify start selection jalview prefix SelectionChanged url to here")); + } + + @Test(groups = "Functional") + public void testStopNotifications() + { + List cmds = testee.stopNotifications(); + assertEquals(cmds.size(), 2); + assertEquals(cmds.get(0), new StructureCommand("info notify stop models jalview")); + assertEquals(cmds.get(1), new StructureCommand("info notify stop selection jalview")); + } } diff --git a/test/jalview/structure/AtomSpecTest.java b/test/jalview/structure/AtomSpecTest.java index ff6e6cb..9881baf 100644 --- a/test/jalview/structure/AtomSpecTest.java +++ b/test/jalview/structure/AtomSpecTest.java @@ -9,23 +9,23 @@ import org.testng.annotations.Test; public class AtomSpecTest { @Test - public void testFromChimeraAtomSpec_chimera() + public void testFromChimeraAtomSpec() { - AtomSpec as = AtomSpec.fromChimeraAtomspec("#1:12.B", false); + AtomSpec as = AtomSpec.fromChimeraAtomspec("#1:12.B"); assertEquals(as.getModelNumber(), 1); assertEquals(as.getPdbResNum(), 12); assertEquals(as.getChain(), "B"); assertNull(as.getPdbFile()); // no model - default to zero - as = AtomSpec.fromChimeraAtomspec(":13.C", false); + as = AtomSpec.fromChimeraAtomspec(":13.C"); assertEquals(as.getModelNumber(), 0); assertEquals(as.getPdbResNum(), 13); assertEquals(as.getChain(), "C"); assertNull(as.getPdbFile()); // model.submodel - as = AtomSpec.fromChimeraAtomspec("#3.2:15", false); + as = AtomSpec.fromChimeraAtomspec("#3.2:15"); assertEquals(as.getModelNumber(), 3); assertEquals(as.getPdbResNum(), 15); assertEquals(as.getChain(), ""); @@ -34,7 +34,7 @@ public class AtomSpecTest String spec = "3:12.B"; try { - as = AtomSpec.fromChimeraAtomspec(spec, false); + as = AtomSpec.fromChimeraAtomspec(spec); fail("Expected exception for " + spec); } catch (IllegalArgumentException e) { @@ -44,7 +44,7 @@ public class AtomSpecTest spec = "#3:12-14.B"; try { - as = AtomSpec.fromChimeraAtomspec(spec, false); + as = AtomSpec.fromChimeraAtomspec(spec); fail("Expected exception for " + spec); } catch (IllegalArgumentException e) { @@ -54,7 +54,7 @@ public class AtomSpecTest spec = ""; try { - as = AtomSpec.fromChimeraAtomspec(spec, false); + as = AtomSpec.fromChimeraAtomspec(spec); fail("Expected exception for " + spec); } catch (IllegalArgumentException e) { @@ -64,7 +64,7 @@ public class AtomSpecTest spec = null; try { - as = AtomSpec.fromChimeraAtomspec(spec, false); + as = AtomSpec.fromChimeraAtomspec(spec); fail("Expected exception for " + spec); } catch (NullPointerException e) { @@ -73,23 +73,23 @@ public class AtomSpecTest } @Test - public void testFromChimeraAtomSpec_chimeraX() + public void testFromChimeraXAtomSpec() { - AtomSpec as = AtomSpec.fromChimeraAtomspec("#1/B:12", true); + AtomSpec as = AtomSpec.fromChimeraXAtomspec("#1/B:12"); assertEquals(as.getModelNumber(), 1); assertEquals(as.getPdbResNum(), 12); assertEquals(as.getChain(), "B"); assertNull(as.getPdbFile()); // no model - default to zero - as = AtomSpec.fromChimeraAtomspec("/C:13", true); + as = AtomSpec.fromChimeraXAtomspec("/C:13"); assertEquals(as.getModelNumber(), 0); assertEquals(as.getPdbResNum(), 13); assertEquals(as.getChain(), "C"); assertNull(as.getPdbFile()); // model.submodel - as = AtomSpec.fromChimeraAtomspec("#3.2/:15", true); + as = AtomSpec.fromChimeraXAtomspec("#3.2/:15"); assertEquals(as.getModelNumber(), 3); assertEquals(as.getPdbResNum(), 15); assertEquals(as.getChain(), ""); @@ -98,7 +98,7 @@ public class AtomSpecTest String spec = "3:12.B"; try { - as = AtomSpec.fromChimeraAtomspec(spec, true); + as = AtomSpec.fromChimeraXAtomspec(spec); fail("Expected exception for " + spec); } catch (IllegalArgumentException e) { @@ -108,7 +108,7 @@ public class AtomSpecTest spec = "#3:12-14.B"; try { - as = AtomSpec.fromChimeraAtomspec(spec, true); + as = AtomSpec.fromChimeraXAtomspec(spec); fail("Expected exception for " + spec); } catch (IllegalArgumentException e) { @@ -118,7 +118,7 @@ public class AtomSpecTest spec = ""; try { - as = AtomSpec.fromChimeraAtomspec(spec, true); + as = AtomSpec.fromChimeraXAtomspec(spec); fail("Expected exception for " + spec); } catch (IllegalArgumentException e) { @@ -128,7 +128,7 @@ public class AtomSpecTest spec = null; try { - as = AtomSpec.fromChimeraAtomspec(spec, true); + as = AtomSpec.fromChimeraXAtomspec(spec); fail("Expected exception for " + spec); } catch (NullPointerException e) {