From: gmungoc Date: Fri, 13 Mar 2020 21:48:21 +0000 (+0000) Subject: JAL-3551 working proof of concept of Jalview driving PyMOL X-Git-Tag: Release_2_11_2_0~37^2~25 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=42f4227ed213d422a87d3b22fc9e85d14ffaf53f;p=jalview.git JAL-3551 working proof of concept of Jalview driving PyMOL --- diff --git a/src/jalview/appletgui/AppletJmolBinding.java b/src/jalview/appletgui/AppletJmolBinding.java index 9a72b2e..dd6dea3 100644 --- a/src/jalview/appletgui/AppletJmolBinding.java +++ b/src/jalview/appletgui/AppletJmolBinding.java @@ -174,11 +174,4 @@ class AppletJmolBinding extends JalviewJmolBinding { return null; } - - @Override - protected void sendAsynchronousCommand(String command, String progressMsg) - { - // TODO Auto-generated method stub - - } } diff --git a/src/jalview/appletgui/ExtJmol.java b/src/jalview/appletgui/ExtJmol.java index 5be53a3..5c20136 100644 --- a/src/jalview/appletgui/ExtJmol.java +++ b/src/jalview/appletgui/ExtJmol.java @@ -179,12 +179,4 @@ public class ExtJmol extends JalviewJmolBinding { return null; } - - @Override - protected void sendAsynchronousCommand(String command, String progressMsg) - { - // TODO Auto-generated method stub - - } - } diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index fe5c980..844fb1b 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -21,8 +21,6 @@ package jalview.ext.jmol; import jalview.api.FeatureRenderer; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.HiddenColumns; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.gui.IProgressIndicator; @@ -30,10 +28,10 @@ import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; import jalview.io.StructureFile; import jalview.structure.AtomSpec; -import jalview.structure.StructureCommandsI.SuperposeData; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel; -import jalview.util.MessageManager; import java.awt.Container; import java.awt.event.ComponentEvent; @@ -41,7 +39,6 @@ import java.awt.event.ComponentListener; import java.io.File; import java.net.URL; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -125,19 +122,21 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } @Override - public List executeCommand(String command, boolean getReply) + public List executeCommand(StructureCommandI command, + boolean getReply) { if (command == null) { return null; } + String cmd = command.getCommand(); jmolHistory(false); - if (lastCommand == null || !lastCommand.equals(command)) + if (lastCommand == null || !lastCommand.equals(cmd)) { - jmolViewer.evalStringQuiet(command + "\n"); + jmolViewer.evalStringQuiet(cmd + "\n"); } jmolHistory(true); - lastCommand = command; + lastCommand = cmd; return null; } @@ -425,7 +424,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ") .append(toks.nextToken()); sb.append("|").append(label).append("\""); - executeCommand(sb.toString(), false); + executeCommand(new StructureCommand(sb.toString()), false); } } @@ -712,14 +711,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } if (matches) { - // add an entry for every chain in the model - for (int i = 0; i < pdb.getChains().size(); i++) - { - String chid = pdb.getId() + ":" - + pdb.getChains().elementAt(i).id; - addChainFile(chid, fileName); - getChainNames().add(chid); - } + stashFoundChains(pdb, fileName); notifyLoaded = true; } } @@ -955,20 +947,20 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } @Override - protected int getModelNoForFile(String pdbFile) + protected String getModelIdForFile(String pdbFile) { if (modelFileNames == null) { - return -1; + return ""; } for (int i = 0; i < modelFileNames.length; i++) { if (modelFileNames[i].equalsIgnoreCase(pdbFile)) { - return i; + return String.valueOf(i); } } - return -1; + return ""; } @Override @@ -976,4 +968,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel { return ViewerType.JMOL; } + + @Override + protected String getModelId(int pdbfnum, String file) + { + return String.valueOf(pdbfnum + 1); + } } diff --git a/src/jalview/ext/jmol/JmolCommands.java b/src/jalview/ext/jmol/JmolCommands.java index 6f682be..bd44921 100644 --- a/src/jalview/ext/jmol/JmolCommands.java +++ b/src/jalview/ext/jmol/JmolCommands.java @@ -29,6 +29,8 @@ import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceI; import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; import jalview.structure.StructureCommandsBase; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; @@ -36,6 +38,7 @@ import jalview.util.Comparison; import java.awt.Color; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -47,10 +50,20 @@ import java.util.Map; */ public class JmolCommands extends StructureCommandsBase { - private static final String CMD_COLOUR_BY_CHARGE = "select *;color white;select ASP,GLU;color red;" - + "select LYS,ARG;color blue;select CYS;color yellow"; + private static final StructureCommand SHOW_BACKBONE = new StructureCommand( + "select *; cartoons off; backbone"); - private static final String CMD_COLOUR_BY_CHAIN = "select *;color chain"; + private static final StructureCommand FOCUS_VIEW = new StructureCommand("zoom 0"); + + private static final StructureCommand COLOUR_ALL_WHITE = new StructureCommand( + "select *;color white;"); + + private static final StructureCommandI COLOUR_BY_CHARGE = new StructureCommand( + "select *;color white;select ASP,GLU;color red;" + + "select LYS,ARG;color blue;select CYS;color yellow"); + + private static final StructureCommandI COLOUR_BY_CHAIN = new StructureCommand( + "select *;color chain"); private static final String PIPE = "|"; @@ -240,41 +253,39 @@ public class JmolCommands extends StructureCommandsBase } @Override - public String colourByChain() + public StructureCommandI colourByChain() { - return CMD_COLOUR_BY_CHAIN; + return COLOUR_BY_CHAIN; } @Override - public String colourByCharge() + public List colourByCharge() { - return CMD_COLOUR_BY_CHARGE; + return Arrays.asList(COLOUR_BY_CHARGE); } @Override - public String colourByResidues(Map colours) + public List colourByResidues(Map colours) { - StringBuilder cmd = new StringBuilder(128); - cmd.append("select *;color white;"); - cmd.append(super.colourByResidues(colours)); - - return cmd.toString(); + List cmds = super.colourByResidues(colours); + cmds.add(0, COLOUR_ALL_WHITE); + return cmds; } @Override - public String setBackgroundColour(Color col) + public StructureCommandI setBackgroundColour(Color col) { - return "background " + getColourString(col); + return new StructureCommand("background " + getColourString(col)); } @Override - public String focusView() + public StructureCommandI focusView() { - return "zoom 0"; + return FOCUS_VIEW; } @Override - public String showChains(List toShow) + public List showChains(List toShow) { StringBuilder atomSpec = new StringBuilder(128); boolean first = true; @@ -295,7 +306,7 @@ public class JmolCommands extends StructureCommandsBase String spec = atomSpec.toString(); String command = "select *;restrict " + spec + ";cartoon;center " + spec; - return command; + return Arrays.asList(new StructureCommand(command)); } /** @@ -319,13 +330,13 @@ public class JmolCommands extends StructureCommandsBase * @see https://chemapps.stolaf.edu/jmol/docs/#compare */ @Override - public String superposeStructures(AtomSpecModel refAtoms, + public List superposeStructures(AtomSpecModel refAtoms, AtomSpecModel atomSpec) { StringBuilder sb = new StringBuilder(64); - int refModel = refAtoms.getModels().iterator().next(); - int model2 = atomSpec.getModels().iterator().next(); - sb.append(String.format("compare {%d.1} {%d.1}", model2, refModel)); + String refModel = refAtoms.getModels().iterator().next(); + String model2 = atomSpec.getModels().iterator().next(); + sb.append(String.format("compare {%s.1} {%s.1}", model2, refModel)); sb.append(" SUBSET {(*.CA | *.P) and conformation=1} ATOMS {"); /* @@ -344,36 +355,36 @@ public class JmolCommands extends StructureCommandsBase sb.append(getAtomSpec(refAtoms, false)).append(getCommandSeparator()) .append("cartoons"); - return sb.toString(); + return Arrays.asList(new StructureCommand(sb.toString())); } @Override - public String openCommandFile(String path) + public StructureCommandI openCommandFile(String path) { /* * https://chemapps.stolaf.edu/jmol/docs/#script * not currently used in Jalview */ - return "script " + path; + return new StructureCommand("script " + path); } @Override - public String saveSession(String filepath) + public StructureCommandI saveSession(String filepath) { /* * https://chemapps.stolaf.edu/jmol/docs/#write * not currently used in Jalview */ - return "write \"" + filepath + "\""; + return new StructureCommand("write \"" + filepath + "\""); } @Override - protected String getColourCommand(String atomSpec, Color colour) + protected StructureCommandI getColourCommand(String atomSpec, Color colour) { StringBuilder sb = new StringBuilder(atomSpec.length()+20); sb.append("select ").append(atomSpec).append(getCommandSeparator()) .append("color").append(getColourString(colour)); - return sb.toString(); + return new StructureCommand(sb.toString()); } @Override @@ -398,7 +409,7 @@ public class JmolCommands extends StructureCommandsBase StringBuilder sb = new StringBuilder(128); boolean first = true; - for (int modelNo : model.getModels()) + for (String modelNo : model.getModels()) { for (String chain : model.getChains(modelNo)) { @@ -427,8 +438,14 @@ public class JmolCommands extends StructureCommandsBase } @Override - public String showBackbone() + public List showBackbone() + { + return Arrays.asList(SHOW_BACKBONE); + } + + @Override + public StructureCommandI loadFile(String file) { - return "select *; cartoons off; backbone"; + return null; } } diff --git a/src/jalview/ext/pymol/PymolCommands.java b/src/jalview/ext/pymol/PymolCommands.java new file mode 100644 index 0000000..1638644 --- /dev/null +++ b/src/jalview/ext/pymol/PymolCommands.java @@ -0,0 +1,214 @@ +package jalview.ext.pymol; + +import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; +import jalview.structure.StructureCommandsBase; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +/** + * A class that generates commands to send to PyMol + * + * @see https://pymolwiki.org/index.php/Category:Commands + */ +public class PymolCommands extends StructureCommandsBase +{ + private static final StructureCommandI COLOUR_BY_CHAIN = new StructureCommand( + "util.cbc"); + + /* + * because the xml-rpc interface can only accept one command at a time, we can't + * concatenate commands and must instead form and send them individually + */ + private static final List COLOR_BY_CHARGE = new ArrayList<>(); + + private static final List SHOW_BACKBONE = new ArrayList<>(); + + static { + COLOR_BY_CHARGE.add(new StructureCommand("color", "white", "*")); + COLOR_BY_CHARGE + .add(new StructureCommand("color", "red", "resn ASP resn GLU")); + COLOR_BY_CHARGE.add( + new StructureCommand("color", "blue", "resn LYS resn ARG")); + COLOR_BY_CHARGE.add(new StructureCommand("color")); + SHOW_BACKBONE.add(new StructureCommand("hide", "everything")); + SHOW_BACKBONE.add(new StructureCommand("show", "ribbon")); + } + + @Override + public StructureCommandI colourByChain() + { + // https://pymolwiki.org/index.php/CBC + // TODO this doesn't execute as an xml-rpc command + return COLOUR_BY_CHAIN; + } + + @Override + public List colourByCharge() + { + return COLOR_BY_CHARGE; + } + + @Override + public StructureCommandI setBackgroundColour(Color col) + { + // https://pymolwiki.org/index.php/Bg_Color + return new StructureCommand("bg_color", getColourString(col)); + } + + /** + * Returns a colour formatted suitable for use in viewer command syntax. For + * example, red is {@code "0xff0000"}. + * + * @param c + * @return + */ + protected String getColourString(Color c) + { + return String.format("0x%02x%02x%02x", c.getRed(), c.getGreen(), + c.getBlue()); + } + + @Override + public StructureCommandI focusView() + { + // TODO what? + return null; + } + + @Override + public List showChains(List toShow) + { + // https://pymolwiki.org/index.php/Show + List commands = new ArrayList<>(); + commands.add(new StructureCommand("hide", "everything")); + commands.add(new StructureCommand("show", "lines")); + StringBuilder chains = new StringBuilder(); + for (String chain : toShow) + { + chains.append(" chain ").append(chain); + } + commands.add(new StructureCommand("show", "cartoon", chains.toString())); + return commands; + } + + @Override + public List superposeStructures(AtomSpecModel refAtoms, + AtomSpecModel atomSpec) + { + // https://pymolwiki.org/index.php/Super + List commands = new ArrayList<>(); + String refAtomsAlphaOnly = getAtomSpec(refAtoms, true); + String atomSpec2AlphaOnly = getAtomSpec(atomSpec, true); + commands.add(new StructureCommand("super", refAtomsAlphaOnly, + atomSpec2AlphaOnly)); + + /* + * and show superposed residues as cartoon + */ + String refAtomsAll = getAtomSpec(refAtoms, false); + String atomSpec2All = getAtomSpec(atomSpec, false); + commands.add(new StructureCommand("show", "cartoon", + refAtomsAll + " " + atomSpec2All)); + + return commands; + } + + @Override + public StructureCommandI openCommandFile(String path) + { + // where is this documented by PyMol? + // todo : xml-rpc answers 'method "@" is not supported' + return new StructureCommand("@" + path); // should be .pml + } + + @Override + public StructureCommandI saveSession(String filepath) + { + // https://pymolwiki.org/index.php/Save#EXAMPLES + return new StructureCommand("save", filepath); // should be .pse + } + + /** + * Returns a selection string in PyMOL 'selection macro' format: + * + *
+   * modelId// chain/residues/
+   * 
+ * + * If more than one chain, makes a selection expression for each, and they are + * separated by spaces. + * + * @see https://pymolwiki.org/index.php/Selection_Macros + */ + @Override + public String getAtomSpec(AtomSpecModel model, boolean alphaOnly) + { + StringBuilder sb = new StringBuilder(64); + boolean first = true; + for (String modelId : model.getModels()) + { + for (String chain : model.getChains(modelId)) + { + if (!first) + { + sb.append(" "); + } + first = false; + List rangeList = model.getRanges(modelId, chain); + chain = chain.trim(); + sb.append(modelId).append("//").append(chain).append("/"); + boolean firstRange = true; + for (int[] range : rangeList) + { + if (!firstRange) + { + sb.append("+"); + } + firstRange = false; + sb.append(String.valueOf(range[0])); + if (range[0] != range[1]) + { + sb.append("-").append(String.valueOf(range[1])); + } + } + sb.append("/"); + if (alphaOnly) + { + sb.append("CA"); + } + } + } + return sb.toString(); + } + + @Override + public List showBackbone() + { + return SHOW_BACKBONE; + } + + @Override + protected StructureCommandI getColourCommand(String atomSpec, Color colour) + { + // https://pymolwiki.org/index.php/Color + return new StructureCommand("color", getColourString(colour), atomSpec); + } + + @Override + protected String getResidueSpec(String residue) + { + // https://pymolwiki.org/index.php/Selection_Algebra + return "resn " + residue; + } + + @Override + public StructureCommandI loadFile(String file) + { + return new StructureCommand("load", file); + } + +} diff --git a/src/jalview/ext/pymol/PymolManager.java b/src/jalview/ext/pymol/PymolManager.java new file mode 100644 index 0000000..ad44677 --- /dev/null +++ b/src/jalview/ext/pymol/PymolManager.java @@ -0,0 +1,298 @@ +package jalview.ext.pymol; + +import jalview.bin.Cache; +import jalview.gui.Preferences; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class PymolManager +{ + private static final int RPC_REPLY_TIMEOUT_MS = 15000; + + private static final int CONNECTION_TIMEOUT_MS = 100; + + private static final String POST1 = ""; + + private static final String POST2 = ""; + + private static final String POST3 = ""; + + private Process pymolProcess; + + private int pymolXmlRpcPort; + + /** + * Returns a list of paths to try for the PyMOL executable. Any user + * preference is placed first, otherwise 'standard' paths depending on the + * operating system. + * + * @return + */ + public static List getPymolPaths() + { + List pathList = new ArrayList<>(); + + String userPath = Cache + .getDefault(Preferences.PYMOL_PATH, null); + if (userPath != null) + { + pathList.add(0, userPath); + } + + /* + * add default installation paths + */ + String pymol = "PyMOL"; + String os = System.getProperty("os.name"); + if (os.startsWith("Linux")) + { + pathList.add("/usr/local/pymol/bin/" + pymol); + pathList.add("/usr/local/bin/" + pymol); + pathList.add("/usr/bin/" + pymol); + pathList.add(System.getProperty("user.home") + "/opt/bin/" + pymol); + } + else if (os.startsWith("Windows")) + { + // todo Windows installation path(s) + } + else if (os.startsWith("Mac")) + { + pathList.add("/Applications/PyMOL.app/Contents/MacOS/" + pymol); + } + return pathList; + } + + public boolean isPymolLaunched() + { + // TODO pull up generic methods for external viewer processes + boolean launched = false; + if (pymolProcess != null) + { + try + { + pymolProcess.exitValue(); + // if we get here, process has ended + } catch (IllegalThreadStateException e) + { + // ok - not yet terminated + launched = true; + } + } + return launched; + } + + public void exitPymol() + { + if (isPymolLaunched() && pymolProcess != null) + { + sendCommand(new StructureCommand("quit"), false); + } + pymolProcess = null; + // currentModelsMap.clear(); + this.pymolXmlRpcPort = 0; + } + + /** + * Sends the command to Pymol; if requested, tries to get and return any + * replies, else returns null + * + * @param command + * @param getReply + * @return + */ + public List sendCommand(StructureCommandI command, + boolean getReply) + { + String postBody = getPostRequest(command); + // System.out.println(postBody);// debug + String rpcUrl = "http://127.0.0.1:" + this.pymolXmlRpcPort; + PrintWriter out = null; + BufferedReader in = null; + List result = new ArrayList<>(); + try + { + URL realUrl = new URL(rpcUrl); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("content-type", "text/xml"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(postBody); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) + { + result.add(line); + } + } catch (Exception e) + { + e.printStackTrace(); + } finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } catch (IOException ex) + { + ex.printStackTrace(); + } + } + return result; + } + + /** + * Builds the body of the XML-RPC format POST request to execute the command + * + * @param command + * @return + */ + static String getPostRequest(StructureCommandI command) + { + StringBuilder sb = new StringBuilder(64); + sb.append(POST1).append(command.getCommand()).append(POST2); + if (command.hasParameters()) + { + for (String p : command.getParameters()) + { + /* + * for now assuming all are string - element is optional + * refactor in future if other data types needed + * https://www.tutorialspoint.com/xml-rpc/xml_rpc_data_model.htm + */ + sb.append("").append(p) + .append(""); + } + } + sb.append(POST3); + return sb.toString(); + } + + public boolean launchPymol() + { + // todo pull up much of this + // Do nothing if already launched + if (isPymolLaunched()) + { + return true; + } + + String error = "Error message: "; + for (String pymolPath : getPymolPaths()) + { + try + { + // ensure symbolic links are resolved + pymolPath = Paths.get(pymolPath).toRealPath().toString(); + File path = new File(pymolPath); + // uncomment the next line to simulate Pymol not installed + // path = new File(pymolPath + "x"); + if (!path.canExecute()) + { + error += "File '" + path + "' does not exist.\n"; + continue; + } + List args = new ArrayList<>(); + args.add(pymolPath); + args.add("-R"); // https://pymolwiki.org/index.php/RPC + ProcessBuilder pb = new ProcessBuilder(args); + pymolProcess = pb.start(); + error = ""; + break; + } catch (Exception e) + { + // pPymol could not be started using this path + error += e.getMessage(); + } + } + if (error.length() == 0) + { + this.pymolXmlRpcPort = getPortNumber(); + System.out.println( + "PyMOL XMLRPC started on port " + pymolXmlRpcPort); + return (pymolXmlRpcPort > 0); + } + + // logger.warn(error); + return false; + } + + private int getPortNumber() + { + // TODO pull up most of this! + int port = 0; + InputStream readChan = pymolProcess.getInputStream(); + BufferedReader lineReader = new BufferedReader( + new InputStreamReader(readChan)); + StringBuilder responses = new StringBuilder(); + try + { + String response = lineReader.readLine(); + while (response != null) + { + responses.append("\n" + response); + // expect: xml-rpc server running on host localhost, port 9123 + if (response.contains("xml-rpc")) + { + String[] tokens = response.split(" "); + for (int i = 0; i < tokens.length - 1; i++) + { + if ("port".equals(tokens[i])) + { + port = Integer.parseInt(tokens[i + 1]); + break; + } + } + } + if (port > 0) + { + break; // hack for hanging readLine() + } + response = lineReader.readLine(); + } + } catch (Exception e) + { + System.err.println( + "Failed to get REST port number from " + responses + ": " + + e.getMessage()); + // logger.error("Failed to get REST port number from " + responses + ": " + // + e.getMessage()); + } finally + { + try + { + lineReader.close(); + } catch (IOException e2) + { + } + } + if (port == 0) + { + System.err.println("Failed to start PyMOL with XMLRPC, response was: " + + responses); + } + System.err.println("PyMOL started with XMLRPC on port " + port); + return port; + } + +} diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 1b1dd35..7e04f39 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -29,17 +29,17 @@ import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.gui.Desktop; import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; import jalview.structure.StructureCommandsBase; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; import jalview.util.ColorUtils; -import jalview.util.IntRangeComparator; import java.awt.Color; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -52,21 +52,25 @@ import java.util.Map; */ public class ChimeraCommands extends StructureCommandsBase { + private static final StructureCommand SHOW_BACKBONE = new StructureCommand("~display all;chain @CA|P"); + public static final String NAMESPACE_PREFIX = "jv_"; - private static final String CMD_COLOUR_BY_CHARGE = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS"; + private static final StructureCommandI COLOUR_BY_CHARGE = new StructureCommand( + "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS"); - private static final String CMD_COLOUR_BY_CHAIN = "rainbow chain"; + private static final StructureCommandI COLOUR_BY_CHAIN = new StructureCommand( + "rainbow chain"); // Chimera clause to exclude alternate locations in atom selection private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9"; @Override - public String getColourCommand(String atomSpec, Color colour) + public StructureCommandI getColourCommand(String atomSpec, Color colour) { // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/color.html String colourCode = getColourString(colour); - return "color " + colourCode + " " + atomSpec; + return new StructureCommand("color " + colourCode + " " + atomSpec); } /** @@ -92,7 +96,7 @@ public class ChimeraCommands extends StructureCommandsBase * @return */ @Override - public String[] setAttributesForFeatures( + public List setAttributesForFeatures( StructureSelectionManager ssm, String[] files, SequenceI[][] seqs, AlignmentViewPanel viewPanel) { @@ -155,6 +159,7 @@ public class ChimeraCommands extends StructureCommandsBase for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { final int modelNumber = pdbfnum + getModelStartNo(); + String modelId = String.valueOf(modelNumber); StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); if (mapping == null || mapping.length < 1) @@ -178,12 +183,12 @@ public class ChimeraCommands extends StructureCommandsBase if (!visibleFeatures.isEmpty()) { scanSequenceFeatures(visibleFeatures, structureMapping, seq, - theMap, modelNumber); + theMap, modelId); } if (showLinkedFeatures) { scanComplementFeatures(complementRenderer, structureMapping, - seq, theMap, modelNumber); + seq, theMap, modelId); } } } @@ -205,7 +210,8 @@ public class ChimeraCommands extends StructureCommandsBase protected static void scanComplementFeatures( FeatureRenderer complementRenderer, StructureMapping structureMapping, SequenceI seq, - Map> theMap, int modelNumber) + Map> theMap, + String modelNumber) { /* * for each sequence residue mapped to a structure position... @@ -269,19 +275,19 @@ public class ChimeraCommands extends StructureCommandsBase } /** - * Inspect features on the sequence; for each feature that is visible, determine - * its mapped ranges in the structure (if any) according to the given mapping, - * and add them to the map. + * Inspect features on the sequence; for each feature that is visible, + * determine its mapped ranges in the structure (if any) according to the + * given mapping, and add them to the map. * * @param visibleFeatures * @param mapping * @param seq * @param theMap - * @param modelNumber + * @param modelId */ protected static void scanSequenceFeatures(List visibleFeatures, StructureMapping mapping, SequenceI seq, - Map> theMap, int modelNumber) + Map> theMap, String modelId) { List sfs = seq.getFeatures().getPositionalFeatures( visibleFeatures.toArray(new String[visibleFeatures.size()])); @@ -321,7 +327,7 @@ public class ChimeraCommands extends StructureCommandsBase } for (int[] range : mappedRanges) { - addAtomSpecRange(featureValues, value, modelNumber, range[0], + addAtomSpecRange(featureValues, value, modelId, range[0], range[1], mapping.getChain()); } } @@ -343,10 +349,10 @@ public class ChimeraCommands extends StructureCommandsBase * @param featureMap * @return */ - protected String[] setAttributes( + protected List setAttributes( Map> featureMap) { - List commands = new ArrayList<>(); + List commands = new ArrayList<>(); for (String featureType : featureMap.keySet()) { String attributeName = makeAttributeName(featureType); @@ -368,13 +374,13 @@ public class ChimeraCommands extends StructureCommandsBase AtomSpecModel atomSpecModel = values.get(value); String featureValue = value.toString(); featureValue = featureValue.replaceAll("\\'", "'"); - String cmd = setAttribute(attributeName, featureValue, + StructureCommandI cmd = setAttribute(attributeName, featureValue, atomSpecModel); commands.add(cmd); } } - return commands.toArray(new String[commands.size()]); + return commands; } /** @@ -390,7 +396,7 @@ public class ChimeraCommands extends StructureCommandsBase * @param atomSpecModel * @return */ - protected String setAttribute(String attributeName, + protected StructureCommandI setAttribute(String attributeName, String attributeValue, AtomSpecModel atomSpecModel) { @@ -398,7 +404,7 @@ public class ChimeraCommands extends StructureCommandsBase sb.append("setattr res ").append(attributeName).append(" '") .append(attributeValue).append("' "); sb.append(getAtomSpec(atomSpecModel, false)); - return sb.toString(); + return new StructureCommand(sb.toString()); } /** @@ -435,15 +441,15 @@ public class ChimeraCommands extends StructureCommandsBase } @Override - public String colourByChain() + public StructureCommandI colourByChain() { - return CMD_COLOUR_BY_CHAIN; + return COLOUR_BY_CHAIN; } @Override - public String colourByCharge() + public List colourByCharge() { - return CMD_COLOUR_BY_CHARGE; + return Arrays.asList(COLOUR_BY_CHARGE); } @Override @@ -453,21 +459,21 @@ public class ChimeraCommands extends StructureCommandsBase } @Override - public String setBackgroundColour(Color col) + public StructureCommandI setBackgroundColour(Color col) { // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/set.html#bgcolor - return "set bgColor " + ColorUtils.toTkCode(col); + return new StructureCommand("set bgColor " + ColorUtils.toTkCode(col)); } @Override - public String focusView() + public StructureCommandI focusView() { // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/focus.html - return "focus"; + return new StructureCommand("focus"); } @Override - public String showChains(List toShow) + public List showChains(List toShow) { /* * Construct a chimera command like @@ -498,11 +504,12 @@ public class ChimeraCommands extends StructureCommandsBase */ final String command = "~display #*; ~ribbon #*; ribbon :" + cmd.toString(); - return command; + return Arrays.asList(new StructureCommand(command)); } @Override - public String superposeStructures(AtomSpecModel spec, AtomSpecModel ref) + public List superposeStructures(AtomSpecModel spec, + AtomSpecModel ref) { /* * Form Chimera match command to match spec to ref @@ -518,28 +525,26 @@ public class ChimeraCommands extends StructureCommandsBase cmd.append("match ").append(atomSpec).append(" ").append(refSpec); /* - * show superposed residues as ribbon, others as chain + * show superposed residues as ribbon */ - // fixme this should precede the loop over all alignments/structures - cmd.append(";~display all; chain @CA|P"); cmd.append("; ribbon "); cmd.append(atomSpec).append("|").append(refSpec).append("; focus"); - return cmd.toString(); + return Arrays.asList(new StructureCommand(cmd.toString())); } @Override - public String openCommandFile(String path) + public StructureCommandI openCommandFile(String path) { // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/filetypes.html - return "open cmd:" + path; + return new StructureCommand("open cmd:" + path); } @Override - public String saveSession(String filepath) + public StructureCommandI saveSession(String filepath) { // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html - return "save " + filepath; + return new StructureCommand("save " + filepath); } /** @@ -571,7 +576,7 @@ public class ChimeraCommands extends StructureCommandsBase { StringBuilder sb = new StringBuilder(128); boolean firstModel = true; - for (Integer model : atomSpec.getModels()) + for (String model : atomSpec.getModels()) { if (!firstModel) { @@ -591,7 +596,7 @@ public class ChimeraCommands extends StructureCommandsBase * @param atomSpec * @param alphaOnly */ - protected void appendModel(StringBuilder sb, Integer model, + protected void appendModel(StringBuilder sb, String model, AtomSpecModel atomSpec, boolean alphaOnly) { sb.append("#").append(model).append(":"); @@ -603,46 +608,10 @@ public class ChimeraCommands extends StructureCommandsBase chain = " ".equals(chain) ? chain : chain.trim(); List rangeList = atomSpec.getRanges(model, chain); - - /* - * sort ranges into ascending start position order - */ - Collections.sort(rangeList, IntRangeComparator.ASCENDING); - - int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0]; - int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1]; - - Iterator iterator = rangeList.iterator(); - while (iterator.hasNext()) + for (int[] range : rangeList) { - int[] range = iterator.next(); - if (range[0] <= end + 1) - { - /* - * range overlaps or is contiguous with the last one - * - so just extend the end position, and carry on - * (unless this is the last in the list) - */ - end = Math.max(end, range[1]); - } - else - { - /* - * we have a break so append the last range - */ - appendRange(sb, start, end, chain, firstPositionForModel, false); - firstPositionForModel = false; - start = range[0]; - end = range[1]; - } - } - - /* - * and append the last range - */ - if (!rangeList.isEmpty()) - { - appendRange(sb, start, end, chain, firstPositionForModel, false); + appendRange(sb, range[0], range[1], chain, firstPositionForModel, + false); firstPositionForModel = false; } } @@ -657,9 +626,15 @@ public class ChimeraCommands extends StructureCommandsBase } @Override - public String showBackbone() + public List showBackbone() + { + return Arrays.asList(SHOW_BACKBONE); + } + + @Override + public StructureCommandI loadFile(String file) { - return "~display all;chain @CA|P"; + return new StructureCommand("open " + file); } } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java index 5eba203..9636a6a 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java @@ -21,12 +21,12 @@ package jalview.ext.rbvi.chimera; import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; import jalview.util.ColorUtils; -import jalview.util.IntRangeComparator; import java.awt.Color; -import java.util.Collections; -import java.util.Iterator; +import java.util.Arrays; import java.util.List; /** @@ -34,12 +34,19 @@ import java.util.List; */ public class ChimeraXCommands extends ChimeraCommands { - private static final String CMD_COLOUR_BY_CHARGE = "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow"; + private static final StructureCommand SHOW_BACKBONE = new StructureCommand( + "~display all;show @CA|P pbonds"); + + private static final StructureCommand FOCUS_VIEW = new StructureCommand( + "view"); + + private static final StructureCommandI COLOUR_BY_CHARGE = new StructureCommand( + "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow"); @Override - public String colourByCharge() + public List colourByCharge() { - return CMD_COLOUR_BY_CHARGE; + return Arrays.asList(COLOUR_BY_CHARGE); } @Override @@ -49,26 +56,26 @@ public class ChimeraXCommands extends ChimeraCommands } @Override - public String setBackgroundColour(Color col) + public StructureCommandI setBackgroundColour(Color col) { // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/set.html - return "set bgColor " + ColorUtils.toTkCode(col); + return new StructureCommand("set bgColor " + ColorUtils.toTkCode(col)); } @Override - public String getColourCommand(String atomSpec, Color colour) + public StructureCommandI getColourCommand(String atomSpec, Color colour) { // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/color.html String colourCode = getColourString(colour); - return "color " + atomSpec + " " + colourCode; + return new StructureCommand("color " + atomSpec + " " + colourCode); } @Override - public String focusView() + public StructureCommandI focusView() { // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/view.html - return "view"; + return FOCUS_VIEW; } /** @@ -96,7 +103,7 @@ public class ChimeraXCommands extends ChimeraCommands * @return */ @Override - protected String setAttribute(String attributeName, + protected StructureCommandI setAttribute(String attributeName, String attributeValue, AtomSpecModel atomSpecModel) { StringBuilder sb = new StringBuilder(128); @@ -104,21 +111,21 @@ public class ChimeraXCommands extends ChimeraCommands sb.append(" res ").append(attributeName).append(" '") .append(attributeValue).append("'"); sb.append(" create true"); - return sb.toString(); + return new StructureCommand(sb.toString()); } @Override - public String openCommandFile(String path) + public StructureCommandI openCommandFile(String path) { // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html - return "open " + path; + return new StructureCommand("open " + path); } @Override - public String saveSession(String filepath) + public StructureCommandI saveSession(String filepath) { // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html - return "save session " + filepath; + return new StructureCommand("save session " + filepath); } /** @@ -133,7 +140,7 @@ public class ChimeraXCommands extends ChimeraCommands { StringBuilder sb = new StringBuilder(128); boolean firstModel = true; - for (Integer model : atomSpec.getModels()) + for (String model : atomSpec.getModels()) { if (!firstModel) { @@ -157,7 +164,7 @@ public class ChimeraXCommands extends ChimeraCommands * @param model * @param atomSpec */ - protected void appendModel(StringBuilder sb, Integer model, + protected void appendModel(StringBuilder sb, String model, AtomSpecModel atomSpec) { sb.append("#").append(model); @@ -167,59 +174,29 @@ public class ChimeraXCommands extends ChimeraCommands boolean firstPositionForChain = true; sb.append("/").append(chain.trim()).append(":"); List rangeList = atomSpec.getRanges(model, chain); - - /* - * sort ranges into ascending start position order - */ - Collections.sort(rangeList, IntRangeComparator.ASCENDING); - - int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0]; - int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1]; - - Iterator iterator = rangeList.iterator(); - while (iterator.hasNext()) + boolean first = true; + for (int[] range : rangeList) { - int[] range = iterator.next(); - if (range[0] <= end + 1) + if (!first) { - /* - * range overlaps or is contiguous with the last one - * - so just extend the end position, and carry on - * (unless this is the last in the list) - */ - end = Math.max(end, range[1]); + sb.append(","); } - else - { - /* - * we have a break so append the last range - */ - appendRange(sb, start, end, chain, firstPositionForChain, true); - start = range[0]; - end = range[1]; - firstPositionForChain = false; - } - } - - /* - * and append the last range - */ - if (!rangeList.isEmpty()) - { - appendRange(sb, start, end, chain, firstPositionForChain, true); + first = false; + appendRange(sb, range[0], range[1], chain, firstPositionForChain, + true); } - firstPositionForChain = false; } } @Override - public String showBackbone() + public List showBackbone() { - return "~display all;show @CA|P pbonds"; + return Arrays.asList(SHOW_BACKBONE); } @Override - public String superposeStructures(AtomSpecModel spec, AtomSpecModel ref) + public List superposeStructures(AtomSpecModel spec, + AtomSpecModel ref) { /* * Form ChimeraX match command to match spec to ref @@ -242,7 +219,7 @@ public class ChimeraXCommands extends ChimeraCommands cmd.append(getAtomSpec(spec, false)).append("|"); cmd.append(getAtomSpec(ref, false)).append("; view"); - return cmd.toString(); + return Arrays.asList(new StructureCommand(cmd.toString())); } } diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index ae34bd0..3553d68 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -24,7 +24,6 @@ import jalview.api.AlignmentViewPanel; import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; -import jalview.datamodel.HiddenColumns; import jalview.datamodel.PDBEntry; import jalview.datamodel.SearchResultMatchI; import jalview.datamodel.SearchResultsI; @@ -34,10 +33,10 @@ import jalview.gui.StructureViewer.ViewerType; import jalview.httpserver.AbstractRequestHandler; import jalview.io.DataSourceType; import jalview.structure.AtomSpec; -import jalview.structure.StructureCommandsI.SuperposeData; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel; -import jalview.util.MessageManager; import java.io.File; import java.io.FileOutputStream; @@ -45,7 +44,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.BindException; import java.util.ArrayList; -import java.util.BitSet; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -352,7 +350,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * @param getResponse */ @Override - public List executeCommand(final String command, + public List executeCommand(final StructureCommandI command, boolean getResponse) { if (chimeraManager == null || command == null) @@ -362,43 +360,21 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } List reply = null; // trim command or it may never find a match in the replyLog!! + String cmd = command.getCommand().trim(); List lastReply = chimeraManager - .sendChimeraCommand(command.trim(), getResponse); + .sendChimeraCommand(cmd, getResponse); if (getResponse) { reply = lastReply; if (debug) { - log("Response from command ('" + command + "') was:\n" + lastReply); + log("Response from command ('" + cmd + "') was:\n" + lastReply); } } return reply; } - /** - * @param command - */ - protected void executeWhenReady(String command) - { - waitForChimera(); - executeCommand(command, false); - waitForChimera(); - } - - private void waitForChimera() - { - while (chimeraManager != null && chimeraManager.isBusy()) - { - try - { - Thread.sleep(15); - } catch (InterruptedException q) - { - } - } - } - @Override public synchronized String[] getStructureFiles() { @@ -582,7 +558,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * Chimera: https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html * ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html */ - String command = getCommandGenerator().saveSession(filepath); + String command = getCommandGenerator().saveSession(filepath) + .getCommand(); List reply = chimeraManager.sendChimeraCommand(command, true); if (reply.contains("Session written")) { @@ -611,7 +588,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * Chimera: https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/open.html * ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html */ - executeCommand("open " + filepath, true); + executeCommand(getCommandGenerator().loadFile(filepath), true); // todo: test for failure - how? return true; } @@ -660,20 +637,20 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel return 0; } - String[] commands = getCommandGenerator() + List commands = getCommandGenerator() .setAttributesForFeatures(getSsm(), files, getSequence(), avp); - if (commands.length > 10) + if (commands.size() > 10) { sendCommandsByFile(commands); } else { - for (String command : commands) + for (StructureCommandI command : commands) { sendAsynchronousCommand(command, null); } } - return commands.length; + return commands.size(); } /** @@ -683,21 +660,22 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * * @param commands */ - protected void sendCommandsByFile(String[] commands) + protected void sendCommandsByFile(List commands) { try { File tmp = File.createTempFile("chim", getCommandFileExtension()); tmp.deleteOnExit(); PrintWriter out = new PrintWriter(new FileOutputStream(tmp)); - for (String command : commands) + for (StructureCommandI command : commands) { - out.println(command); + out.println(command.getCommand()); } out.flush(); out.close(); String path = tmp.getAbsolutePath(); - String command = getCommandGenerator().openCommandFile(path); + StructureCommandI command = getCommandGenerator() + .openCommandFile(path); sendAsynchronousCommand(command, null); } catch (IOException e) { @@ -737,7 +715,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel // fails for 'average.bfactor' (which is bad): String cmd = "list residues attr '" + attName + "'"; - List residues = executeCommand(cmd, true); + List residues = executeCommand(new StructureCommand(cmd), true); boolean featureAdded = createFeaturesForAttributes(attName, residues); if (featureAdded) @@ -852,14 +830,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } @Override - public int getModelNoForFile(String pdbFile) + public String getModelIdForFile(String pdbFile) { List foundModels = chimeraMaps.get(pdbFile); if (foundModels != null && !foundModels.isEmpty()) { - return foundModels.get(0).getModelNumber(); + return String.valueOf(foundModels.get(0).getModelNumber()); } - return -1; + return ""; } /** diff --git a/src/jalview/gui/AppJmol.java b/src/jalview/gui/AppJmol.java index 0768c00..4087e63 100644 --- a/src/jalview/gui/AppJmol.java +++ b/src/jalview/gui/AppJmol.java @@ -26,6 +26,7 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.gui.StructureViewer.ViewerType; +import jalview.structure.StructureCommand; import jalview.structures.models.AAStructureBindingModel; import jalview.util.BrowserLauncher; import jalview.util.ImageMaker; @@ -245,8 +246,8 @@ public class AppJmol extends StructureViewerBase { command = ""; } - jmb.executeCommand(command, false); - jmb.executeCommand("set hoverDelay=0.1", false); + jmb.executeCommand(new StructureCommand(command), false); + jmb.executeCommand(new StructureCommand("set hoverDelay=0.1"), false); jmb.setFinishedInit(true); } @@ -323,7 +324,7 @@ public class AppJmol extends StructureViewerBase cmd.append("loadingJalviewdata=true\nload APPEND "); cmd.append(filesString); cmd.append("\nloadingJalviewdata=null"); - final String command = cmd.toString(); + final StructureCommand command = new StructureCommand(cmd.toString()); lastnotify = jmb.getLoadNotifiesHandled(); try diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java index 1a5e901..a6e479d 100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@ -23,7 +23,6 @@ package jalview.gui; import jalview.api.AlignmentViewPanel; import jalview.api.FeatureRenderer; import jalview.bin.Cache; -import jalview.datamodel.AlignmentI; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.ext.rbvi.chimera.JalviewChimeraBinding; @@ -34,7 +33,6 @@ import jalview.structures.models.AAStructureBindingModel; import jalview.util.BrowserLauncher; import jalview.util.MessageManager; import jalview.util.Platform; -import jalview.ws.dbsources.Pdb; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -516,7 +514,7 @@ public class ChimeraViewFrame extends StructureViewerBase pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos], jmb.getChains()[pos], pe.getFile(), protocol, getProgressIndicator()); - stashFoundChains(pdb, pe.getFile()); + jmb.stashFoundChains(pdb, pe.getFile()); } catch (OutOfMemoryError oomerror) { @@ -572,71 +570,6 @@ public class ChimeraViewFrame extends StructureViewerBase worker = null; } - /** - * Fetch PDB data and save to a local file. Returns the full path to the file, - * or null if fetch fails. TODO: refactor to common with Jmol ? duplication - * - * @param processingEntry - * @return - * @throws Exception - */ - - private void stashFoundChains(StructureFile pdb, String file) - { - for (int i = 0; i < pdb.getChains().size(); i++) - { - String chid = new String( - pdb.getId() + ":" + pdb.getChains().elementAt(i).id); - jmb.getChainNames().add(chid); - jmb.addChainFile(chid, file); - } - } - - private String fetchPdbFile(PDBEntry processingEntry) throws Exception - { - String filePath = null; - Pdb pdbclient = new Pdb(); - AlignmentI pdbseq = null; - String pdbid = processingEntry.getId(); - long handle = System.currentTimeMillis() - + Thread.currentThread().hashCode(); - - /* - * Write 'fetching PDB' progress on AlignFrame as we are not yet visible - */ - String msg = MessageManager.formatMessage("status.fetching_pdb", - new Object[] - { pdbid }); - getAlignmentPanel().alignFrame.setProgressBar(msg, handle); - // long hdl = startProgressBar(MessageManager.formatMessage( - // "status.fetching_pdb", new Object[] - // { pdbid })); - try - { - pdbseq = pdbclient.getSequenceRecords(pdbid); - } catch (OutOfMemoryError oomerror) - { - new OOMWarning("Retrieving PDB id " + pdbid, oomerror); - } finally - { - msg = pdbid + " " + MessageManager.getString("label.state_completed"); - getAlignmentPanel().alignFrame.setProgressBar(msg, handle); - // stopProgressBar(msg, hdl); - } - /* - * If PDB data were saved and are not invalid (empty alignment), return the - * file path. - */ - if (pdbseq != null && pdbseq.getHeight() > 0) - { - // just use the file name from the first sequence's first PDBEntry - filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries() - .elementAt(0).getFile()).getAbsolutePath(); - processingEntry.setFile(filePath); - } - return filePath; - } - @Override public void eps_actionPerformed() { diff --git a/src/jalview/gui/JalviewChimeraXBindingModel.java b/src/jalview/gui/JalviewChimeraXBindingModel.java index 3a6c89c..3124fc1 100644 --- a/src/jalview/gui/JalviewChimeraXBindingModel.java +++ b/src/jalview/gui/JalviewChimeraXBindingModel.java @@ -5,6 +5,7 @@ import jalview.datamodel.SequenceI; import jalview.ext.rbvi.chimera.ChimeraXCommands; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; +import jalview.structure.StructureCommand; import jalview.structure.StructureSelectionManager; import java.util.List; @@ -40,7 +41,7 @@ public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel int modelNumber = chimeraMaps.size() + 1; String command = "setattr #" + modelNumber + " models name " + pe.getId(); - executeCommand(command, false); + executeCommand(new StructureCommand(command), false); modelsToMap.add(new ChimeraModel(pe.getId(), ModelType.PDB_MODEL, modelNumber, 0)); } @@ -79,4 +80,10 @@ public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel return ViewerType.CHIMERAX; } + @Override + protected String getModelId(int pdbfnum, String file) + { + return String.valueOf(pdbfnum + 1); + } + } diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index 3070ca1..d0f6cdb 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -107,6 +107,8 @@ public class Preferences extends GPreferences public static final String CHIMERAX_PATH = "CHIMERAX_PATH"; + public static final String PYMOL_PATH = "PYMOL_PATH"; + public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS"; public static final String SHOW_AUTOCALC_ABOVE = "SHOW_AUTOCALC_ABOVE"; @@ -347,14 +349,14 @@ public class Preferences extends GPreferences boolean isChimeraX = viewerType.equals(ViewerType.CHIMERAX.name()); if (viewerType.equals(ViewerType.JMOL.name())) { - chimeraPath.setText(""); + structureViewerPath.setText(""); } else { - chimeraPath.setText(Cache + structureViewerPath.setText(Cache .getDefault(isChimeraX ? CHIMERAX_PATH : CHIMERA_PATH, "")); } - chimeraPath.addActionListener(new ActionListener() + structureViewerPath.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) @@ -364,7 +366,7 @@ public class Preferences extends GPreferences Cache.setProperty(structViewer.getSelectedItem() .equals(ViewerType.CHIMERAX.name()) ? CHIMERAX_PATH - : CHIMERA_PATH, chimeraPath.getText()); + : CHIMERA_PATH, structureViewerPath.getText()); } } }); @@ -697,12 +699,22 @@ public class Preferences extends GPreferences Boolean.toString(useRnaView.isSelected())); Cache.applicationProperties.setProperty(STRUCT_FROM_PDB, Boolean.toString(structFromPdb.isSelected())); + String viewer = structViewer.getSelectedItem().toString(); + String viewerPath = structureViewerPath.getText(); Cache.applicationProperties.setProperty(STRUCTURE_DISPLAY, - structViewer.getSelectedItem().toString()); - boolean isChimeraX = structViewer.getSelectedItem().toString() - .equals(ViewerType.CHIMERAX.name()); - Cache.setOrRemove(isChimeraX ? CHIMERAX_PATH : CHIMERA_PATH, - chimeraPath.getText()); + viewer); + if (viewer.equals(ViewerType.CHIMERA.name())) + { + Cache.setOrRemove(CHIMERA_PATH, viewerPath); + } + else if (viewer.equals(ViewerType.CHIMERAX.name())) + { + Cache.setOrRemove(CHIMERAX_PATH, viewerPath); + } + else if (viewer.equals(ViewerType.PYMOL.name())) + { + Cache.setOrRemove(PYMOL_PATH, viewerPath); + } Cache.applicationProperties.setProperty("MAP_WITH_SIFTS", Boolean.toString(siftsMapping.isSelected())); SiftsSettings.setMapWithSifts(siftsMapping.isSelected()); @@ -1212,9 +1224,9 @@ public class Preferences extends GPreferences */ private boolean validateChimeraPath() { - if (chimeraPath.getText().trim().length() > 0) + if (structureViewerPath.getText().trim().length() > 0) { - File f = new File(chimeraPath.getText()); + File f = new File(structureViewerPath.getText()); if (!f.canExecute()) { JvOptionPane.showInternalMessageDialog(Desktop.desktop, @@ -1228,33 +1240,33 @@ public class Preferences extends GPreferences } /** - * If Chimera or ChimeraX is selected, check it can be found on default or - * user-specified path, if not show a warning/help dialog. + * If Chimera or ChimeraX or Pymol is selected, check it can be found on + * default or user-specified path, if not show a warning/help dialog */ @Override protected void structureViewer_actionPerformed(String selectedItem) { if (selectedItem.equals(ViewerType.JMOL.name())) { - chimeraPath.setEnabled(false); - chimeraPathLabel.setEnabled(false); + structureViewerPath.setEnabled(false); + structureViewerPathLabel.setEnabled(false); return; } boolean found = false; - chimeraPath.setEnabled(true); - chimeraPathLabel.setEnabled(true); - chimeraPathLabel.setText(MessageManager + structureViewerPath.setEnabled(true); + structureViewerPathLabel.setEnabled(true); + structureViewerPathLabel.setText(MessageManager .formatMessage("label.chimera_path", selectedItem)); /* * Try user-specified and standard paths for Chimera executable */ boolean isChimeraX = selectedItem.equals(ViewerType.CHIMERAX.name()); - chimeraPath.setText(Cache + structureViewerPath.setText(Cache .getDefault(isChimeraX ? CHIMERAX_PATH : CHIMERA_PATH, "")); List paths = StructureManager.getChimeraPaths(isChimeraX); - paths.add(0, chimeraPath.getText()); + paths.add(0, structureViewerPath.getText()); for (String path : paths) { if (new File(path.trim()).canExecute()) diff --git a/src/jalview/gui/PymolBindingModel.java b/src/jalview/gui/PymolBindingModel.java new file mode 100644 index 0000000..af4afb0 --- /dev/null +++ b/src/jalview/gui/PymolBindingModel.java @@ -0,0 +1,176 @@ +package jalview.gui; + +import jalview.api.AlignmentViewPanel; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SequenceI; +import jalview.ext.pymol.PymolCommands; +import jalview.ext.pymol.PymolManager; +import jalview.gui.StructureViewer.ViewerType; +import jalview.structure.AtomSpec; +import jalview.structure.StructureCommandI; +import jalview.structure.StructureSelectionManager; +import jalview.structures.models.AAStructureBindingModel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PymolBindingModel extends AAStructureBindingModel +{ + private PymolManager pymolManager; + + private Thread pymolMonitor; + + /* + * full paths to structure files opened in PyMOL + */ + List structureFiles = new ArrayList<>(); + + /* + * lookup from file path to PyMOL object name + */ + Map pymolObjects = new HashMap<>(); + + /** + * Constructor + * + * @param viewer + * @param ssm + * @param pdbentry + * @param sequenceIs + */ + public PymolBindingModel(StructureViewerBase viewer, + StructureSelectionManager ssm, PDBEntry[] pdbentry, + SequenceI[][] sequenceIs) + { + super(ssm, pdbentry, sequenceIs, null); + pymolManager = new PymolManager(); + setStructureCommands(new PymolCommands()); + setViewer(viewer); + } + + @Override + public String[] getStructureFiles() + { + return structureFiles.toArray(new String[structureFiles.size()]); + } + + @Override + public void highlightAtoms(List atoms) + { + } + + @Override + public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment) + { + // pull up? + return new SequenceRenderer(alignment.getAlignViewport()); + } + + @Override + protected List executeCommand(StructureCommandI command, + boolean getReply) + { + System.out.println(command.toString()); // debug + return pymolManager.sendCommand(command, getReply); + } + + @Override + protected String getModelIdForFile(String file) + { + return pymolObjects.containsKey(file) ? pymolObjects.get(file) : ""; + } + + @Override + protected ViewerType getViewerType() + { + return ViewerType.PYMOL; + } + + public boolean isPymolRunning() + { + return pymolManager.isPymolLaunched(); + } + + public void closeViewer(boolean closePymol) + { + getSsm().removeStructureViewerListener(this, this.getStructureFiles()); + if (closePymol) + { + pymolManager.exitPymol(); + } + // if (this.pymolListener != null) + // { + // pymolListener.shutdown(); + // pymolListener = null; + // } + pymolManager = null; + + if (pymolMonitor != null) + { + pymolMonitor.interrupt(); + } + releaseUIResources(); + } + + public boolean openSession(String pymolSessionFile) + { + StructureCommandI cmd = getCommandGenerator() + .loadFile(pymolSessionFile); + executeCommand(cmd, false); + return true; + } + + public boolean launchPymol() + { + if (pymolManager.isPymolLaunched()) + { + return true; + } + + boolean launched = pymolManager.launchPymol(); + if (launched) + { + // start listening for PyMOL selections - how?? + } + else + { + System.err.println("Failed to launch PyMOL!"); + } + return launched; + } + + public void openFile(PDBEntry pe) + { + // todo : check not already open, remap / rename, etc + String file = pe.getFile(); + StructureCommandI cmd = getCommandGenerator().loadFile(file); + + /* + * a second parameter sets the pdbid as the loaded PyMOL object name + */ + String pdbId = pe.getId(); + cmd.addParameter(pdbId); + + executeCommand(cmd, false); + + pymolObjects.put(file, pdbId); + if (!structureFiles.contains(file)) + { + structureFiles.add(file); + } + if (getSsm() != null) + { + getSsm().addStructureViewerListener(this); + } + + } + + @Override + protected String getModelId(int pdbfnum, String file) + { + return file; + } + +} diff --git a/src/jalview/gui/PymolViewer.java b/src/jalview/gui/PymolViewer.java new file mode 100644 index 0000000..09451be --- /dev/null +++ b/src/jalview/gui/PymolViewer.java @@ -0,0 +1,338 @@ +package jalview.gui; + +import jalview.api.AlignmentViewPanel; +import jalview.api.FeatureRenderer; +import jalview.bin.Cache; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SequenceI; +import jalview.gui.StructureViewer.ViewerType; +import jalview.io.DataSourceType; +import jalview.io.StructureFile; +import jalview.structures.models.AAStructureBindingModel; +import jalview.util.MessageManager; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JInternalFrame; +import javax.swing.event.InternalFrameAdapter; +import javax.swing.event.InternalFrameEvent; + +public class PymolViewer extends StructureViewerBase +{ + private static final int myWidth = 500; + + private static final int myHeight = 150; + + private PymolBindingModel binding; + + private String pymolSessionFile; + + public PymolViewer() + { + super(); + + /* + * closeViewer will decide whether or not to close this frame + * depending on whether user chooses to Cancel or not + */ + setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE); + } + + public PymolViewer(PDBEntry pdb, SequenceI[] seqs, Object object, + AlignmentPanel ap) + { + this(); + openNewPymol(ap, new PDBEntry[] { pdb }, + new SequenceI[][] + { seqs }); + } + + public PymolViewer(PDBEntry[] pe, boolean alignAdded, SequenceI[][] seqs, + AlignmentPanel ap) + { + this(); + setAlignAddedStructures(alignAdded); + openNewPymol(ap, pe, seqs); + } + + private void openNewPymol(AlignmentPanel ap, PDBEntry[] pe, + SequenceI[][] seqs) + { + createProgressBar(); + binding = new PymolBindingModel(this, ap.getStructureSelectionManager(), + pe, seqs); + addAlignmentPanel(ap); + useAlignmentPanelForColourbyseq(ap); + + if (pe.length > 1) + { + useAlignmentPanelForSuperposition(ap); + } + binding.setColourBySequence(true); + setSize(myWidth, myHeight); + initMenus(); + + addingStructures = false; + worker = new Thread(this); + worker.start(); + + this.addInternalFrameListener(new InternalFrameAdapter() + { + @Override + public void internalFrameClosing( + InternalFrameEvent internalFrameEvent) + { + closeViewer(false); + } + }); + + } + + /** + * Create a helper to manage progress bar display + */ + protected void createProgressBar() + { + if (getProgressIndicator() == null) + { + setProgressIndicator(new ProgressBar(statusPanel, statusBar)); + } + } + + @Override + public void run() + { + // todo pull up much of this + + StringBuilder errormsgs = new StringBuilder(128); + List filePDB = new ArrayList<>(); + List filePDBpos = new ArrayList<>(); + String[] curfiles = binding.getStructureFiles(); // files currently in viewer + for (int pi = 0; pi < binding.getPdbCount(); pi++) + { + String file = null; + PDBEntry thePdbEntry = binding.getPdbEntry(pi); + if (thePdbEntry.getFile() == null) + { + /* + * Retrieve PDB data, save to file, attach to PDBEntry + */ + file = fetchPdbFile(thePdbEntry); + if (file == null) + { + errormsgs.append("'" + thePdbEntry.getId() + "' "); + } + } + else + { + /* + * got file already + */ + file = new File(thePdbEntry.getFile()).getAbsoluteFile() + .getPath(); + // todo - skip if already loaded in PyMOL + } + if (file != null) + { + filePDB.add(thePdbEntry); + filePDBpos.add(Integer.valueOf(pi)); + } + } + + if (!filePDB.isEmpty()) + { + /* + * at least one structure to add to viewer + */ + binding.setFinishedInit(false); + if (!addingStructures) + { + try + { + initPymol(); + } catch (Exception ex) + { + Cache.log.error("Couldn't open PyMOL viewer!", ex); + } + } + int num = -1; + for (PDBEntry pe : filePDB) + { + num++; + if (pe.getFile() != null) + { + try + { + int pos = filePDBpos.get(num).intValue(); + long startTime = startProgressBar(getViewerName() + " " + + MessageManager.getString("status.opening_file_for") + + " " + pe.getId()); + binding.openFile(pe); + binding.addSequence(pos, binding.getSequence()[pos]); + File fl = new File(pe.getFile()); + DataSourceType protocol = DataSourceType.URL; + try + { + if (fl.exists()) + { + protocol = DataSourceType.FILE; + } + } catch (Throwable e) + { + } finally + { + stopProgressBar("", startTime); + } + + StructureFile pdb = binding.getSsm().setMapping( + binding.getSequence()[pos], binding.getChains()[pos], + pe.getFile(), protocol, + getProgressIndicator()); + binding.stashFoundChains(pdb, pe.getFile()); + } catch (Exception ex) + { + Cache.log.error( + "Couldn't open " + pe.getFile() + " in Chimera viewer!", + ex); + } finally + { + // Cache.log.debug("File locations are " + files); + } + } + } + + binding.refreshGUI(); + binding.setFinishedInit(true); + binding.setLoadingFromArchive(false); + + /* + * ensure that any newly discovered features (e.g. RESNUM) + * are added to any open feature settings dialog + */ + FeatureRenderer fr = getBinding().getFeatureRenderer(null); + if (fr != null) + { + fr.featuresAdded(); + } + + // refresh the sequence colours for the new structure(s) + for (AlignmentViewPanel ap : _colourwith) + { + binding.updateColours(ap); + } + // do superposition if asked to + if (alignAddedStructures) + { + new Thread(new Runnable() + { + @Override + public void run() + { + alignStructsWithAllAlignPanels(); + } + }).start(); + } + addingStructures = false; + } + _started = false; + worker = null; + + } + + /** + * Launch PyMOL. If we have a session file name, send PyMOL the command to + * open its saved session file. + */ + void initPymol() + { + Desktop.addInternalFrame(this, + binding.getViewerTitle(getViewerName(), true), + getBounds().width, getBounds().height); + + if (!binding.launchPymol()) + { + JvOptionPane.showMessageDialog(Desktop.desktop, + MessageManager.getString("label.pymol_failed"), + MessageManager.getString("label.error_loading_file"), + JvOptionPane.ERROR_MESSAGE); + this.dispose(); + return; + } + + if (this.pymolSessionFile != null) + { + boolean opened = binding.openSession(pymolSessionFile); + if (!opened) + { + System.err.println("An error occurred opening PyMOL session file " + + pymolSessionFile); + } + } + // binding.startPymolListener(); + } + + @Override + public AAStructureBindingModel getBinding() + { + return binding; + } + + @Override + public void closeViewer(boolean closePymol) + { + if (binding != null && binding.isPymolRunning()) + { + if (!closePymol) + { + // TODO i18n (and pull up) + String prompt = MessageManager + .formatMessage("label.confirm_close_pymol", new Object[] + { binding.getViewerTitle(getViewerName(), false) }); + prompt = JvSwingUtils.wrapTooltip(true, prompt); + int confirm = JvOptionPane.showConfirmDialog(this, prompt, + MessageManager.getString("label.close_viewer"), + JvOptionPane.YES_NO_CANCEL_OPTION); + /* + * abort closure if user hits escape or Cancel + */ + if (confirm == JvOptionPane.CANCEL_OPTION + || confirm == JvOptionPane.CLOSED_OPTION) + { + return; + } + closePymol = confirm == JvOptionPane.YES_OPTION; + } + binding.closeViewer(closePymol); + } + setAlignmentPanel(null); + _aps.clear(); + _alignwith.clear(); + _colourwith.clear(); + // TODO: check for memory leaks where instance isn't finalised because + // binding + // holds a reference to the window + binding = null; + dispose(); + } + + @Override + public String getStateInfo() + { + return null; + } + + @Override + public ViewerType getViewerType() + { + return ViewerType.PYMOL; + } + + @Override + protected String getViewerName() + { + return "PyMOL"; + } + +} diff --git a/src/jalview/gui/StructureViewer.java b/src/jalview/gui/StructureViewer.java index 79d3836..360ddf2 100644 --- a/src/jalview/gui/StructureViewer.java +++ b/src/jalview/gui/StructureViewer.java @@ -56,7 +56,7 @@ public class StructureViewer public enum ViewerType { - JMOL, CHIMERA, CHIMERAX + JMOL, CHIMERA, CHIMERAX, PYMOL }; /** @@ -170,6 +170,10 @@ public class StructureViewer sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs, ap); } + else if (viewerType.equals(ViewerType.PYMOL)) + { + sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap); + } else { Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString()); @@ -314,6 +318,10 @@ public class StructureViewer { sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap); } + else if (viewerType.equals(ViewerType.PYMOL)) + { + sview = new PymolViewer(pdb, seqsForPdb, null, ap); + } else { Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString()); diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index 6dd7d50..25d9998 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -36,6 +36,7 @@ import jalview.schemes.ColourSchemes; import jalview.structure.StructureMapping; import jalview.structures.models.AAStructureBindingModel; import jalview.util.MessageManager; +import jalview.ws.dbsources.Pdb; import java.awt.Color; import java.awt.Component; @@ -1087,4 +1088,57 @@ public abstract class StructureViewerBase extends GStructureViewer getBinding().showChains(toshow); } + /** + * Tries to fetch a PDB file and save to a temporary local file. Returns the + * saved file path if successful, or null if not. + * + * @param processingEntry + * @return + */ + protected String fetchPdbFile(PDBEntry processingEntry) + { + String filePath = null; + Pdb pdbclient = new Pdb(); + AlignmentI pdbseq = null; + String pdbid = processingEntry.getId(); + long handle = System.currentTimeMillis() + + Thread.currentThread().hashCode(); + + /* + * Write 'fetching PDB' progress on AlignFrame as we are not yet visible + */ + String msg = MessageManager.formatMessage("status.fetching_pdb", + new Object[] + { pdbid }); + getAlignmentPanel().alignFrame.setProgressBar(msg, handle); + // long hdl = startProgressBar(MessageManager.formatMessage( + // "status.fetching_pdb", new Object[] + // { pdbid })); + try + { + pdbseq = pdbclient.getSequenceRecords(pdbid); + } catch (Exception e) + { + System.err.println( + "Error retrieving PDB id " + pdbid + ": " + e.getMessage()); + } finally + { + msg = pdbid + " " + MessageManager.getString("label.state_completed"); + getAlignmentPanel().alignFrame.setProgressBar(msg, handle); + // stopProgressBar(msg, hdl); + } + /* + * If PDB data were saved and are not invalid (empty alignment), return the + * file path. + */ + if (pdbseq != null && pdbseq.getHeight() > 0) + { + // just use the file name from the first sequence's first PDBEntry + filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries() + .elementAt(0).getFile()).getAbsolutePath(); + processingEntry.setFile(filePath); + } + return filePath; + } + } diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 6c46f43..f2761dd 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -179,9 +179,9 @@ public class GPreferences extends JPanel protected JComboBox structViewer = new JComboBox<>(); - protected JLabel chimeraPathLabel; + protected JLabel structureViewerPathLabel; - protected JTextField chimeraPath = new JTextField(); + protected JTextField structureViewerPath = new JTextField(); protected ButtonGroup mappingMethod = new ButtonGroup(); @@ -1253,6 +1253,7 @@ public class GPreferences extends JPanel structViewer.addItem(ViewerType.JMOL.name()); structViewer.addItem(ViewerType.CHIMERA.name()); structViewer.addItem(ViewerType.CHIMERAX.name()); + structViewer.addItem(ViewerType.PYMOL.name()); structViewer.addActionListener(new ActionListener() { @Override @@ -1265,38 +1266,38 @@ public class GPreferences extends JPanel structureTab.add(structViewer); ypos += lineSpacing; - chimeraPathLabel = new JLabel(); - chimeraPathLabel.setFont(LABEL_FONT);// new Font("SansSerif", 0, 11)); - chimeraPathLabel.setHorizontalAlignment(SwingConstants.LEFT); - chimeraPathLabel.setText(MessageManager + structureViewerPathLabel = new JLabel(); + structureViewerPathLabel.setFont(LABEL_FONT);// new Font("SansSerif", 0, 11)); + structureViewerPathLabel.setHorizontalAlignment(SwingConstants.LEFT); + structureViewerPathLabel.setText(MessageManager .formatMessage("label.chimera_path", "Chimera(X)")); - chimeraPathLabel.setBounds(new Rectangle(10, ypos, 170, height)); - chimeraPathLabel.setEnabled(false); - structureTab.add(chimeraPathLabel); + structureViewerPathLabel.setBounds(new Rectangle(10, ypos, 170, height)); + structureViewerPathLabel.setEnabled(false); + structureTab.add(structureViewerPathLabel); - chimeraPath.setFont(LABEL_FONT); - chimeraPath.setText(""); - chimeraPath.setEnabled(false); + structureViewerPath.setFont(LABEL_FONT); + structureViewerPath.setText(""); + structureViewerPath.setEnabled(false); final String tooltip = JvSwingUtils.wrapTooltip(true, MessageManager.getString("label.chimera_path_tip")); - chimeraPath.setToolTipText(tooltip); - chimeraPath.setBounds(new Rectangle(190, ypos, 290, height)); - chimeraPath.addMouseListener(new MouseAdapter() + structureViewerPath.setToolTipText(tooltip); + structureViewerPath.setBounds(new Rectangle(190, ypos, 290, height)); + structureViewerPath.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - if (chimeraPath.isEnabled() && e.getClickCount() == 2) + if (structureViewerPath.isEnabled() && e.getClickCount() == 2) { String chosen = openFileChooser(); if (chosen != null) { - chimeraPath.setText(chosen); + structureViewerPath.setText(chosen); } } } }); - structureTab.add(chimeraPath); + structureTab.add(structureViewerPath); ypos += lineSpacing; nwMapping.setFont(LABEL_FONT); diff --git a/src/jalview/structure/AtomSpecModel.java b/src/jalview/structure/AtomSpecModel.java index 1b7d284..1ef653e 100644 --- a/src/jalview/structure/AtomSpecModel.java +++ b/src/jalview/structure/AtomSpecModel.java @@ -33,9 +33,9 @@ import java.util.TreeMap; public class AtomSpecModel { /* - * { modelNo, {chainCode, List ranges} } + * { modelId, {chainCode, List ranges} } */ - private Map> atomSpec; + private Map> atomSpec; /** * Constructor @@ -53,7 +53,7 @@ public class AtomSpecModel * @param endPos * @param chain */ - public void addRange(int model, int startPos, int endPos, String chain) + public void addRange(String model, int startPos, int endPos, String chain) { /* * Get/initialize map of data for the colour and model @@ -80,12 +80,17 @@ public class AtomSpecModel chainData.set(startPos, endPos + 1); } - public Iterable getModels() + public Iterable getModels() { return atomSpec.keySet(); } - public Iterable getChains(Integer model) + public int getModelCount() + { + return atomSpec.size(); + } + + public Iterable getChains(String model) { return atomSpec.containsKey(model) ? atomSpec.get(model).keySet() : null; @@ -99,7 +104,7 @@ public class AtomSpecModel * @param chain * @return */ - public List getRanges(Integer model, String chain) + public List getRanges(String model, String chain) { List ranges = new ArrayList<>(); if (atomSpec.containsKey(model)) diff --git a/src/jalview/structure/StructureCommand.java b/src/jalview/structure/StructureCommand.java new file mode 100644 index 0000000..f7875ab --- /dev/null +++ b/src/jalview/structure/StructureCommand.java @@ -0,0 +1,75 @@ +package jalview.structure; + +import java.util.ArrayList; +import java.util.List; + +public class StructureCommand implements StructureCommandI +{ + private String command; + + private List parameters; + + public StructureCommand(String cmd, String... params) + { + command = cmd; + if (params != null) + { + for (String p : params) + { + addParameter(p); + } + } + } + + @Override + public void addParameter(String param) + { + if (parameters == null) + { + parameters = new ArrayList<>(); + } + parameters.add(param); + } + + @Override + public String getCommand() + { + return command; + } + + @Override + public List getParameters() + { + return parameters; + } + + @Override + public boolean hasParameters() + { + return parameters != null && !parameters.isEmpty(); + } + + @Override + public String toString() + { + if (!hasParameters()) + { + return command; + } + StringBuilder sb = new StringBuilder(32); + sb.append(command).append("("); + boolean first = true; + for (String p : parameters) + { + if (!first) + { + sb.append(","); + } + first = false; + sb.append(p); + } + sb.append(")"); + return sb.toString(); + } + +} diff --git a/src/jalview/structure/StructureCommandI.java b/src/jalview/structure/StructureCommandI.java new file mode 100644 index 0000000..e39bbba --- /dev/null +++ b/src/jalview/structure/StructureCommandI.java @@ -0,0 +1,14 @@ +package jalview.structure; + +import java.util.List; + +public interface StructureCommandI +{ + String getCommand(); + + List getParameters(); + + void addParameter(String param); + + boolean hasParameters(); +} diff --git a/src/jalview/structure/StructureCommandsBase.java b/src/jalview/structure/StructureCommandsBase.java index 44764db..8c6ea4e 100644 --- a/src/jalview/structure/StructureCommandsBase.java +++ b/src/jalview/structure/StructureCommandsBase.java @@ -1,18 +1,10 @@ package jalview.structure; -import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; -import jalview.api.FeatureRenderer; -import jalview.api.SequenceRenderer; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceI; -import jalview.renderer.seqfeatures.FeatureColourFinder; -import jalview.util.Comparison; import java.awt.Color; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -39,7 +31,8 @@ public abstract class StructureCommandsBase implements StructureCommandsI } @Override - public String[] setAttributesForFeatures(StructureSelectionManager ssm, + public List setAttributesForFeatures( + StructureSelectionManager ssm, String[] files, SequenceI[][] sequence, AlignmentViewPanel avp) { // default does nothing, override where this is implemented @@ -74,8 +67,8 @@ public abstract class StructureCommandsBase implements StructureCommandsI * @param chain */ public static final void addAtomSpecRange(Map map, - Object value, - int model, int startPos, int endPos, String chain) + Object value, String model, int startPos, int endPos, + String chain) { /* * Get/initialize map of data for the colour @@ -99,30 +92,22 @@ public abstract class StructureCommandsBase implements StructureCommandsI * @return */ @Override - public String[] colourBySequence(Map colourMap) + public List colourBySequence( + Map colourMap) { /* - * This version concatenates all commands into a single String (semi-colon - * delimited). If length limit issues arise, refactor to return one color - * command per colour. + * default implementation creates one command per colour; + * override to concatenate colour commands if wanted */ - List commands = new ArrayList<>(); - StringBuilder sb = new StringBuilder(256); - boolean firstColour = true; + List commands = new ArrayList<>(); for (Object key : colourMap.keySet()) { Color colour = (Color) key; - if (!firstColour) - { - sb.append(getCommandSeparator()).append(" "); - } - firstColour = false; final AtomSpecModel colourData = colourMap.get(colour); - sb.append(getColourCommand(colourData, colour)); + commands.add(getColourCommand(colourData, colour)); } - commands.add(sb.toString()); - return commands.toArray(new String[commands.size()]); + return commands; } /** @@ -133,7 +118,8 @@ public abstract class StructureCommandsBase implements StructureCommandsI * @param colour * @return */ - protected String getColourCommand(AtomSpecModel atomSpecModel, Color colour) + protected StructureCommandI getColourCommand(AtomSpecModel atomSpecModel, + Color colour) { String atomSpec = getAtomSpec(atomSpecModel, false); return getColourCommand(atomSpec, colour); @@ -147,21 +133,25 @@ public abstract class StructureCommandsBase implements StructureCommandsI * @param colour * @return */ - protected abstract String getColourCommand(String atomSpec, Color colour); + protected abstract StructureCommandI getColourCommand(String atomSpec, + Color colour); @Override - public String colourByResidues(Map colours) + public List colourByResidues( + Map colours) { - StringBuilder cmd = new StringBuilder(12 * colours.size()); - + List commands = new ArrayList<>(); for (Entry entry : colours.entrySet()) { - String residue = entry.getKey(); - String atomSpec = getResidueSpec(residue); - cmd.append(getColourCommand(atomSpec, entry.getValue())); - cmd.append(getCommandSeparator()); + commands.add(colourResidue(entry.getKey(), entry.getValue())); } - return cmd.toString(); + return commands; + } + + private StructureCommandI colourResidue(String resName, Color col) + { + String atomSpec = getResidueSpec(resName); + return getColourCommand(atomSpec, col); } /** diff --git a/src/jalview/structure/StructureCommandsFactory.java b/src/jalview/structure/StructureCommandsFactory.java deleted file mode 100644 index 9319427..0000000 --- a/src/jalview/structure/StructureCommandsFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -package jalview.structure; - -import jalview.ext.jmol.JmolCommands; -import jalview.ext.rbvi.chimera.ChimeraCommands; -import jalview.ext.rbvi.chimera.ChimeraXCommands; -import jalview.gui.StructureViewer.ViewerType; - -/** - * A factory that serves a class that can generate structure commands for a - * specified structure viewer - */ -public class StructureCommandsFactory -{ - public StructureCommandsI getStructureCommands(ViewerType viewer) - { - StructureCommandsI commands = null; - switch (viewer) - { - case JMOL: - commands = new JmolCommands(); - break; - case CHIMERA: - commands = new ChimeraCommands(); - break; - case CHIMERAX: - commands = new ChimeraXCommands(); - break; - default: - } - return commands; - } -} diff --git a/src/jalview/structure/StructureCommandsI.java b/src/jalview/structure/StructureCommandsI.java index 359eac6..0934488 100644 --- a/src/jalview/structure/StructureCommandsI.java +++ b/src/jalview/structure/StructureCommandsI.java @@ -17,47 +17,11 @@ import java.util.Map; public interface StructureCommandsI { /** - * Data bean class to simplify parameterisation in superposeStructures - */ - public class SuperposeData - { - public String filename; - - public String pdbId; - - public String chain = ""; - - public boolean isRna; - - /* - * The pdb residue number (if any) mapped to columns of the alignment - */ - public int[] pdbResNo; // or use SparseIntArray? - - public int modelNo; - - /** - * Constructor - * - * @param width - * width of alignment (number of columns that may potentially - * participate in superposition) - * @param model - * structure viewer model number - */ - public SuperposeData(int width, int model) - { - pdbResNo = new int[width]; - modelNo = model; - } - } - - /** * Returns the command to colour by chain * * @return */ - String colourByChain(); + StructureCommandI colourByChain(); /** * Returns the command to colour residues using a charge-based scheme: @@ -70,7 +34,7 @@ public interface StructureCommandsI * * @return */ - String colourByCharge(); + List colourByCharge(); /** * Returns the command to colour residues with the colours provided in the @@ -79,7 +43,7 @@ public interface StructureCommandsI * @param colours * @return */ - String colourByResidues(Map colours); + List colourByResidues(Map colours); /** * Returns the command to set the background colour of the structure viewer @@ -87,7 +51,7 @@ public interface StructureCommandsI * @param col * @return */ - String setBackgroundColour(Color col); + StructureCommandI setBackgroundColour(Color col); /** * Returns commands to colour mapped residues of structures according to @@ -98,23 +62,24 @@ public interface StructureCommandsI * @return */ - String[] colourBySequence(Map colourMap); + List colourBySequence( + Map colourMap); /** * Returns a command to centre the display in the structure viewer * * @return */ - String focusView(); + StructureCommandI focusView(); /** * Returns a command to show only the selected chains. The items in the input - * list should be formatted as "modelno:chainid". + * list should be formatted as "modelid:chainid". * * @param toShow * @return */ - String showChains(List toShow); + List showChains(List toShow); /** * Returns zero, one or more commands to set attributes on mapped residues in @@ -126,7 +91,8 @@ public interface StructureCommandsI * @param avp * @return */ - String[] setAttributesForFeatures(StructureSelectionManager ssm, + List setAttributesForFeatures( + StructureSelectionManager ssm, String[] files, SequenceI[][] sequence, AlignmentViewPanel avp); /** @@ -139,7 +105,7 @@ public interface StructureCommandsI * @param atomSpec * @return */ - String superposeStructures(AtomSpecModel refAtoms, + List superposeStructures(AtomSpecModel refAtoms, AtomSpecModel atomSpec); /** @@ -148,7 +114,7 @@ public interface StructureCommandsI * @param path * @return */ - String openCommandFile(String path); + StructureCommandI openCommandFile(String path); /** * Returns a command to save the current viewer session state to the given @@ -157,7 +123,7 @@ public interface StructureCommandsI * @param filepath * @return */ - String saveSession(String filepath); + StructureCommandI saveSession(String filepath); /** * Returns a representation of the atom set represented by the model, in @@ -181,9 +147,19 @@ public interface StructureCommandsI int getModelStartNo(); /** - * Show only the backbone of the peptide (cartoons in Jmol, chain in Chimera) + * Returns command(s) to show only the backbone of the peptide (cartoons in + * Jmol, chain in Chimera) + * + * @return + */ + List showBackbone(); + + /** + * Returns a command to open a file at the given path * + * @param file * @return */ - String showBackbone(); + // refactor if needed to distinguish loading data or session files + StructureCommandI loadFile(String file); } diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java index 0c1cd50..8d8957c 100644 --- a/src/jalview/structures/models/AAStructureBindingModel.java +++ b/src/jalview/structures/models/AAStructureBindingModel.java @@ -32,13 +32,14 @@ import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; +import jalview.io.StructureFile; import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.schemes.ColourSchemeI; import jalview.schemes.ResidueProperties; import jalview.structure.AtomSpec; import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommandI; import jalview.structure.StructureCommandsI; -import jalview.structure.StructureCommandsI.SuperposeData; import jalview.structure.StructureListener; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; @@ -69,6 +70,42 @@ public abstract class AAStructureBindingModel extends SequenceStructureBindingModel implements StructureListener, StructureSelectionManagerProvider { + /** + * Data bean class to simplify parameterisation in superposeStructures + */ + public static class SuperposeData + { + public String filename; + + public String pdbId; + + public String chain = ""; + + public boolean isRna; + + /* + * The pdb residue number (if any) mapped to columns of the alignment + */ + public int[] pdbResNo; // or use SparseIntArray? + + public String modelId; + + /** + * Constructor + * + * @param width + * width of alignment (number of columns that may potentially + * participate in superposition) + * @param model + * structure viewer model number + */ + public SuperposeData(int width, String model) + { + pdbResNo = new int[width]; + modelId = model; + } + } + private static final int MIN_POS_TO_SUPERPOSE = 4; private static final String COLOURING_STRUCTURES = MessageManager @@ -125,7 +162,7 @@ public abstract class AAStructureBindingModel private boolean finishedInit = false; /** - * current set of model filenames loaded in the Jmol instance + * current set of model filenames loaded in the viewer */ protected String[] modelFileNames = null; @@ -613,7 +650,7 @@ public abstract class AAStructureBindingModel * @return */ protected int findSuperposableResidues(AlignmentI alignment, - BitSet matched, SuperposeData[] structures) + BitSet matched, AAStructureBindingModel.SuperposeData[] structures) { int refStructure = -1; String[] files = getStructureFiles(); @@ -828,11 +865,11 @@ public abstract class AAStructureBindingModel } } - SuperposeData[] structures = new SuperposeData[files.length]; + AAStructureBindingModel.SuperposeData[] structures = new AAStructureBindingModel.SuperposeData[files.length]; for (int f = 0; f < files.length; f++) { - structures[f] = new SuperposeData(width, - f + commandGenerator.getModelStartNo()); + structures[f] = new AAStructureBindingModel.SuperposeData(width, + getModelIdForFile(files[f])); } /* @@ -864,7 +901,8 @@ public abstract class AAStructureBindingModel * Show all as backbone before doing superposition(s) * (residues used for matching will be shown as ribbon) */ - executeCommand(commandGenerator.showBackbone(), false); + // todo better way to ensure synchronous than setting getReply true!! + executeCommands(commandGenerator.showBackbone(), true, null); /* * superpose each (other) structure to the reference in turn @@ -874,9 +912,10 @@ public abstract class AAStructureBindingModel if (i != refStructure) { AtomSpecModel atomSpec = getAtomSpec(structures[i], matched); - String commands = commandGenerator.superposeStructures(refAtoms, + List commands = commandGenerator + .superposeStructures(refAtoms, atomSpec); - List replies = executeCommands(true, commands); + List replies = executeCommands(commands, true, null); for (String reply : replies) { // return this error (Chimera only) to the user @@ -892,7 +931,7 @@ public abstract class AAStructureBindingModel return error; } - private AtomSpecModel getAtomSpec(SuperposeData superposeData, + private AtomSpecModel getAtomSpec(AAStructureBindingModel.SuperposeData superposeData, BitSet matched) { AtomSpecModel model = new AtomSpecModel(); @@ -900,7 +939,7 @@ public abstract class AAStructureBindingModel while (nextColumnMatch != -1) { int pdbResNum = superposeData.pdbResNo[nextColumnMatch]; - model.addRange(superposeData.modelNo, pdbResNum, pdbResNum, + model.addRange(superposeData.modelId, pdbResNum, pdbResNum, superposeData.chain); nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1); } @@ -941,7 +980,7 @@ public abstract class AAStructureBindingModel { colourBySequence = false; - executeCommand(commandGenerator.colourByCharge(), false, + executeCommands(commandGenerator.colourByCharge(), false, COLOURING_STRUCTURES); } @@ -980,13 +1019,14 @@ public abstract class AAStructureBindingModel /* * pass to the command constructor, and send the command */ - String cmd = commandGenerator.colourByResidues(colours); - executeCommand(cmd, false, COLOURING_STRUCTURES); + List cmd = commandGenerator + .colourByResidues(colours); + executeCommands(cmd, false, COLOURING_STRUCTURES); } public void setBackgroundColour(Color col) { - String cmd = commandGenerator.setBackgroundColour(col); + StructureCommandI cmd = commandGenerator.setBackgroundColour(col); executeCommand(cmd, false, null); } @@ -1002,95 +1042,119 @@ public abstract class AAStructureBindingModel * @param msg * @return */ - private List executeCommand(String cmd, boolean getReply, - String msg) + private List executeCommand(StructureCommandI cmd, + boolean getReply, String msg) { if (getReply) { - return executeSynchronous(cmd, msg, getReply); + /* + * synchronous (same thread) execution so reply can be returned + */ + final JalviewStructureDisplayI theViewer = getViewer(); + final long handle = msg == null ? 0 : theViewer.startProgressBar(msg); + try + { + return executeCommand(cmd, getReply); + } finally + { + if (msg != null) + { + theViewer.stopProgressBar(null, handle); + } + } } else { - executeAsynchronous(cmd, msg); + /* + * asynchronous (new thread) execution if no reply needed + */ + final JalviewStructureDisplayI theViewer = getViewer(); + final long handle = msg == null ? 0 : theViewer.startProgressBar(msg); + + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + try + { + executeCommand(cmd, false); + } finally + { + if (msg != null) + { + theViewer.stopProgressBar(null, handle); + } + } + } + }); return null; } } /** - * Sends the command in the current thread. If a message is supplied, this is - * shown before the thread is started, and removed when it completes. May - * return a reply to the command if requested. + * Execute one structure viewer command. If {@code getReply} is true, may + * optionally return one or more reply messages, else returns null. * * @param cmd - * @param msg * @param getReply - * @return */ - private List executeSynchronous(String cmd, String msg, boolean getReply) + protected abstract List executeCommand(StructureCommandI cmd, + boolean getReply); + + /** + * A helper method that converts list of commands to a vararg array + * + * @param commands + * @param getReply + * @param msg + */ + private List executeCommands(List commands, + boolean getReply, String msg) { - final JalviewStructureDisplayI theViewer = getViewer(); - final long handle = msg == null ? 0 : theViewer.startProgressBar(msg); - try - { - return executeCommand(cmd, getReply); - } finally - { - if (msg != null) - { - theViewer.stopProgressBar(null, handle); - } - } + return executeCommands(getReply, msg, + commands.toArray(new StructureCommandI[commands.size()])); } /** - * Sends the command in a separate thread. If a message is supplied, this is - * shown before the thread is started, and removed when it completes. No value - * is returned. + * Executes one or more structure viewer commands. If a progress message is + * provided, it is shown first, and removed after all commands have been run. * - * @param cmd + * @param getReply * @param msg + * @param commands + * @return */ - private void executeAsynchronous(String cmd, String msg) + protected List executeCommands(boolean getReply, String msg, + StructureCommandI[] commands) { + // todo: tidy this up + + /* + * show progress message if specified + */ final JalviewStructureDisplayI theViewer = getViewer(); final long handle = msg == null ? 0 : theViewer.startProgressBar(msg); - SwingUtilities.invokeLater(new Runnable() + List response = getReply ? new ArrayList<>() : null; + try { - @Override - public void run() + for (StructureCommandI cmd : commands) { - try - { - executeCommand(cmd, false); - } finally + List replies = executeCommand(cmd, getReply, null); + if (getReply && replies != null) { - if (msg != null) - { - theViewer.stopProgressBar(null, handle); - } + response.addAll(replies); } } - }); - } - - protected abstract List executeCommand(String command, - boolean getReply); - - protected List executeCommands(boolean getReply, - String... commands) - { - // todo: tidy this up - List response = getReply ? new ArrayList<>() : null; - for (String cmd : commands) + return response; + } finally { - List replies = executeCommand(cmd, getReply); - if (getReply && replies != null) + if (msg != null) { - response.addAll(replies); + theViewer.stopProgressBar(null, handle); } } - return response; } /** @@ -1114,9 +1178,9 @@ public abstract class AAStructureBindingModel Map colourMap = buildColoursMap(ssm, files, sequence, sr, alignmentv); - String[] colourBySequenceCommands = commandGenerator + List colourBySequenceCommands = commandGenerator .colourBySequence(colourMap); - executeCommands(false, colourBySequenceCommands); + executeCommands(colourBySequenceCommands, false, null); } /** @@ -1124,7 +1188,7 @@ public abstract class AAStructureBindingModel */ public void focusView() { - executeCommand(commandGenerator.focusView(), false); + executeCommand(commandGenerator.focusView(), false, null); } /** @@ -1150,22 +1214,21 @@ public abstract class AAStructureBindingModel if (tokens.length == 2) { String pdbFile = getFileForChain(chainId); - int modelNo = getModelNoForFile(pdbFile); - String model = modelNo == -1 ? "" : String.valueOf(modelNo); + String model = getModelIdForFile(pdbFile); showThese.add(model + ":" + tokens[1]); } } - executeCommand(commandGenerator.showChains(showThese), false); + executeCommands(commandGenerator.showChains(showThese), false, null); } /** - * Answers the structure viewer's model number given a PDB file name. Returns - * -1 if model number is not found. + * Answers the structure viewer's model id given a PDB file name. Returns an + * empty string if model id is not found. * * @param chainId * @return */ - protected abstract int getModelNoForFile(String chainId); + protected abstract String getModelIdForFile(String chainId); public boolean hasFileLoadingError() { @@ -1247,7 +1310,8 @@ public abstract class AAStructureBindingModel * @param command * @param progressMsg */ - protected void sendAsynchronousCommand(String command, String progressMsg) + protected void sendAsynchronousCommand(StructureCommandI command, + String progressMsg) { final JalviewStructureDisplayI theViewer = getViewer(); final long handle = progressMsg == null ? 0 @@ -1259,7 +1323,7 @@ public abstract class AAStructureBindingModel { try { - executeCommand(command, false); + executeCommand(command, false, null); } finally { if (progressMsg != null) @@ -1279,7 +1343,7 @@ public abstract class AAStructureBindingModel * {@code AtomSpecModel}, where the atomspec model holds * *
-   *   Model numbers
+   *   Model ids
    *     Chains
    *       Residue positions
    * 
@@ -1308,7 +1372,8 @@ public abstract class AAStructureBindingModel for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { - final int modelNumber = pdbfnum + commandGenerator.getModelStartNo(); + // todo indirect this resolution / allow override + final String modelId = getModelIdForFile(files[pdbfnum]); StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); if (mapping == null || mapping.length < 1) @@ -1365,7 +1430,7 @@ public abstract class AAStructureBindingModel { if (startPos != -1) { - addAtomSpecRange(colourMap, lastColour, modelNumber, + addAtomSpecRange(colourMap, lastColour, modelId, startPos, lastPos, lastChain); } startPos = pos; @@ -1377,7 +1442,7 @@ public abstract class AAStructureBindingModel // final colour range if (lastColour != null) { - addAtomSpecRange(colourMap, lastColour, modelNumber, startPos, + addAtomSpecRange(colourMap, lastColour, modelId, startPos, lastPos, lastChain); } // break; @@ -1389,6 +1454,35 @@ public abstract class AAStructureBindingModel } /** + * todo better refactoring (map lookup or similar to get viewer structure id) + * + * @param pdbfnum + * @param file + * @return + */ + protected String getModelId(int pdbfnum, String file) + { + return String.valueOf(pdbfnum); + } + + /** + * Saves chains, formatted as "pdbId:chainCode", and lookups from this to the + * full PDB file path + * + * @param pdb + * @param file + */ + public void stashFoundChains(StructureFile pdb, String file) + { + for (int i = 0; i < pdb.getChains().size(); i++) + { + String chid = pdb.getId() + ":" + pdb.getChains().elementAt(i).id; + addChainFile(chid, file); + getChainNames().add(chid); + } + } + + /** * Helper method to add one contiguous range to the AtomSpec model for the given * value (creating the model if necessary). As used by Jalview, {@code value} is *