Merge branch 'features/JAL-1333' into Release_2_8_2_Branch
authorj.procter@dundee.ac.uk <jprocter@jims-mbp-2.home>
Tue, 22 Jul 2014 19:22:04 +0000 (20:22 +0100)
committerj.procter@dundee.ac.uk <jprocter@jims-mbp-2.home>
Tue, 22 Jul 2014 19:22:04 +0000 (20:22 +0100)
41 files changed:
.classpath
THIRDPARTYLIBS
lib/log4j-1.2.8.jar [deleted file]
src/ext/edu/ucsf/rbvi/strucviz2/ChimUtils.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraChain.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraModel.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraResidue.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraStructuralObject.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraTreeModel.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/StructureSettings.java [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java [new file with mode: 0644]
src/jalview/api/SequenceStructureBinding.java
src/jalview/api/structures/JalviewStructureDisplayI.java [new file with mode: 0644]
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AppletJmol.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/PDBFileWithJmol.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java [new file with mode: 0644]
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java [new file with mode: 0644]
src/jalview/ext/varna/JalviewVarnaBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AppJmol.java
src/jalview/gui/ChimeraViewFrame.java [new file with mode: 0644]
src/jalview/gui/Jalview2XML.java
src/jalview/gui/JalviewChimeraBindingModel.java [new file with mode: 0644]
src/jalview/gui/PopupMenu.java
src/jalview/gui/StructureViewer.java [new file with mode: 0644]
src/jalview/gui/UserDefinedColours.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/schemes/AnnotationColourGradient.java
src/jalview/structures/models/SequenceStructureBindingModel.java [new file with mode: 0644]
src/jalview/ws/jws2/JPred301Client.java
test/jalview/ext/jmol/PDBFileWithJmolTest.java
test/jalview/ext/rbvi/chimera/AllTests.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/ChimeraConnect.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/testProps.jvprops [new file with mode: 0644]
test/jalview/ws/jabaws/JpredJabaStructExportImport.java [new file with mode: 0644]

index dd90b49..a7270ff 100644 (file)
@@ -9,7 +9,6 @@
        <classpathentry kind="lib" path="lib/commons-discovery.jar"/>
        <classpathentry kind="lib" path="lib/jaxrpc.jar"/>
        <classpathentry kind="lib" path="lib/jhall.jar"/>
-       <classpathentry kind="lib" path="lib/log4j-1.2.8.jar"/>
        <classpathentry kind="lib" path="lib/mail.jar"/>
        <classpathentry kind="lib" path="lib/regex.jar"/>
        <classpathentry kind="lib" path="lib/saaj.jar"/>
@@ -48,6 +47,9 @@
        <classpathentry kind="lib" path="lib/min-jabaws-client-2.1.0.jar" sourcepath="/clustengine"/>
        <classpathentry kind="lib" path="lib/VARNAv3-9.jar" sourcepath="/Users/jimp/Documents/Jalview/VARNA/VARNAv3-9-src.jar"/>
        <classpathentry kind="lib" path="lib/json_simple-1.1.jar" sourcepath="/Users/jimp/Downloads/json_simple-1.1-all.zip"/>
+       <classpathentry kind="lib" path="lib/slf4j-api-1.7.7.jar"/>
+       <classpathentry kind="lib" path="lib/log4j-to-slf4j-2.0-rc2.jar"/>
+       <classpathentry kind="lib" path="lib/slf4j-log4j12-1.7.7.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
        <classpathentry kind="output" path="classes"/>
index 179e679..386541e 100644 (file)
@@ -2,6 +2,11 @@ The Jalview Desktop relies on a number of third-party libraries.
 These can be found in the lib directory, with additional compile
 time dependencies in the utils directory.
 
+A number of sources have also been adapted for incorporation into Jalview's source tree
+
+ext.edu.ucsf.rbvi.strucviz2 includes sources originally developed by Scooter Morris and Nadezhda Doncheva for the Cytoscape StructureViz2 plugin. It is released under the Berkley license and we hereby acknowledge its original copyright is held by the UCSF Computer Graphics Laboratory
+ and the software was developed with support by the NIH National Center for Research Resources, grant P41-RR01081. 
+
 Licencing information for each library is given below:
 
 JGoogleAnalytics_0.3.jar       APL 2.0 License - http://code.google.com/p/jgoogleanalytics/
@@ -34,6 +39,7 @@ wsdl4j.jar
 xercesImpl.jar
 xml-apis.jar
 json_simple-1.1.jar : Apache 2.0 license : downloaded from https://code.google.com/p/json-simple/downloads/list (will move to 1.1.1 version when jalview is mavenised and osgi-ised)
+
 Additional dependencies
 
 examples/javascript/deployJava.js : http://java.com/js/deployJava.js
diff --git a/lib/log4j-1.2.8.jar b/lib/log4j-1.2.8.jar
deleted file mode 100755 (executable)
index 05f8702..0000000
Binary files a/lib/log4j-1.2.8.jar and /dev/null differ
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimUtils.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimUtils.java
new file mode 100644 (file)
index 0000000..aedf657
--- /dev/null
@@ -0,0 +1,755 @@
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+
+
+public abstract class ChimUtils {
+
+       private static Logger logger = LoggerFactory
+                       .getLogger(ChimUtils.class);
+
+       static int MAX_SUB_MODELS = 1000;
+
+       public static final HashMap<String, String> aaNames;
+
+       public static String RESIDUE_ATTR = "ChimeraResidue";
+       public static String RINALYZER_ATTR = "RINalyzerResidue";
+       public static String DEFAULT_STRUCTURE_KEY = "pdbFileName";
+
+       /**
+        * Parse the model number returned by Chimera and return the int value
+        */
+       // invoked by the ChimeraModel constructor
+       // line = model id #0 type Molecule name 1ert
+       public static int[] parseModelNumber(String inputLine) {
+               int hash = inputLine.indexOf('#');
+               int space = inputLine.indexOf(' ', hash);
+               int decimal = inputLine.substring(hash + 1, space).indexOf('.');
+               // model number is between hash+1 and space
+               int modelNumber = -1;
+               int subModelNumber = 0;
+               try {
+                       if (decimal > 0) {
+                               subModelNumber = Integer.parseInt(inputLine.substring(decimal + hash + 2, space));
+                               space = decimal + hash + 1;
+                       }
+                       modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
+               } catch (Exception e) {
+                       logger.warn("Unexpected return from Chimera: " + inputLine, e);
+               }
+               return new int[] { modelNumber, subModelNumber };
+       }
+
+       /**
+        * Parse the model number returned by Chimera and return the int value
+        */
+       // invoked by openModel in ChimeraManager
+       // line: #1, chain A: hiv-1 protease
+       public static int[] parseOpenedModelNumber(String inputLine) {
+               int hash = inputLine.indexOf('#');
+               int space = inputLine.indexOf(',', hash);
+               int decimal = inputLine.substring(hash + 1, space).indexOf('.');
+               // model number is between hash+1 and space
+               int modelNumber = -1;
+               int subModelNumber = 0;
+               try {
+                       if (decimal > 0) {
+                               subModelNumber = Integer.parseInt(inputLine.substring(decimal + hash + 2, space));
+                               space = decimal + hash + 1;
+                       }
+                       modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
+               } catch (Exception e) {
+                       logger.warn("Unexpected return from Chimera: " + inputLine, e);
+               }
+               return new int[] { modelNumber, subModelNumber };
+       }
+
+       /**
+        * Parse the model identifier returned by Chimera and return the String value
+        */
+       // invoked by the ChimeraModel constructor
+       // line = model id #0 type Molecule name 1ert
+       public static String parseModelName(String inputLine) {
+               int start = inputLine.indexOf("name ");
+               if (start < 0)
+                       return null;
+               // Might get a quoted string (don't understand why, but there you have it)
+               if (inputLine.startsWith("\"", start + 5)) {
+                       start += 6; // Skip over the first quote
+                       int end = inputLine.lastIndexOf('"');
+                       if (end >= 1) {
+                               return inputLine.substring(start, end);
+                       } else
+                               return inputLine.substring(start);
+               } else {
+                       return inputLine.substring(start + 5);
+               }
+       }
+
+       public static Color parseModelColor(String inputLine) {
+               try {
+                       int colorStart = inputLine.indexOf("color ");
+                       String colorString = inputLine.substring(colorStart + 6);
+                       String[] rgbStrings = colorString.split(",");
+                       float[] rgbValues = new float[4];
+                       for (int i = 0; i < rgbStrings.length; i++) {
+                               Float f = new Float(rgbStrings[i]);
+                               rgbValues[i] = f.floatValue();
+                       }
+                       if (rgbStrings.length == 4) {
+                               return new Color(rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3]);
+                       } else {
+                               return new Color(rgbValues[0], rgbValues[1], rgbValues[2]);
+                       }
+               } catch (Exception ex) {
+                       logger.warn("Unexpected return from Chimera: " + inputLine, ex);
+               }
+               return Color.white;
+       }
+
+       /**
+        * Create the key to use for forming the model/submodel key into the modelHash
+        * 
+        * @param model
+        *            the model number
+        * @param subModel
+        *            the submodel number
+        * @return the model key as an Integer
+        */
+       public static Integer makeModelKey(int model, int subModel) {
+               return new Integer(model * MAX_SUB_MODELS + subModel);
+       }
+
+       // invoked by the getResdiue (parseConnectivityReplies in CreateStructureNetworkTask)
+       // atomSpec = #0:1.A or #1:96.B@N
+       public static ChimeraModel getModel(String atomSpec, ChimeraManager chimeraManager) {
+               // System.out.println("getting model for "+atomSpec);
+               String[] split = atomSpec.split(":");
+               // No model specified....
+               if (split[0].length() == 0) {
+                       logger.info("Unexpected return from Chimera: " + atomSpec);
+                       return null;
+               }
+               // System.out.println("model = "+split[0].substring(1));
+               int model = 0;
+               int submodel = 0;
+               try {
+                       String[] subSplit = split[0].substring(1).split("\\.");
+                       if (subSplit.length > 0)
+                               model = Integer.parseInt(subSplit[0]);
+                       else
+                               model = Integer.parseInt(split[0].substring(1));
+
+                       if (subSplit.length > 1)
+                               submodel = Integer.parseInt(subSplit[1]);
+               } catch (Exception e) {
+                       // ignore
+                       logger.warn("Unexpected return from Chimera: " + atomSpec, e);
+               }
+               return chimeraManager.getChimeraModel(model, submodel);
+       }
+
+       // invoked by the parseConnectivityReplies in CreateStructureNetworkTask
+       // atomSpec = #0:1.A or #1:96.B@N
+       public static ChimeraResidue getResidue(String atomSpec, ChimeraManager chimeraManager) {
+               // System.out.println("Getting residue from: "+atomSpec);
+               ChimeraModel model = getModel(atomSpec, chimeraManager); // Get the model
+               if (model == null) {
+                       model = chimeraManager.getChimeraModel();
+               }
+               return getResidue(atomSpec, model);
+       }
+
+       // invoked by the getResdiue (parseConnectivityReplies in CreateStructureNetworkTask)
+       // atomSpec = #0:1.A or #1:96.B@N
+       public static ChimeraResidue getResidue(String atomSpec, ChimeraModel model) {
+               // System.out.println("Getting residue from: "+atomSpec);
+               String[] split = atomSpec.split(":|@");
+
+               // Split into residue and chain
+               String[] residueChain = split[1].split("\\.");
+
+               if (residueChain[0].length() == 0) {
+                       logger.info("Unexpected return from Chimera: " + atomSpec);
+                       return null;
+               }
+
+               if (residueChain.length == 2 && residueChain[1].length() > 0) {
+                       ChimeraChain chain = model.getChain(residueChain[1]);
+                       return chain.getResidue(residueChain[0]);
+               }
+               return model.getResidue("_", residueChain[0]);
+       }
+
+       public static ChimeraChain getChain(String atomSpec, ChimeraModel model) {
+               String[] split = atomSpec.split(":|@");
+
+               // Split into residue and chain
+               String[] residueChain = split[1].split("\\.");
+               if (residueChain.length == 1) {
+                       logger.info("Unexpected return from Chimera: " + atomSpec);
+                       return null;
+               }
+               return model.getChain(residueChain[1]);
+       }
+
+       public static String getAtomName(String atomSpec) {
+               String[] split = atomSpec.split("@");
+               if (split.length > 1) {
+                       return split[1];
+               }
+               return atomSpec;
+       }
+
+       public static boolean isBackbone(String atom) {
+               if (atom.equals("C") || atom.equals("CA") || atom.equals("N") || atom.equals("O")
+                               || atom.equals("H"))
+                       return true;
+               return false;
+       }
+
+       public static String getIntSubtype(String node, String atom) {
+               String[] split = node.split("#| ");
+               String resType = "";
+               if (split.length == 2) {
+                       resType = split[0].trim().toUpperCase();
+               } else if (split.length == 3) {
+                       resType = split[1].trim().toUpperCase();
+               }
+               if (resType.equalsIgnoreCase("HOH") || resType.equalsIgnoreCase("WAT")) {
+                       return "water";
+               } else if (aaNames.containsKey(resType)) {
+                       if (atom.equals("C") || atom.equals("CA") || atom.equals("N") || atom.equals("O")
+                                       || atom.equals("H")) {
+                               return "mc";
+                       } else {
+                               return "sc";
+                       }
+               } else {
+                       return "other";
+               }
+       }
+
+
+       public static String[] getResKeyParts(String resKey) {
+               // [pdbID[.modelNo]#][residueID][.chainID]
+               // pdbID := 4-character code | "URL" | "path"
+               String[] resKeyParts = new String[4];
+               String[] split = resKey.split("#");
+               String resChain = null;
+               // if no "#" then it is either only a pdb id or a residue or a chain
+               if (split.length == 1) {
+                       // pdb id without model
+                       if (resKey.length() == 4 && resKey.indexOf("\\.") < 0) {
+                               parseModelID(resKey, resKeyParts);
+                       }
+                       // pdb link or file
+                       else if (resKey.startsWith("\"")) {
+                               parseModelID(resKey, resKeyParts);
+                       }
+                       // chain and residue or model and number
+                       else {
+                               String[] splitSplit = resKey.split("\\.");
+                               if (splitSplit.length == 1) {
+                                       // only a chain or a residue
+                                       resChain = resKey;
+                               } else {
+                                       try {
+                                               // pdb with a model
+                                               Integer.parseInt(splitSplit[1]);
+                                               parseModelID(resKey, resKeyParts);
+                                       } catch (NumberFormatException ex) {
+                                               // residue and chain
+                                               resChain = resKey;
+                                       }
+                               }
+                       }
+               } else if (split.length == 2) {
+                       // model and residue+chain
+                       parseModelID(split[0], resKeyParts);
+                       resChain = split[1];
+               } else {
+                       // model string with "#"
+                       // TODO: [Optional] Are there more possibilities?
+                       parseModelID(resKey.substring(0, resKey.lastIndexOf("#")), resKeyParts);
+                       resChain = resKey.substring(resKey.lastIndexOf("#") + 1, resKey.length());
+               }
+               if (resChain != null) {
+                       //System.out.println(resChain);
+                       String[] resChainSplit = resChain.split("\\.");
+                       if (resChainSplit.length == 1) {
+                               // TODO: [Optional] Find a better way to distinguish between chain and residue
+                               // if only one character and not an int, probably a chain
+                               if (resChainSplit[0].length() == 1) {
+                                       try {
+                                               Integer.parseInt(resChainSplit[0]);
+                                               resKeyParts[3] = resChainSplit[0];
+                                       } catch (NumberFormatException ex) {
+                                               resKeyParts[2] = resChainSplit[0];
+                                       }
+                               } else {
+                                       resKeyParts[3] = resChainSplit[0];
+                               }
+                       } else if (resChainSplit.length == 2) {
+                               resKeyParts[2] = resChainSplit[0];
+                               resKeyParts[3] = resChainSplit[1];
+                       } else {
+                               // too many dots?
+                               logger.info("Could not parse residue identifier: " + resKey);
+                       }
+               }
+               // String print = "";
+               // for (int i = 0; i < resKeyParts.length; i++) {
+               // if (resKeyParts[i] == null) {
+               // print += i + ": null\t";
+               // } else {
+               // print += i + ": " + resKeyParts[i] + ";";
+               // }
+               // }
+               // System.out.println(print);
+               return resKeyParts;
+       }
+
+       public static void parseModelID(String modelID, String[] resKeyParts) {
+               if (modelID.startsWith("\"")) {
+                       if (modelID.endsWith("\"")) {
+                               resKeyParts[0] = modelID.substring(1, modelID.length() - 1);
+                               return;
+                       } else {
+                               try {
+                                       Integer.parseInt(modelID.substring(modelID.lastIndexOf("\"") + 2,
+                                                       modelID.length()));
+                                       resKeyParts[0] = modelID.substring(0, modelID.lastIndexOf("\"") - 1);
+                                       resKeyParts[1] = modelID.substring(modelID.lastIndexOf("\"") + 2,
+                                                       modelID.length());
+                               } catch (NumberFormatException ex) {
+                                       resKeyParts[0] = modelID.substring(1);
+                               }
+                       }
+               } else {
+                       String[] modelIDNo = modelID.split("\\.");
+                       if (modelIDNo.length == 1) {
+                               resKeyParts[0] = modelIDNo[0];
+                       } else if (modelIDNo.length == 2) {
+                               try {
+                                       Integer.parseInt(modelIDNo[1]);
+                                       resKeyParts[0] = modelIDNo[0];
+                                       resKeyParts[1] = modelIDNo[1];
+                               } catch (NumberFormatException ex) {
+                                       resKeyParts[0] = modelID;
+                               }
+                       } else {
+                               logger.info("Could not parse model identifier: " + modelID);
+                       }
+               }
+       }
+
+       /**
+        * This method takes a Cytoscape attribute specification ([structure#][residue][.chainID]) and
+        * returns the lowest-level object referenced by the spec. For example, if the spec is "1tkk",
+        * this method will return a ChimeraModel. If the spec is ".A", it will return a ChimeraChain,
+        * etc.
+        * 
+        * @param attrSpec
+        *            the specification string
+        * @param chimeraManager
+        *            the Chimera object we're currently using
+        * @return a ChimeraStructuralObject of the lowest type
+        */
+       public static ChimeraStructuralObject fromAttributeOld(String attrSpec,
+                       ChimeraManager chimeraManager) {
+               if (attrSpec == null || attrSpec.indexOf(',') > 0 || attrSpec.indexOf('-') > 0) {
+                       // No support for either lists or ranges
+                       logger.warn("No support for identifier: " + attrSpec);
+                       return null;
+               }
+
+               String residue = null;
+               String model = null;
+               String chain = null;
+
+               ChimeraModel chimeraModel = null;
+               ChimeraChain chimeraChain = null;
+               ChimeraResidue chimeraResidue = null;
+
+               // System.out.println("Getting object from attribute: "+attrSpec);
+               try {
+                       String[] split = attrSpec.split("#");
+                       String resChain = null;
+                       if (split.length == 1) {
+                               // no model
+                               resChain = split[0];
+                       } else if (split.length == 2) {
+                               // model and rest
+                               model = split[0];
+                               resChain = split[1];
+                       } else {
+                               // model string with "#"
+                               model = attrSpec.substring(0, attrSpec.lastIndexOf("#"));
+                               resChain = attrSpec.substring(attrSpec.lastIndexOf("#") + 1, attrSpec.length());
+                       }
+                       if (resChain != null) {
+                               String[] resChainSplit = resChain.split("\\.");
+                               if (resChainSplit.length == 1) {
+                                       residue = resChainSplit[0];
+                               } else if (resChainSplit.length == 2) {
+                                       residue = resChainSplit[0];
+                                       chain = resChainSplit[1];
+                               } else {
+                                       // too many dots?
+                                       logger.warn("No support for identifier: " + attrSpec);
+                               }
+                       }
+
+                       // if (split.length == 1) {
+                       // // No model
+                       // residue = split[0];
+                       // } else if (split.length == 3) {
+                       // // We have all three
+                       // model = split[0];
+                       // residue = split[1];
+                       // chain = split[2];
+                       // } else if (split.length == 2 && attrSpec.indexOf('#') > 0) {
+                       // // Model and Residue
+                       // model = split[0];
+                       // residue = split[1];
+                       // } else {
+                       // // Residue and Chain
+                       // residue = split[0];
+                       // chain = split[1];
+                       // }
+
+                       // System.out.println("model = " + model + " chain = " + chain + " residue = " +
+                       // residue);
+                       if (model != null) {
+                               List<ChimeraModel> models = chimeraManager.getChimeraModels(model,
+                                               ModelType.PDB_MODEL);
+                               if (models.size() == 1) {
+                                       chimeraModel = models.get(0);
+                               } else {
+                                       try {
+                                               chimeraModel = chimeraManager.getChimeraModel(Integer.valueOf(model), 0);
+                                       } catch (NumberFormatException ex) {
+                                               // ignore
+                                       }
+                               }
+                       }
+                       if (chimeraModel == null) {
+                               chimeraModel = chimeraManager.getChimeraModel();
+                       }
+                       // System.out.println("ChimeraModel = " + chimeraModel);
+
+                       if (chain != null) {
+                               chimeraChain = chimeraModel.getChain(chain);
+                               // System.out.println("ChimeraChain = " + chimeraChain);
+                       }
+                       if (residue != null) {
+                               if (chimeraChain != null) {
+                                       chimeraResidue = chimeraChain.getResidue(residue);
+                               } else {
+                                       chimeraResidue = chimeraModel.getResidue("_", residue);
+                               }
+                               // System.out.println("ChimeraResidue = " + chimeraResidue);
+                       }
+
+                       if (chimeraResidue != null)
+                               return chimeraResidue;
+
+                       if (chimeraChain != null)
+                               return chimeraChain;
+
+                       if (chimeraModel != null)
+                               return chimeraModel;
+
+               } catch (Exception ex) {
+                       logger.warn("Could not parse residue identifier: " + attrSpec, ex);
+               }
+               return null;
+       }
+
+       public static ChimeraStructuralObject fromAttribute(String attrSpec,
+                       ChimeraManager chimeraManager) {
+               // TODO: Make sure it is OK to remove this: || attrSpec.indexOf('-') > 0
+               if (attrSpec == null || attrSpec.indexOf(',') > 0) {
+                       // No support for either lists or ranges
+                       // System.out.println("No support for identifier: " + attrSpec);
+                       logger.warn("No support for identifier: " + attrSpec);
+                       return null;
+               }
+               String[] modelIDNoResChain = getResKeyParts(attrSpec);
+
+               ChimeraModel chimeraModel = null;
+               ChimeraChain chimeraChain = null;
+               ChimeraResidue chimeraResidue = null;
+
+               // System.out.println("Getting object from attribute: "+attrSpec);
+               try {
+                       if (modelIDNoResChain[0] != null) {
+                               String modelID = modelIDNoResChain[0];
+                               List<ChimeraModel> models = chimeraManager.getChimeraModels(modelID,
+                                               ModelType.PDB_MODEL);
+                               if (models.size() == 1) { // usual case with only one model
+                                       chimeraModel = models.get(0);
+                               } else if (models.size() > 1 && modelIDNoResChain[1] != null) {
+                                       // there are several submodels
+                                       try {
+                                               int modelNo = Integer.valueOf(modelIDNoResChain[1]);
+                                               for (ChimeraModel model : models) {
+                                                       if (model.getSubModelNumber() == modelNo) {
+                                                               chimeraModel = model;
+                                                               break;
+                                                       }
+                                               }
+                                       } catch (NumberFormatException ex) {
+                                               // ignore
+                                       }
+                               } else {
+                                       // TODO: [Optional] What is this doing?
+                                       try {
+                                               chimeraModel = chimeraManager.getChimeraModel(Integer.valueOf(modelID), 0);
+                                       } catch (NumberFormatException ex) {
+                                               // ignore
+                                       }
+                               }
+                       }
+                       if (chimeraModel == null) {
+                               // TODO: [Optional] Find a better way to handle this case
+                               // If no model can be matched, continue
+                               // System.out.println("No matching model could be find for " + attrSpec);
+                               return null;
+                               // chimeraModel = chimeraManager.getChimeraModel();
+                               // logger.warn("No matching model could be find for " + attrSpec + ". Trying with "
+                               // + chimeraModel.toSpec());
+                       }
+                       // System.out.println("ChimeraModel = " + chimeraModel);
+
+                       if (modelIDNoResChain[3] != null) {
+                               chimeraChain = chimeraModel.getChain(modelIDNoResChain[3]);
+                               // System.out.println("ChimeraChain = " + chimeraChain);
+                       }
+                       if (modelIDNoResChain[2] != null) {
+                               String residue = modelIDNoResChain[2];
+                               if (chimeraChain != null) {
+                                       chimeraResidue = chimeraChain.getResidue(residue);
+                               } else if (chimeraModel.getChain("_") != null) {
+                                       chimeraResidue = chimeraModel.getResidue("_", residue);
+                               } else if (chimeraModel.getChainCount() == 1) {
+                                       chimeraResidue = chimeraModel.getResidue(chimeraModel.getChainNames()
+                                                       .iterator().next(), residue);
+                               }
+                               // System.out.println("ChimeraResidue = " + chimeraResidue);
+                       }
+
+                       if (chimeraResidue != null)
+                               return chimeraResidue;
+
+                       if (chimeraChain != null)
+                               return chimeraChain;
+
+                       if (chimeraModel != null)
+                               return chimeraModel;
+
+               } catch (Exception ex) {
+                       // System.out.println("Could not parse chimera identifier: " +
+                       // attrSpec+"("+ex.getMessage()+")");
+                       logger.warn("Could not parse chimera identifier: " + attrSpec, ex);
+               }
+               return null;
+       }
+
+       /**
+        * Search for structure references in the residue list
+        * 
+        * @param residueList
+        *            the list of residues
+        * @return a concatenated list of structures encoded in the list
+        */
+       public static String findStructures(String residueList) {
+               if (residueList == null)
+                       return null;
+               String[] residues = residueList.split(",");
+               Map<String, String> structureNameMap = new HashMap<String, String>();
+               for (int i = 0; i < residues.length; i++) {
+                       String[] components = residues[i].split("#");
+                       if (components.length > 1) {
+                               structureNameMap.put(components[0], components[1]);
+                       }
+               }
+               if (structureNameMap.isEmpty())
+                       return null;
+
+               String structure = null;
+               for (String struct : structureNameMap.keySet()) {
+                       if (structure == null)
+                               structure = new String();
+                       else
+                               structure = structure.concat(",");
+                       structure = structure.concat(struct);
+               }
+               return structure;
+       }
+
+       // invoked by openStructures in StructureManager
+       public static List<String> parseFuncRes(List<String> residueNames, String modelName) {
+               List<String> resRanges = new ArrayList<String>();
+               for (int i = 0; i < residueNames.size(); i++) {
+                       String residue = residueNames.get(i);
+                       // Parse out the structure, if there is one
+                       String[] components = residue.split("#");
+                       if (components.length > 1 && !modelName.equals(components[0])) {
+                               continue;
+                       } else if (components.length > 1) {
+                               residue = components[1];
+                       } else if (components.length == 1) {
+                               residue = components[0];
+                       }
+                       // Check to see if we have a range-spec
+                       String resRange = "";
+                       if (residue == null || residue.equals("") || residue.length() == 0) {
+                               continue;
+                       }
+                       String[] range = residue.split("-", 2);
+                       String chain = null;
+                       for (int res = 0; res < range.length; res++) {
+                               if (res == 1) {
+                                       resRange = resRange.concat("-");
+                                       if (chain != null && range[res].indexOf('.') == -1)
+                                               range[res] = range[res].concat("." + chain);
+                               }
+
+                               if (res == 0 && range.length >= 2 && range[res].indexOf('.') > 0) {
+                                       // This is a range spec with the leading residue containing a chain spec
+                                       String[] resChain = range[res].split("\\.");
+                                       chain = resChain[1];
+                                       range[res] = resChain[0];
+                               }
+                               // Fix weird SFLD syntax...
+                               if (range[res].indexOf('|') > 0 && Character.isDigit(range[res].charAt(0))) {
+                                       int offset = range[res].indexOf('|');
+                                       String str = range[res].substring(offset + 1) + range[res].substring(0, offset);
+                                       range[res] = str;
+                               }
+
+                               // Convert to legal atom-spec
+                               if (Character.isDigit(range[res].charAt(0))) {
+                                       resRange = resRange.concat(range[res]);
+                               } else if (Character.isDigit(range[res].charAt(1))) {
+                                       resRange = resRange.concat(range[res].substring(1));
+                               } else if (range[res].charAt(0) == '.') {
+                                       // Do we have a chain spec?
+                                       resRange = resRange.concat(range[res]);
+                               } else {
+                                       resRange = resRange.concat(range[res].substring(3));
+                               }
+                       }
+                       if (!resRanges.contains(resRange)) {
+                               resRanges.add(resRange);
+                       }
+               }
+               return resRanges;
+       }
+
+       static {
+               aaNames = new HashMap<String, String>();
+               aaNames.put("ALA", "A Ala Alanine N[C@@H](C)C(O)=O");
+               aaNames.put("ARG", "R Arg Arginine N[C@@H](CCCNC(N)=N)C(O)=O");
+               aaNames.put("ASN", "N Asn Asparagine N[C@@H](CC(N)=O)C(O)=O");
+               aaNames.put("ASP", "D Asp Aspartic_acid N[C@@H](CC(O)=O)C(O)=O");
+               aaNames.put("CYS", "C Cys Cysteine N[C@@H](CS)C(O)=O");
+               aaNames.put("GLN", "Q Gln Glutamine N[C@H](C(O)=O)CCC(N)=O");
+               aaNames.put("GLU", "E Glu Glumatic_acid N[C@H](C(O)=O)CCC(O)=O");
+               aaNames.put("GLY", "G Gly Glycine NCC(O)=O");
+               aaNames.put("HIS", "H His Histidine N[C@@H](CC1=CN=CN1)C(O)=O");
+               aaNames.put("ILE", "I Ile Isoleucine N[C@]([C@H](C)CC)([H])C(O)=O");
+               aaNames.put("LEU", "L Leu Leucine N[C@](CC(C)C)([H])C(O)=O");
+               aaNames.put("LYS", "K Lys Lysine N[C@](CCCCN)([H])C(O)=O");
+               aaNames.put("DLY", "K Dly D-Lysine NCCCC[C@@H](N)C(O)=O");
+               aaNames.put("MET", "M Met Methionine N[C@](CCSC)([H])C(O)=O");
+               aaNames.put("PHE", "F Phe Phenylalanine N[C@](CC1=CC=CC=C1)([H])C(O)=O");
+               aaNames.put("PRO", "P Pro Proline OC([C@@]1([H])NCCC1)=O");
+               aaNames.put("SER", "S Ser Serine OC[C@](C(O)=O)([H])N");
+               aaNames.put("THR", "T Thr Threonine O[C@H](C)[C@](C(O)=O)([H])N");
+               aaNames.put("TRP", "W Trp Tryptophan N[C@@]([H])(CC1=CN([H])C2=C1C=CC=C2)C(O)=O");
+               aaNames.put("TYR", "Y Tyr Tyrosine N[C@@](C(O)=O)([H])CC1=CC=C(O)C=C1");
+               aaNames.put("VAL", "V Val Valine N[C@@](C(O)=O)([H])C(C)C");
+               aaNames.put("ASX", "B Asx Aspartic_acid_or_Asparagine");
+               aaNames.put("GLX", "Z Glx Glutamine_or_Glutamic_acid");
+               aaNames.put("XAA", "X Xaa Any_or_unknown_amino_acid");
+               aaNames.put("HOH", "HOH HOH Water [H]O[H]");
+       }
+
+       /**
+        * Convert the amino acid type to a full name
+        * 
+        * @param aaType
+        *            the residue type to convert
+        * @return the full name of the residue
+        */
+       public static String toFullName(String aaType) {
+               if (!aaNames.containsKey(aaType))
+                       return aaType;
+               String[] ids = ((String) aaNames.get(aaType)).split(" ");
+               return ids[2].replace('_', ' ');
+       }
+
+       /**
+        * Convert the amino acid type to a single letter
+        * 
+        * @param aaType
+        *            the residue type to convert
+        * @return the single letter representation of the residue
+        */
+       public static String toSingleLetter(String aaType) {
+               if (!aaNames.containsKey(aaType))
+                       return aaType;
+               String[] ids = ((String) aaNames.get(aaType)).split(" ");
+               return ids[0];
+       }
+
+       /**
+        * Convert the amino acid type to three letters
+        * 
+        * @param aaType
+        *            the residue type to convert
+        * @return the three letter representation of the residue
+        */
+       public static String toThreeLetter(String aaType) {
+               if (!aaNames.containsKey(aaType))
+                       return aaType;
+               String[] ids = ((String) aaNames.get(aaType)).split(" ");
+               return ids[1];
+       }
+
+       /**
+        * Convert the amino acid type to its SMILES string
+        * 
+        * @param aaType
+        *            the residue type to convert
+        * @return the SMILES representation of the residue
+        */
+       public static String toSMILES(String aaType) {
+               if (!aaNames.containsKey(aaType))
+                       return null;
+               String[] ids = ((String) aaNames.get(aaType)).split(" ");
+               if (ids.length < 4)
+                       return null;
+               return ids[3];
+       }
+
+       public static String getAlignName(ChimeraStructuralObject chimObj) {
+               String name = chimObj.getChimeraModel().toString();
+               if (chimObj instanceof ChimeraChain) {
+                       name = ((ChimeraChain) chimObj).toString() + " [" + name + "]";
+               }
+               return name;
+       }
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraChain.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraChain.java
new file mode 100644 (file)
index 0000000..92193f2
--- /dev/null
@@ -0,0 +1,330 @@
+/* vim: set ts=2: */
+/**
+ * Copyright (c) 2006 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions, and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions, and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *   3. Redistributions must acknowledge that this software was
+ *      originally developed by the UCSF Computer Graphics Laboratory
+ *      under support by the NIH National Center for Research Resources,
+ *      grant P41-RR01081.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ * This class provides the implementation for the ChimeraChain object
+ * 
+ * @author scooter
+ * 
+ */
+// TODO: [Optional] Implement toAttr() method
+public class ChimeraChain implements ChimeraStructuralObject {
+
+       /**
+        * The model/subModel number this chain is a part of
+        */
+       private int modelNumber;
+       private int subModelNumber;
+
+       /**
+        * A pointer to the model this chain is a part of
+        */
+       private ChimeraModel chimeraModel;
+
+       /**
+        * The chainID (from the PDB record)
+        */
+       private String chainId;
+
+       /**
+        * The residues that are part of this chain
+        */
+       private TreeMap<String, ChimeraResidue> residueMap;
+
+       /**
+        * userData to associate with this chain
+        */
+       private Object userData;
+
+       /**
+        * Flag to indicate the selection state
+        */
+       private boolean selected = false;
+
+       /**
+        * Constructor to create a new ChimeraChain
+        * 
+        * @param model
+        *          the model number this chain is part of
+        * @param subModel
+        *          the subModel number this chain is part of
+        * @param chainId
+        *          the chain ID for this chain
+        */
+       public ChimeraChain(int model, int subModel, String chainId) {
+               this.modelNumber = model;
+               this.subModelNumber = subModel;
+               this.chainId = chainId;
+               residueMap = new TreeMap<String, ChimeraResidue>();
+       }
+
+       /**
+        * set the selected state of this chain
+        * 
+        * @param selected
+        *          a boolean to set the selected state to
+        */
+       public void setSelected(boolean selected) {
+               this.selected = selected;
+       }
+
+       /**
+        * return the selected state of this chain
+        * 
+        * @return the selected state
+        */
+       public boolean isSelected() {
+               return selected;
+       }
+
+       public boolean hasSelectedChildren() {
+               if (selected) {
+                       return true;
+               } else {
+                       for (ChimeraResidue residue : getResidues()) {
+                               if (residue.isSelected())
+                                       return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Return the list of selected residues
+        * 
+        * @return all selected residues
+        */
+       public List<ChimeraResidue> getSelectedResidues() {
+               List<ChimeraResidue> residueList = new ArrayList<ChimeraResidue>();
+               if (selected) {
+                       residueList.addAll(getResidues());
+               } else {
+                       for (ChimeraResidue residue : getResidues()) {
+                               if (residue.isSelected())
+                                       residueList.add(residue);
+                       }
+               }
+               return residueList;
+       }
+
+       /**
+        * Add a residue to the chain.
+        * 
+        * @param residue
+        *          the ChimeraResidue to add to the chain.
+        */
+       public void addResidue(ChimeraResidue residue) {
+               String index = residue.getIndex();
+               // Put it in our map so that we can return it in order
+               residueMap.put(index, residue);
+       }
+
+       /**
+        * Return the list of residues in this chain in pdb residue order
+        * 
+        * @return a Collection of residues in residue order
+        */
+       public Collection<ChimeraResidue> getResidues() {
+               return residueMap.values();
+       }
+
+       /**
+        * Return the list of residues in this chain as a list
+        * 
+        * @return List of residues
+        */
+       public List<ChimeraStructuralObject> getChildren() {
+               return new ArrayList<ChimeraStructuralObject>(residueMap.values());
+       }
+
+       /**
+        * Get a specific residue
+        * 
+        * @param residueIndex
+        *          String representation of the residue index
+        * @return the ChimeraResidue represented by the residueIndex
+        */
+       public ChimeraResidue getResidue(String index) {
+               // Integer index = new Integer(residueIndex);
+               if (residueMap.containsKey(index))
+                       return residueMap.get(index);
+               return null;
+       }
+
+       /**
+        * Get a list of residues as a residue range
+        * 
+        * @param residueRange
+        *          String representation of the residue range
+        * @return the List of ChimeraResidues represented by the range
+        */
+       public List<ChimeraResidue> getResidueRange(String residueRange) {
+               String[] range = residueRange.split("-", 2);
+               if (range[1] == null || range[1].length() == 0) {
+                       range[1] = range[0];
+               }
+               List<ChimeraResidue> resultRange = new ArrayList<ChimeraResidue>();
+               int start = Integer.parseInt(range[0]);
+               int end = Integer.parseInt(range[1]);
+               for (int i = start; i <= end; i++) {
+                       String index = String.valueOf(i);
+                       if (residueMap.containsKey(index))
+                               resultRange.add(residueMap.get(index));
+               }
+               return resultRange;
+       }
+
+       /**
+        * Get the ID for this chain
+        * 
+        * @return String value of the chainId
+        */
+       public String getChainId() {
+               return chainId;
+       }
+
+       /**
+        * Get the model number for this chain
+        * 
+        * @return the model number
+        */
+       public int getModelNumber() {
+               return modelNumber;
+       }
+
+       /**
+        * Get the sub-model number for this chain
+        * 
+        * @return the sub-model number
+        */
+       public int getSubModelNumber() {
+               return subModelNumber;
+       }
+
+       /**
+        * Return a string representation of this chain as follows: Chain <i>chainId</i>
+        * (<i>residue_count</i> residues)
+        * 
+        * @return String representation of chain
+        */
+       public String displayName() {
+               if (chainId.equals("_")) {
+                       return ("Chain (no ID) (" + getResidueCount() + " residues)");
+               } else {
+                       return ("Chain " + chainId + " (" + getResidueCount() + " residues)");
+               }
+       }
+
+       /**
+        * Return a string representation of this chain as follows: Node xxx [Model yyyy Chain
+        * <i>chainId</i>]
+        * 
+        * @return String representation of chain
+        */
+       public String toString() {
+               String displayName = chimeraModel.getModelName();
+               if (displayName.length() > 14)
+                       displayName = displayName.substring(0, 13) + "...";
+               if (chainId.equals("_")) {
+                       return (displayName + " Chain (no ID) (" + getResidueCount() + " residues)");
+               } else {
+                       return (displayName + " Chain " + chainId + " (" + getResidueCount() + " residues)");
+               }
+       }
+
+       /**
+        * Return the Chimera specification for this chain
+        * 
+        * @return Chimera specification
+        */
+       public String toSpec() {
+               if (chainId.equals("_")) {
+                       return ("#" + modelNumber + "." + subModelNumber + ":.");
+               } else {
+                       return ("#" + modelNumber + "." + subModelNumber + ":." + chainId);
+               }
+       }
+
+       /**
+        * Return the number of residues in this chain
+        * 
+        * @return integer number of residues
+        */
+       public int getResidueCount() {
+               return residueMap.size();
+       }
+
+       /**
+        * Set the ChimeraModel for this chain
+        * 
+        * @param model
+        *          ChimeraModel to associate with this chain
+        */
+       public void setChimeraModel(ChimeraModel model) {
+               this.chimeraModel = model;
+       }
+
+       /**
+        * Get the ChimeraModel for this chain
+        * 
+        * @return ChimeraModel associated with this chain
+        */
+       public ChimeraModel getChimeraModel() {
+               return chimeraModel;
+       }
+
+       /**
+        * Get the user data for this Chain
+        * 
+        * @return user data
+        */
+       public Object getUserData() {
+               return userData;
+       }
+
+       /**
+        * Set the user data for this Chain
+        * 
+        * @param data
+        *          the user data to associate with this chain
+        */
+       public void setUserData(Object data) {
+               this.userData = data;
+       }
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
new file mode 100644 (file)
index 0000000..c0b3a39
--- /dev/null
@@ -0,0 +1,699 @@
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.awt.Color;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
+
+/**
+ * This object maintains the Chimera communication information.
+ */
+public class ChimeraManager
+{
+
+  private Process chimera;
+
+  private ListenerThreads chimeraListenerThreads;
+
+  private Map<Integer, ChimeraModel> currentModelsMap;
+
+  private Logger logger = LoggerFactory
+          .getLogger(ext.edu.ucsf.rbvi.strucviz2.ChimeraManager.class);
+
+  private StructureManager structureManager;
+
+  public ChimeraManager(StructureManager structureManager)
+  {
+    this.structureManager = structureManager;
+    chimera = null;
+    chimeraListenerThreads = null;
+    currentModelsMap = new HashMap<Integer, ChimeraModel>();
+
+  }
+
+  public List<ChimeraModel> getChimeraModels(String modelName)
+  {
+    List<ChimeraModel> models = getChimeraModels(modelName,
+            ModelType.PDB_MODEL);
+    models.addAll(getChimeraModels(modelName, ModelType.SMILES));
+    return models;
+  }
+
+  public List<ChimeraModel> getChimeraModels(String modelName,
+          ModelType modelType)
+  {
+    List<ChimeraModel> models = new ArrayList<ChimeraModel>();
+    for (ChimeraModel model : currentModelsMap.values())
+    {
+      if (modelName.equals(model.getModelName())
+              && modelType.equals(model.getModelType()))
+      {
+        models.add(model);
+      }
+    }
+    return models;
+  }
+
+  public Map<String, List<ChimeraModel>> getChimeraModelsMap()
+  {
+    Map<String, List<ChimeraModel>> models = new HashMap<String, List<ChimeraModel>>();
+    for (ChimeraModel model : currentModelsMap.values())
+    {
+      String modelName = model.getModelName();
+      if (!models.containsKey(modelName))
+      {
+        models.put(modelName, new ArrayList<ChimeraModel>());
+      }
+      if (!models.get(modelName).contains(model))
+      {
+        models.get(modelName).add(model);
+      }
+    }
+    return models;
+  }
+
+  public ChimeraModel getChimeraModel(Integer modelNumber,
+          Integer subModelNumber)
+  {
+    Integer key = ChimUtils.makeModelKey(modelNumber, subModelNumber);
+    if (currentModelsMap.containsKey(key))
+    {
+      return currentModelsMap.get(key);
+    }
+    return null;
+  }
+
+  public ChimeraModel getChimeraModel()
+  {
+    return currentModelsMap.values().iterator().next();
+  }
+
+  public Collection<ChimeraModel> getChimeraModels()
+  {
+    // this method is invoked by the model navigator dialog
+    return currentModelsMap.values();
+  }
+
+  public int getChimeraModelsCount(boolean smiles)
+  {
+    // this method is invokes by the model navigator dialog
+    int counter = currentModelsMap.size();
+    if (smiles)
+    {
+      return counter;
+    }
+
+    for (ChimeraModel model : currentModelsMap.values())
+    {
+      if (model.getModelType() == ModelType.SMILES)
+      {
+        counter--;
+      }
+    }
+    return counter;
+  }
+
+  public boolean hasChimeraModel(Integer modelNubmer)
+  {
+    return hasChimeraModel(modelNubmer, 0);
+  }
+
+  public boolean hasChimeraModel(Integer modelNubmer, Integer subModelNumber)
+  {
+    return currentModelsMap.containsKey(ChimUtils.makeModelKey(modelNubmer,
+            subModelNumber));
+  }
+
+  public void addChimeraModel(Integer modelNumber, Integer subModelNumber,
+          ChimeraModel model)
+  {
+    currentModelsMap.put(
+            ChimUtils.makeModelKey(modelNumber, subModelNumber), model);
+  }
+
+  public void removeChimeraModel(Integer modelNumber, Integer subModelNumber)
+  {
+    int modelKey = ChimUtils.makeModelKey(modelNumber, subModelNumber);
+    if (currentModelsMap.containsKey(modelKey))
+    {
+      currentModelsMap.remove(modelKey);
+    }
+  }
+
+  public List<ChimeraModel> openModel(String modelPath, ModelType type)
+  {
+    logger.info("chimera open " + modelPath);
+    stopListening();
+    List<String> response = null;
+    // TODO: [Optional] Handle modbase models
+    if (type == ModelType.MODBASE_MODEL)
+    {
+      response = sendChimeraCommand("open modbase:" + modelPath, true);
+      // } else if (type == ModelType.SMILES) {
+      // response = sendChimeraCommand("open smiles:" + modelName, true);
+      // modelName = "smiles:" + modelName;
+    }
+    else
+    {
+      response = sendChimeraCommand("open " + modelPath, true);
+    }
+    if (response == null)
+    {
+      // something went wrong
+      logger.warn("Could not open " + modelPath);
+      return null;
+    }
+    List<ChimeraModel> models = new ArrayList<ChimeraModel>();
+    int[] modelNumbers = null;
+    if (type == ModelType.PDB_MODEL)
+    {
+      for (String line : response)
+      {
+        if (line.startsWith("#"))
+        {
+          modelNumbers = ChimUtils.parseOpenedModelNumber(line);
+          if (modelNumbers != null)
+          {
+            int modelNumber = ChimUtils.makeModelKey(modelNumbers[0],
+                    modelNumbers[1]);
+            if (currentModelsMap.containsKey(modelNumber))
+            {
+              continue;
+            }
+            String modelName = modelPath;
+            // TODO: [Optional] Convert path to name in a better way
+            if (modelPath.lastIndexOf(File.separator) > 0)
+            {
+              modelName = modelPath.substring(modelPath
+                      .lastIndexOf(File.separator) + 1);
+            }
+            else if (modelPath.lastIndexOf("/") > 0)
+            {
+              modelName = modelPath
+                      .substring(modelPath.lastIndexOf("/") + 1);
+            }
+            ChimeraModel newModel = new ChimeraModel(modelName, type,
+                    modelNumbers[0], modelNumbers[1]);
+            currentModelsMap.put(modelNumber, newModel);
+            models.add(newModel);
+            modelNumbers = null;
+          }
+        }
+      }
+    }
+    else
+    {
+      // TODO: [Optional] Open smiles from file would fail. Do we need it?
+      // If parsing fails, iterate over all open models to get the right one
+      List<ChimeraModel> openModels = getModelList();
+      for (ChimeraModel openModel : openModels)
+      {
+        String openModelName = openModel.getModelName();
+        if (openModelName.endsWith("..."))
+        {
+          openModelName = openModelName.substring(0,
+                  openModelName.length() - 3);
+        }
+        if (modelPath.startsWith(openModelName))
+        {
+          openModel.setModelName(modelPath);
+          int modelNumber = ChimUtils
+                  .makeModelKey(openModel.getModelNumber(),
+                          openModel.getSubModelNumber());
+          if (!currentModelsMap.containsKey(modelNumber))
+          {
+            currentModelsMap.put(modelNumber, openModel);
+            models.add(openModel);
+          }
+        }
+      }
+    }
+
+    // assign color and residues to open models
+    for (ChimeraModel newModel : models)
+    {
+      // get model color
+      Color modelColor = getModelColor(newModel);
+      if (modelColor != null)
+      {
+        newModel.setModelColor(modelColor);
+      }
+
+      // Get our properties (default color scheme, etc.)
+      // Make the molecule look decent
+      // chimeraSend("repr stick "+newModel.toSpec());
+
+      // Create the information we need for the navigator
+      if (type != ModelType.SMILES)
+      {
+        addResidues(newModel);
+      }
+    }
+
+    sendChimeraCommand("focus", false);
+    startListening();
+    return models;
+  }
+
+  public void closeModel(ChimeraModel model)
+  {
+    // int model = structure.modelNumber();
+    // int subModel = structure.subModelNumber();
+    // Integer modelKey = makeModelKey(model, subModel);
+    stopListening();
+    logger.info("chimera close model " + model.getModelName());
+    if (currentModelsMap.containsKey(ChimUtils.makeModelKey(
+            model.getModelNumber(), model.getSubModelNumber())))
+    {
+      sendChimeraCommand("close " + model.toSpec(), false);
+      // currentModelNamesMap.remove(model.getModelName());
+      currentModelsMap.remove(ChimUtils.makeModelKey(
+              model.getModelNumber(), model.getSubModelNumber()));
+      // selectionList.remove(chimeraModel);
+    }
+    else
+    {
+      logger.warn("Could not find model " + model.getModelName()
+              + " to close.");
+    }
+    startListening();
+  }
+
+  public void startListening()
+  {
+    sendChimeraCommand("listen start models; listen start select", false);
+  }
+
+  public void stopListening()
+  {
+    sendChimeraCommand("listen stop models; listen stop select", false);
+  }
+
+  /**
+   * Select something in Chimera
+   * 
+   * @param command
+   *          the selection command to pass to Chimera
+   */
+  public void select(String command)
+  {
+    sendChimeraCommand("listen stop select; " + command
+            + "; listen start select", false);
+  }
+
+  public void focus()
+  {
+    sendChimeraCommand("focus", false);
+  }
+
+  public void clearOnChimeraExit()
+  {
+    chimera = null;
+    currentModelsMap.clear();
+    chimeraListenerThreads = null;
+    structureManager.clearOnChimeraExit();
+  }
+
+  public void exitChimera()
+  {
+    if (isChimeraLaunched() && chimera != null)
+    {
+      sendChimeraCommand("stop really", false);
+      try
+      {
+        chimera.destroy();
+      } catch (Exception ex)
+      {
+        // ignore
+      }
+    }
+    clearOnChimeraExit();
+  }
+
+  public Map<Integer, ChimeraModel> getSelectedModels()
+  {
+    Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<Integer, ChimeraModel>();
+    List<String> chimeraReply = sendChimeraCommand(
+            "list selection level molecule", true);
+    if (chimeraReply != null)
+    {
+      for (String modelLine : chimeraReply)
+      {
+        ChimeraModel chimeraModel = new ChimeraModel(modelLine);
+        Integer modelKey = ChimUtils.makeModelKey(
+                chimeraModel.getModelNumber(),
+                chimeraModel.getSubModelNumber());
+        selectedModelsMap.put(modelKey, chimeraModel);
+      }
+    }
+    return selectedModelsMap;
+  }
+
+  public List<String> getSelectedResidueSpecs()
+  {
+    List<String> selectedResidues = new ArrayList<String>();
+    List<String> chimeraReply = sendChimeraCommand(
+            "list selection level residue", true);
+    if (chimeraReply != null)
+    {
+      for (String inputLine : chimeraReply)
+      {
+        String[] inputLineParts = inputLine.split("\\s+");
+        if (inputLineParts.length == 5)
+        {
+          selectedResidues.add(inputLineParts[2]);
+        }
+      }
+    }
+    return selectedResidues;
+  }
+
+  public void getSelectedResidues(
+          Map<Integer, ChimeraModel> selectedModelsMap)
+  {
+    List<String> chimeraReply = sendChimeraCommand(
+            "list selection level residue", true);
+    if (chimeraReply != null)
+    {
+      for (String inputLine : chimeraReply)
+      {
+        ChimeraResidue r = new ChimeraResidue(inputLine);
+        Integer modelKey = ChimUtils.makeModelKey(r.getModelNumber(),
+                r.getSubModelNumber());
+        if (selectedModelsMap.containsKey(modelKey))
+        {
+          ChimeraModel model = selectedModelsMap.get(modelKey);
+          model.addResidue(r);
+        }
+      }
+    }
+  }
+
+  /**
+   * Return the list of ChimeraModels currently open. Warning: if smiles model
+   * name too long, only part of it with "..." is printed.
+   * 
+   * 
+   * @return List of ChimeraModel's
+   */
+  // TODO: [Optional] Handle smiles names in a better way in Chimera?
+  public List<ChimeraModel> getModelList()
+  {
+    List<ChimeraModel> modelList = new ArrayList<ChimeraModel>();
+    List<String> list = sendChimeraCommand("list models type molecule",
+            true);
+    if (list != null)
+    {
+      for (String modelLine : list)
+      {
+        ChimeraModel chimeraModel = new ChimeraModel(modelLine);
+        modelList.add(chimeraModel);
+      }
+    }
+    return modelList;
+  }
+
+  /**
+   * Return the list of depiction presets available from within Chimera. Chimera
+   * will return the list as a series of lines with the format: Preset type
+   * number "description"
+   * 
+   * @return list of presets
+   */
+  public List<String> getPresets()
+  {
+    ArrayList<String> presetList = new ArrayList<String>();
+    List<String> output = sendChimeraCommand("preset list", true);
+    if (output != null)
+    {
+      for (String preset : output)
+      {
+        preset = preset.substring(7); // Skip over the "Preset"
+        preset = preset.replaceFirst("\"", "(");
+        preset = preset.replaceFirst("\"", ")");
+        // string now looks like: type number (description)
+        presetList.add(preset);
+      }
+    }
+    return presetList;
+  }
+
+  public boolean isChimeraLaunched()
+  {
+    // TODO: [Optional] What is the best way to test if chimera is launched?
+
+    // sendChimeraCommand("test", true) !=null
+    if (chimera != null)
+    {
+      return true;
+    }
+    return false;
+  }
+
+  public boolean launchChimera(List<String> chimeraPaths)
+  {
+    // Do nothing if Chimera is already launched
+    if (isChimeraLaunched())
+    {
+      return true;
+    }
+
+    // Try to launch Chimera (eventually using one of the possible paths)
+    String error = "Error message: ";
+    String workingPath = "";
+    // iterate over possible paths for starting Chimera
+    for (String chimeraPath : chimeraPaths)
+    {
+      File path = new File(chimeraPath);
+      if (!path.canExecute())
+      {
+        error += "File '" + path + "' does not exist.\n";
+        continue;
+      }
+      try
+      {
+        List<String> args = new ArrayList<String>();
+        args.add(chimeraPath);
+        args.add("--start");
+        args.add("ReadStdin");
+        ProcessBuilder pb = new ProcessBuilder(args);
+        chimera = pb.start();
+        error = "";
+        workingPath = chimeraPath;
+        logger.info("Strarting " + chimeraPath);
+        break;
+      } catch (Exception e)
+      {
+        // Chimera could not be started
+        error += e.getMessage();
+      }
+    }
+    // If no error, then Chimera was launched successfully
+    if (error.length() == 0)
+    {
+      // Initialize the listener threads
+      chimeraListenerThreads = new ListenerThreads(chimera,
+              structureManager);
+      chimeraListenerThreads.start();
+      // structureManager.initChimTable();
+      structureManager.setChimeraPathProperty(workingPath);
+      // TODO: [Optional] Check Chimera version and show a warning if below 1.8
+      // Ask Chimera to give us updates
+      startListening();
+      return true;
+    }
+
+    // Tell the user that Chimera could not be started because of an error
+    logger.warn(error);
+    return false;
+  }
+
+  /**
+   * Determine the color that Chimera is using for this model.
+   * 
+   * @param model
+   *          the ChimeraModel we want to get the Color for
+   * @return the default model Color for this model in Chimera
+   */
+  public Color getModelColor(ChimeraModel model)
+  {
+    List<String> colorLines = sendChimeraCommand(
+            "list model spec " + model.toSpec() + " attribute color", true);
+    if (colorLines == null || colorLines.size() == 0)
+    {
+      return null;
+    }
+    return ChimUtils.parseModelColor((String) colorLines.get(0));
+  }
+
+  /**
+   * 
+   * Get information about the residues associated with a model. This uses the
+   * Chimera listr command. We don't return the resulting residues, but we add
+   * the residues to the model.
+   * 
+   * @param model
+   *          the ChimeraModel to get residue information for
+   * 
+   */
+  public void addResidues(ChimeraModel model)
+  {
+    int modelNumber = model.getModelNumber();
+    int subModelNumber = model.getSubModelNumber();
+    // Get the list -- it will be in the reply log
+    List<String> reply = sendChimeraCommand(
+            "list residues spec " + model.toSpec(), true);
+    if (reply == null)
+    {
+      return;
+    }
+    for (String inputLine : reply)
+    {
+      ChimeraResidue r = new ChimeraResidue(inputLine);
+      if (r.getModelNumber() == modelNumber
+              || r.getSubModelNumber() == subModelNumber)
+      {
+        model.addResidue(r);
+      }
+    }
+  }
+
+  public List<String> getAttrList()
+  {
+    List<String> attributes = new ArrayList<String>();
+    final List<String> reply = sendChimeraCommand("list resattr", true);
+    if (reply != null)
+    {
+      for (String inputLine : reply)
+      {
+        String[] lineParts = inputLine.split("\\s");
+        if (lineParts.length == 2 && lineParts[0].equals("resattr"))
+        {
+          attributes.add(lineParts[1]);
+        }
+      }
+    }
+    return attributes;
+  }
+
+  public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
+          ChimeraModel model)
+  {
+    Map<ChimeraResidue, Object> values = new HashMap<ChimeraResidue, Object>();
+    final List<String> reply = sendChimeraCommand("list residue spec "
+            + model.toSpec() + " attribute " + aCommand, true);
+    if (reply != null)
+    {
+      for (String inputLine : reply)
+      {
+        String[] lineParts = inputLine.split("\\s");
+        if (lineParts.length == 5)
+        {
+          ChimeraResidue residue = ChimUtils
+                  .getResidue(lineParts[2], model);
+          String value = lineParts[4];
+          if (residue != null)
+          {
+            if (value.equals("None"))
+            {
+              continue;
+            }
+            if (value.equals("True") || value.equals("False"))
+            {
+              values.put(residue, Boolean.valueOf(value));
+              continue;
+            }
+            try
+            {
+              Double doubleValue = Double.valueOf(value);
+              values.put(residue, doubleValue);
+            } catch (NumberFormatException ex)
+            {
+              values.put(residue, value);
+            }
+          }
+        }
+      }
+    }
+    return values;
+  }
+
+  private volatile boolean busy = false;
+
+  /**
+   * Send a command to Chimera.
+   * 
+   * @param command
+   *          Command string to be send.
+   * @param reply
+   *          Flag indicating whether the method should return the reply from
+   *          Chimera or not.
+   * @return List of Strings corresponding to the lines in the Chimera reply or
+   *         <code>null</code>.
+   */
+  public List<String> sendChimeraCommand(String command, boolean reply)
+  {
+    if (!isChimeraLaunched())
+    {
+      return null;
+    }
+    while (busy)
+    {
+      try
+      {
+        Thread.sleep(25);
+      } catch (InterruptedException q)
+      {
+      }
+      ;
+    }
+    busy = true;
+    chimeraListenerThreads.clearResponse(command);
+    String text = command.concat("\n");
+    // System.out.println("send command to chimera: " + text);
+    try
+    {
+      // send the command
+      chimera.getOutputStream().write(text.getBytes());
+      chimera.getOutputStream().flush();
+    } catch (IOException e)
+    {
+      // logger.info("Unable to execute command: " + text);
+      // logger.info("Exiting...");
+      logger.warn("Unable to execute command: " + text);
+      logger.warn("Exiting...");
+      clearOnChimeraExit();
+      busy = false;
+      return null;
+    }
+    if (!reply)
+    {
+      busy = false;
+      return null;
+    }
+    List<String> rsp = chimeraListenerThreads.getResponse(command);
+    busy = false;
+    return rsp;
+  }
+
+  public StructureManager getStructureManager()
+  {
+    return structureManager;
+  }
+
+  public boolean isBusy()
+  {
+    return busy;
+  }
+
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraModel.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraModel.java
new file mode 100644 (file)
index 0000000..432c6fb
--- /dev/null
@@ -0,0 +1,466 @@
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+
+/**
+ * This class provides the implementation for the ChimeraModel, ChimeraChain, and ChimeraResidue
+ * objects
+ * 
+ * @author scooter
+ * 
+ */
+public class ChimeraModel implements ChimeraStructuralObject {
+
+       private String name; // The name of this model
+       private ModelType type; // The type of the model
+       private int modelNumber; // The model number
+       private int subModelNumber; // The sub-model number
+
+       private Color modelColor = null; // The color of this model (from Chimera)
+       private Object userData = null; // User data associated with this model
+       private boolean selected = false; // The selected state of this model
+
+       private TreeMap<String, ChimeraChain> chainMap; // The list of chains
+       // private TreeMap<String, ChimeraResidue> residueMap; // The list of residues
+       private HashSet<ChimeraResidue> funcResidues; // List of functional residues
+
+       /**
+        * Constructor to create a model
+        * 
+        * @param name
+        *            the name of this model
+        * @param color
+        *            the model Color
+        * @param modelNumber
+        *            the model number
+        * @param subModelNumber
+        *            the sub-model number
+        */
+       public ChimeraModel(String name, ModelType type, int modelNumber, int subModelNumber) {
+               this.name = name;
+               this.type = type;
+               this.modelNumber = modelNumber;
+               this.subModelNumber = subModelNumber;
+
+               this.chainMap = new TreeMap<String, ChimeraChain>();
+               this.funcResidues = new HashSet<ChimeraResidue>();
+       }
+
+       /**
+        * Constructor to create a model from the Chimera input line
+        * 
+        * @param inputLine
+        *            Chimera input line from which to construct this model
+        */
+       // TODO: [Optional] How to distinguish between PDB and MODBASE?
+       // invoked when listing models: listm type molecule; lists level molecule
+       // line = model id #0 type Molecule name 1ert
+       public ChimeraModel(String inputLine) {
+               this.name = ChimUtils.parseModelName(inputLine);
+               // TODO: [Optional] Write a separate method to get model type
+               if (name.startsWith("smiles")) {
+                       this.type = ModelType.SMILES;
+               } else {
+                       this.type = ModelType.PDB_MODEL;
+               }
+               this.modelNumber = ChimUtils.parseModelNumber(inputLine)[0];
+               this.subModelNumber = ChimUtils.parseModelNumber(inputLine)[1];
+
+               this.chainMap = new TreeMap<String, ChimeraChain>();
+               this.funcResidues = new HashSet<ChimeraResidue>();
+       }
+
+       /**
+        * Add a residue to this model
+        * 
+        * @param residue
+        *            to add to the model
+        */
+       public void addResidue(ChimeraResidue residue) {
+               residue.setChimeraModel(this);
+               // residueMap.put(residue.getIndex(), residue);
+               String chainId = residue.getChainId();
+               if (chainId != null) {
+                       addResidue(chainId, residue);
+               } else {
+                       addResidue("_", residue);
+               }
+               // Put it in our map so that we can return it in order
+               // residueMap.put(residue.getIndex(), residue);
+       }
+
+       /**
+        * Add a residue to a chain in this model. If the chain associated with chainId doesn't exist,
+        * it will be created.
+        * 
+        * @param chainId
+        *            to add the residue to
+        * @param residue
+        *            to add to the chain
+        */
+       public void addResidue(String chainId, ChimeraResidue residue) {
+               ChimeraChain chain = null;
+               if (!chainMap.containsKey(chainId)) {
+                       chain = new ChimeraChain(this.modelNumber, this.subModelNumber, chainId);
+                       chain.setChimeraModel(this);
+                       chainMap.put(chainId, chain);
+               } else {
+                       chain = chainMap.get(chainId);
+               }
+               chain.addResidue(residue);
+       }
+
+       /**
+        * Get the ChimeraModel (required for ChimeraStructuralObject interface)
+        * 
+        * @return ChimeraModel
+        */
+       public ChimeraModel getChimeraModel() {
+               return this;
+       }
+
+       /**
+        * Get the model color of this model
+        * 
+        * @return model color of this model
+        */
+       public Color getModelColor() {
+               return this.modelColor;
+       }
+
+       /**
+        * Set the color of this model
+        * 
+        * @param color
+        *            Color of this model
+        */
+       public void setModelColor(Color color) {
+               this.modelColor = color;
+       }
+
+       /**
+        * Return the name of this model
+        * 
+        * @return model name
+        */
+       public String getModelName() {
+               return name;
+       }
+
+       /**
+        * Set the name of this model
+        * 
+        * @param name
+        *            model name
+        */
+       public void setModelName(String name) {
+               this.name = name;
+       }
+
+       /**
+        * Get the model number of this model
+        * 
+        * @return integer model number
+        */
+       public int getModelNumber() {
+               return modelNumber;
+       }
+
+       /**
+        * Set the model number of this model
+        * 
+        * @param modelNumber
+        *            integer model number
+        */
+       public void setModelNumber(int modelNumber) {
+               this.modelNumber = modelNumber;
+       }
+
+       /**
+        * Get the sub-model number of this model
+        * 
+        * @return integer sub-model number
+        */
+       public int getSubModelNumber() {
+               return subModelNumber;
+       }
+
+       /**
+        * Set the sub-model number of this model
+        * 
+        * @param subModelNumber
+        *            integer model number
+        */
+       public void setSubModelNumber(int subModelNumber) {
+               this.subModelNumber = subModelNumber;
+       }
+
+       public ModelType getModelType() {
+               return type;
+       }
+
+       public void setModelType(ModelType type) {
+               this.type = type;
+       }
+
+       public HashSet<ChimeraResidue> getFuncResidues() {
+               return funcResidues;
+       }
+
+       public void setFuncResidues(List<String> residues) {
+               for (String residue : residues) {
+                       for (ChimeraChain chain : getChains()) {
+                               if (residue.indexOf("-") > 0) {
+                                       funcResidues.addAll(chain.getResidueRange(residue));
+                               } else {
+                                       funcResidues.add(chain.getResidue(residue));
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get the user data for this model
+        * 
+        * @return user data
+        */
+       public Object getUserData() {
+               return userData;
+       }
+
+       /**
+        * Set the user data for this model
+        * 
+        * @param data
+        *            user data to associate with this model
+        */
+       public void setUserData(Object data) {
+               this.userData = data;
+       }
+
+       /**
+        * Return the selected state of this model
+        * 
+        * @return the selected state
+        */
+       public boolean isSelected() {
+               return selected;
+       }
+
+       /**
+        * Set the selected state of this model
+        * 
+        * @param selected
+        *            a boolean to set the selected state to
+        */
+       public void setSelected(boolean selected) {
+               this.selected = selected;
+       }
+
+       /**
+        * Return the chains in this model as a List
+        * 
+        * @return the chains in this model as a list
+        */
+       public List<ChimeraStructuralObject> getChildren() {
+               return new ArrayList<ChimeraStructuralObject>(chainMap.values());
+       }
+
+       /**
+        * Return the chains in this model as a colleciton
+        * 
+        * @return the chains in this model
+        */
+       public Collection<ChimeraChain> getChains() {
+               return chainMap.values();
+       }
+
+       /**
+        * Get the number of chains in this model
+        * 
+        * @return integer chain count
+        */
+       public int getChainCount() {
+               return chainMap.size();
+       }
+
+       /**
+        * Get the list of chain names associated with this model
+        * 
+        * @return return the list of chain names for this model
+        */
+       public Collection<String> getChainNames() {
+               return chainMap.keySet();
+       }
+
+       /**
+        * Get the residues associated with this model
+        * 
+        * @return the list of residues in this model
+        */
+       public Collection<ChimeraResidue> getResidues() {
+               Collection<ChimeraResidue> residues = new ArrayList<ChimeraResidue>();
+               for (ChimeraChain chain : getChains()) {
+                       residues.addAll(chain.getResidues());
+               }
+               return residues;
+       }
+
+       /**
+        * Get the number of residues in this model
+        * 
+        * @return integer residues count
+        */
+       public int getResidueCount() {
+               int count = 0;
+               for (ChimeraChain chain : getChains()) {
+                       count += chain.getResidueCount();
+               }
+               return count;
+       }
+
+       /**
+        * Get a specific chain from the model
+        * 
+        * @param chain
+        *            the ID of the chain to return
+        * @return ChimeraChain associated with the chain
+        */
+       public ChimeraChain getChain(String chain) {
+               if (chainMap.containsKey(chain)) {
+                       return chainMap.get(chain);
+               }
+               return null;
+       }
+
+       /**
+        * Return a specific residue based on its index
+        * 
+        * @param index
+        *            of the residue to return
+        * @return the residue associated with that index
+        */
+       public ChimeraResidue getResidue(String chainId, String index) {
+               if (chainMap.containsKey(chainId)) {
+                       return chainMap.get(chainId).getResidue(index);
+               }
+               return null;
+       }
+
+       /**
+        * Checks if this model has selected children.
+        */
+       public boolean hasSelectedChildren() {
+               if (selected) {
+                       return true;
+               } else {
+                       for (ChimeraChain chain : getChains()) {
+                               if (chain.hasSelectedChildren()) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Return the list of selected residues
+        * 
+        * @return all selected residues
+        */
+       public List<ChimeraResidue> getSelectedResidues() {
+               List<ChimeraResidue> residueList = new ArrayList<ChimeraResidue>();
+               for (ChimeraChain chain : getChains()) {
+                       if (selected) {
+                               residueList.addAll(chain.getSelectedResidues());
+                       } else {
+                               residueList.addAll(getResidues());
+                       }
+               }
+               return residueList;
+       }
+
+
+       /**
+        * Return the Chimera specification for this model.
+        */
+       public String toSpec() {
+               if (subModelNumber == 0)
+                       return ("#" + modelNumber);
+               return ("#" + modelNumber + "." + subModelNumber);
+       }
+
+       /**
+        * Return a string representation for the model. Shorten if longer than 100 characters.
+        */
+       public String toString() {
+               String modelName = "";
+               // TODO: [Optional] Change cutoff for shortening model names in the structure naviagator dialog
+               if (getChainCount() > 0) {
+                       modelName = "Model " + toSpec() + " " + name + " (" + getChainCount() + " chains, "
+                                       + getResidueCount() + " residues)";
+               } else if (getResidueCount() > 0) {
+                       modelName = "Model " + toSpec() + " " + name + " (" + getResidueCount() + " residues)";
+               } else {
+                       modelName = "Model " + toSpec() + " " + name + "";
+               }
+
+               Set<String> networkNames = new HashSet<String>();
+               Set<String> nodeNames = new HashSet<String>();
+               Set<String> edgeNames = new HashSet<String>();
+
+               String cytoName = " [";
+               if (networkNames.size() > 0) {
+                       if (networkNames.size() == 1) {
+                               cytoName += "Network {";
+                       } else if (networkNames.size() > 1) {
+                               cytoName += "Networks {";
+                       }
+                       for (String cName : networkNames) {
+                               cytoName += cName + ",";
+                       }
+                       cytoName = cytoName.substring(0, cytoName.length() - 1) + "}, ";
+               }
+               if (nodeNames.size() > 0) {
+                       if (nodeNames.size() == 1) {
+                               cytoName += "Node {";
+                       } else if (nodeNames.size() > 1) {
+                               cytoName += "Nodes {";
+                       }
+                       for (String cName : nodeNames) {
+                               cytoName += cName + ",";
+                       }
+                       cytoName = cytoName.substring(0, cytoName.length() - 1) + "}, ";
+               }
+               if (edgeNames.size() > 0) {
+                       if (edgeNames.size() == 1) {
+                               cytoName += "Edge {";
+                       } else if (edgeNames.size() > 1) {
+                               cytoName += "Edges {";
+                       }
+                       for (String cName : edgeNames) {
+                               cytoName += cName + ",";
+                       }
+                       cytoName = cytoName.substring(0, cytoName.length() - 1) + "}, ";
+               }
+               if (cytoName.endsWith(", ")) {
+                       cytoName = cytoName.substring(0, cytoName.length() - 2);
+               }
+               cytoName += "]";
+               String nodeName = modelName + cytoName;
+               if (nodeName.length() > 100) {
+                       nodeName = nodeName.substring(0, 100) + "...";
+               }
+               return nodeName;
+       }
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraResidue.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraResidue.java
new file mode 100644 (file)
index 0000000..10825ae
--- /dev/null
@@ -0,0 +1,394 @@
+/* vim: set ts=2: */
+/**
+ * Copyright (c) 2006 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions, and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions, and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *   3. Redistributions must acknowledge that this software was
+ *      originally developed by the UCSF Computer Graphics Laboratory
+ *      under support by the NIH National Center for Research Resources,
+ *      grant P41-RR01081.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class provides the implementation for the ChimeraResidue, object
+ * 
+ * @author scooter
+ * 
+ */
+
+public class ChimeraResidue implements ChimeraStructuralObject, Comparable<ChimeraResidue> {
+
+       /* Constants */
+       public static final int SINGLE_LETTER = 0; // Display residues as a single
+                                                                                                                                                                                       // letter
+       public static final int THREE_LETTER = 1; // Display residues as three letters
+       public static final int FULL_NAME = 2; // Display full residue names
+
+       private String type; // Residue type
+       private String index; // Residue index
+       private String chainId; // ChainID for this residue
+       private int modelNumber; // model number for this residue
+       private int subModelNumber; // sub-model number for this residue
+       protected int residueNumber;
+       protected String insertionCode;
+       private ChimeraModel chimeraModel; // ChimeraModel the residue is part of
+       private Object userData; // user data to associate with this residue
+       // public static HashMap<String, String> aaNames = null; // a map of amino acid
+       // names
+       private static int displayType = THREE_LETTER; // the current display type
+       private boolean selected = false; // the selection state
+
+       /**
+        * Constructor to create a new ChimeraResidue
+        * 
+        * @param type
+        *          the residue type
+        * @param index
+        *          the index of the residue
+        * @param modelNumber
+        *          the model number this residue is part of
+        */
+       public ChimeraResidue(String type, String index, int modelNumber) {
+               this(type, index, modelNumber, 0);
+       }
+
+       /**
+        * Constructor to create a new ChimeraResidue
+        * 
+        * @param type
+        *          the residue type
+        * @param index
+        *          the index of the residue
+        * @param modelNumber
+        *          the model number this residue is part of
+        * @param subModelNumber
+        *          the sub-model number this residue is part of
+        */
+       public ChimeraResidue(String type, String index, int modelNumber, int subModelNumber) {
+               this.type = type;
+               this.index = index;
+               this.modelNumber = modelNumber;
+               this.subModelNumber = subModelNumber;
+               splitInsertionCode(this.index);
+               // if (aaNames == null) {
+               // initNames();
+               // }
+       }
+
+       /**
+        * Constructor to create a new ChimeraResidue from an input line
+        * 
+        * @param chimeraInputLine
+        *          a Chimera residue description
+        */
+       // invoked when listing (selected) residues: listr spec #0; lists level residue
+       // Line: residue id #0:37.A type MET
+       public ChimeraResidue(String chimeraInputLine) {
+               // initNames();
+               String[] split1 = chimeraInputLine.split(":");
+
+               // First half has model number -- get the number
+               int numberOffset = split1[0].indexOf('#');
+               String model = split1[0].substring(numberOffset + 1);
+               int decimalOffset = model.indexOf('.'); // Do we have a sub-model?
+               try {
+                       this.subModelNumber = 0;
+                       if (decimalOffset > 0) {
+                               this.subModelNumber = Integer.parseInt(model.substring(decimalOffset + 1));
+                               this.modelNumber = Integer.parseInt(model.substring(0, decimalOffset));
+                       } else {
+                               this.modelNumber = Integer.parseInt(model);
+                       }
+               } catch (Exception e) {
+                       LoggerFactory.getLogger(ChimeraResidue.class)
+                                       .error("Unexpected return from Chimera: " + model);
+                       this.modelNumber = -1;
+               }
+
+               // Second half has residue info: index & type
+               String[] rTokens = split1[1].split(" ");
+               this.type = rTokens[2];
+
+               String[] iTokens = rTokens[0].split("\\.");
+               if (iTokens.length > 0) {
+                       this.index = iTokens[0];
+
+                       // Careful, might or might not have a chainID
+                       if (iTokens.length > 1)
+                               this.chainId = iTokens[1];
+                       else
+                               this.chainId = "_";
+               } else
+                       this.index = rTokens[0];
+
+               splitInsertionCode(this.index);
+       }
+
+       /**
+        * Set the selected state for this residue
+        * 
+        * @param selected
+        *          the selection state to set
+        */
+       public void setSelected(boolean selected) {
+               this.selected = selected;
+       }
+
+       /**
+        * Return the selected state of this residue
+        * 
+        * @return the selected state
+        */
+       public boolean isSelected() {
+               return selected;
+       }
+
+       /**
+        * Return an array made up of this residue (required for ChimeraStructuralObject interface
+        * 
+        * @return a List with this residue as its sole member
+        */
+       public List<ChimeraStructuralObject> getChildren() {
+               List<ChimeraStructuralObject> v = new ArrayList<ChimeraStructuralObject>();
+               v.add(this);
+               return v;
+       }
+
+       /**
+        * Return the string representation of this residue as follows: "<i>residue_name</i> <i>index</i>"
+        * where <i>residue_name</i> could be either the single letter, three letter, or full name
+        * representation of the amino acid.
+        * 
+        * @return the string representation
+        */
+       public String displayName() {
+               return toString();
+       }
+
+       /**
+        * Return the string representation of this residue as follows: "<i>residue_name</i> <i>index</i>"
+        * where <i>residue_name</i> could be either the single letter, three letter, or full name
+        * representation of the amino acid.
+        * 
+        * @return the string representation
+        */
+       public String toString() {
+               if (displayType == FULL_NAME) {
+                       return (ChimUtils.toFullName(type) + " " + index);
+               } else if (displayType == SINGLE_LETTER) {
+                       return (ChimUtils.toSingleLetter(type) + " " + index);
+               } else if (displayType == THREE_LETTER) {
+                       return (ChimUtils.toThreeLetter(type) + " " + index);
+               } else {
+                       return (type + " " + index);
+               }
+       }
+
+       /**
+        * Return the Chimera specification for this Residue
+        * 
+        * @return Chimera specification
+        */
+       public String toSpec() {
+               if (!chainId.equals("_"))
+                       return ("#" + modelNumber + ":" + index + "." + chainId);
+               else
+                       return ("#" + modelNumber + ":" + index + ".");
+       }
+
+       /**
+        * Get the index of this residue
+        * 
+        * @return residue index
+        */
+       public String getIndex() {
+               return this.index;
+       }
+
+       /**
+        * Get the chainID for this residue
+        * 
+        * @return String value of the chainId
+        */
+       public String getChainId() {
+               return this.chainId;
+       }
+
+       /**
+        * Get the type for this residue
+        * 
+        * @return residue type
+        */
+       public String getType() {
+               return this.type;
+       }
+
+       /**
+        * Get the model number for this residue
+        * 
+        * @return the model number
+        */
+       public int getModelNumber() {
+               return this.modelNumber;
+       }
+
+       /**
+        * Get the sub-model number for this residue
+        * 
+        * @return the sub-model number
+        */
+       public int getSubModelNumber() {
+               return this.subModelNumber;
+       }
+
+       /**
+        * Get the model this residue is part of
+        * 
+        * @return the ChimeraModel
+        */
+       public ChimeraModel getChimeraModel() {
+               return this.chimeraModel;
+       }
+
+       /**
+        * Set the model this residue is part of
+        * 
+        * @param chimeraModel
+        *          the ChimeraModel this model is part of
+        */
+       public void setChimeraModel(ChimeraModel chimeraModel) {
+               this.chimeraModel = chimeraModel;
+       }
+
+       /**
+        * Get the user data for this residue
+        * 
+        * @return user data
+        */
+       public Object getUserData() {
+               return userData;
+       }
+
+       /**
+        * Set the user data for this Residue
+        * 
+        * @param data
+        *          the user data to associate with this residue
+        */
+       public void setUserData(Object data) {
+               this.userData = data;
+       }
+
+       public int compareTo(ChimeraResidue c2) {
+               if (residueNumber < c2.residueNumber)
+                       return -1;
+               else if (residueNumber == c2.residueNumber) {
+                       if (insertionCode == null && c2.insertionCode == null)
+                               return 0;
+                       else if (insertionCode == null)
+                               return -1;
+                       else if (c2.insertionCode == null)
+                               return 1;
+                       return (insertionCode.compareTo(c2.insertionCode));
+               }
+               return 1;
+       }
+
+       public void splitInsertionCode(String residue) {
+               // OK, split the index into number and insertion code
+               Pattern p = Pattern.compile("(\\d*)([A-Z]?)");
+               Matcher m = p.matcher(residue);
+               if (m.matches()) {
+                       this.residueNumber = Integer.parseInt(m.group(1));
+                       if (m.groupCount() > 1)
+                               this.insertionCode = m.group(2);
+                       else
+                               this.insertionCode = null;
+               }
+       }
+
+       /**********************************************
+        * Static routines
+        *********************************************/
+
+       /**
+        * Initialize the residue names
+        */
+       // private static void initNames() {
+       // // Create our residue name table
+       // aaNames = new HashMap<String, String>();
+       // aaNames.put("ALA", "A Ala Alanine N[C@@H](C)C(O)=O");
+       // aaNames.put("ARG", "R Arg Arginine N[C@@H](CCCNC(N)=N)C(O)=O");
+       // aaNames.put("ASN", "N Asn Asparagine N[C@@H](CC(N)=O)C(O)=O");
+       // aaNames.put("ASP", "D Asp Aspartic_acid N[C@@H](CC(O)=O)C(O)=O");
+       // aaNames.put("CYS", "C Cys Cysteine N[C@@H](CS)C(O)=O");
+       // aaNames.put("GLN", "Q Gln Glutamine N[C@H](C(O)=O)CCC(N)=O");
+       // aaNames.put("GLU", "E Glu Glumatic_acid N[C@H](C(O)=O)CCC(O)=O");
+       // aaNames.put("GLY", "G Gly Glycine NCC(O)=O");
+       // aaNames.put("HIS", "H His Histidine N[C@@H](CC1=CN=CN1)C(O)=O");
+       // aaNames.put("ILE", "I Ile Isoleucine N[C@]([C@H](C)CC)([H])C(O)=O");
+       // aaNames.put("LEU", "L Leu Leucine N[C@](CC(C)C)([H])C(O)=O");
+       // aaNames.put("LYS", "K Lys Lysine N[C@](CCCCN)([H])C(O)=O");
+       // aaNames.put("DLY", "K Dly D-Lysine NCCCC[C@@H](N)C(O)=O");
+       // aaNames.put("MET", "M Met Methionine N[C@](CCSC)([H])C(O)=O");
+       // aaNames.put("PHE", "F Phe Phenylalanine N[C@](CC1=CC=CC=C1)([H])C(O)=O");
+       // aaNames.put("PRO", "P Pro Proline OC([C@@]1([H])NCCC1)=O");
+       // aaNames.put("SER", "S Ser Serine OC[C@](C(O)=O)([H])N");
+       // aaNames.put("THR", "T Thr Threonine O[C@H](C)[C@](C(O)=O)([H])N");
+       // aaNames.put("TRP", "W Trp Tryptophan N[C@@]([H])(CC1=CN([H])C2=C1C=CC=C2)C(O)=O");
+       // aaNames.put("TYR", "Y Tyr Tyrosine N[C@@](C(O)=O)([H])CC1=CC=C(O)C=C1");
+       // aaNames.put("VAL", "V Val Valine N[C@@](C(O)=O)([H])C(C)C");
+       // aaNames.put("ASX", "B Asx Aspartic_acid_or_Asparagine");
+       // aaNames.put("GLX", "Z Glx Glutamine_or_Glutamic_acid");
+       // aaNames.put("XAA", "X Xaa Any_or_unknown_amino_acid");
+       // aaNames.put("HOH", "HOH HOH Water [H]O[H]");
+       // }
+
+       /**
+        * Set the display type.
+        * 
+        * @param type
+        *          the display type
+        */
+       public static void setDisplayType(int type) {
+               displayType = type;
+       }
+
+       public static int getDisplayType() {
+               return displayType;
+       }
+
+       public boolean hasSelectedChildren() {
+               return false;
+       }
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraStructuralObject.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraStructuralObject.java
new file mode 100644 (file)
index 0000000..1678317
--- /dev/null
@@ -0,0 +1,112 @@
+/* vim: set ts=2: */
+/**
+ * Copyright (c) 2006 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions, and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions, and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *   3. Redistributions must acknowledge that this software was
+ *      originally developed by the UCSF Computer Graphics Laboratory
+ *      under support by the NIH National Center for Research Resources,
+ *      grant P41-RR01081.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.lang.String;
+import java.util.List;
+
+/**
+ * This interface provides a common set of methods that are implemented by the ChimeraModel,
+ * ChimeraChain, and ChimeraResidue classes.
+ * 
+ * @author scooter
+ * 
+ */
+
+public interface ChimeraStructuralObject {
+
+       /**
+        * Return the Chimera selection specification for this object
+        * 
+        * @return a String representing a Chimera atom-spec
+        */
+       public String toSpec();
+
+       /**
+        * Return a String representation for this object
+        * 
+        * @return a String representing the object name
+        */
+       public String toString();
+
+       /**
+        * Return the userData for this object
+        * 
+        * @return an Object representing the userData (usually TreePath)
+        */
+       public Object getUserData();
+
+       /**
+        * Set the userData for this object
+        * 
+        * @param userData
+        *          the Object representing the userData (usually TreePath)
+        */
+       public void setUserData(Object userData);
+
+       /**
+        * Return the ChimeraModel for this object
+        * 
+        * @return the ChimeraModel this object is part of
+        */
+       public ChimeraModel getChimeraModel();
+
+       /**
+        * Set the "selected" state of this object
+        * 
+        * @param selected
+        *          boolean value as to whether this object is selected
+        */
+       public void setSelected(boolean selected);
+
+       /**
+        * Get the "selected" state of this object
+        * 
+        * @return the selected state of this object
+        */
+       public boolean isSelected();
+
+       /**
+        * Get the selected state of this object and its children.
+        * 
+        * @return true if any child is selected.
+        */
+       public boolean hasSelectedChildren();
+
+       /**
+        * Get the children of this object (if any)
+        * 
+        * @return the children of the object
+        */
+       public List<ChimeraStructuralObject> getChildren();
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraTreeModel.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraTreeModel.java
new file mode 100644 (file)
index 0000000..f3447ce
--- /dev/null
@@ -0,0 +1,194 @@
+/* vim: set ts=2: */
+/**
+ * Copyright (c) 2006 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions, and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions, and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *   3. Redistributions must acknowledge that this software was
+ *      originally developed by the UCSF Computer Graphics Laboratory
+ *      under support by the NIH National Center for Research Resources,
+ *      grant P41-RR01081.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package ext.edu.ucsf.rbvi.strucviz2;
+
+// System imports
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+// Cytoscape imports
+// StructureViz imports
+
+/**
+ * The ChimeraTreeModel class provides the underlying model
+ * for the navigation tree in the ModelNavigatorDialog.
+ *
+ * @author scooter
+ * @see ModelNavigatorDialog
+        */
+public class ChimeraTreeModel extends DefaultTreeModel {
+       private ChimeraManager chimeraManager;
+       private JTree navigationTree;
+       private int residueDisplay = ChimeraResidue.THREE_LETTER;
+
+       /**
+        * Constructor for the ChimeraTreeModel.
+        *
+        * @param chimeraObject the Chimera object that this tree represents
+        * @param tree the JTree used to display the object
+        * @see Chimera
+        */
+       public ChimeraTreeModel (ChimeraManager chimeraManager, JTree tree) {
+               super(new DefaultMutableTreeNode());
+               this.chimeraManager = chimeraManager;
+               this.navigationTree = tree;
+               DefaultMutableTreeNode rootNode = buildTree();
+               this.setRoot(rootNode);
+       }
+
+       /**
+        * Set the display type for the residues.  The display type
+        * must be one of:
+        *
+        *      ChimeraResidue.THREE_LETTER
+        *      ChimeraResidue.SINGLE_LETTER
+        *      ChimeraResidue.FULL_NAME
+        *
+        * @param newDisplay the display type
+        * @see ChimeraResidue
+        */
+       public void setResidueDisplay(int newDisplay) {
+               this.residueDisplay = newDisplay;
+       }
+               
+       /**
+        * This method is called to rebuild the tree model "from scratch"
+        */
+       public void reload() {
+               // First, rebuild the tree with the new data
+               DefaultMutableTreeNode rootNode = buildTree();
+               this.setRoot(rootNode);
+
+               // Now let the superclass do all of the work
+               super.reload();
+       }
+
+       /**
+        * Rebuild an existing tree
+        */
+       public void rebuildTree() {
+               DefaultMutableTreeNode rootNode = buildTree();
+               DefaultTreeModel model = (DefaultTreeModel)navigationTree.getModel();
+               model.setRoot(rootNode);
+               model.reload();
+       }
+
+       /**
+        * Build the tree from the current Chimera data
+        *
+        * @return DefaultMutableTreeNode that represents the currently loaded Chimera models
+        */
+       private DefaultMutableTreeNode buildTree() {
+               int modelCount = chimeraManager.getChimeraModelsCount(true);
+               DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(modelCount+" Open Chimera Models");
+               TreePath rootPath = new TreePath(rootNode);
+
+               TreePath path = null;
+               DefaultMutableTreeNode model = null;
+
+               // Add all of the Chimera models
+               for (ChimeraModel chimeraModel: chimeraManager.getChimeraModels()) {
+                       model = new DefaultMutableTreeNode(chimeraModel);
+                       path = rootPath.pathByAddingChild(model);
+                       chimeraModel.setUserData(path);
+                       addChainNodes(chimeraModel, model, path);
+                       rootNode.add(model);
+               }
+               return rootNode;
+       }
+
+       /**
+        * add chains to a tree model
+        *
+        * @param chimeraModel the ChimeraModel to get the chains from
+        * @param treeModel the tree model to add the chains to
+        * @param treePath the tree path where the chains should be added
+        */
+       private void addChainNodes(ChimeraModel chimeraModel, 
+                                                                                                                DefaultMutableTreeNode treeModel,
+                                                                                                                TreePath treePath) {
+               DefaultMutableTreeNode chain = null;
+               TreePath chainPath = null;
+
+               // Get the list of chains
+               Collection<ChimeraChain> chainList = chimeraModel.getChains();
+
+               if (chainList.size() == 0) {
+                       // No chains!  Just add the residues
+                       addResidues(chimeraModel.getResidues(), treeModel, treePath);   
+                       return;
+               }
+
+               // Iterate over the chains and add the chain and all of
+               // the chain's residues
+               for (ChimeraChain chimeraChain: chainList) {
+                       chain = new DefaultMutableTreeNode(chimeraChain);
+                       chainPath = treePath.pathByAddingChild(chain);
+                       chimeraChain.setUserData(chainPath);
+                       addResidues(chimeraChain.getResidues(), chain, chainPath);
+                       treeModel.add(chain);   
+               }
+       }
+
+       /**
+        * add residues to a tree model
+        *
+        * @param residues the residues to add
+        * @param treeModel the tree model to add the residues to
+        * @param treePath the tree path where the residues should be added
+        */
+       private void addResidues(Collection<ChimeraResidue> residues, 
+                                                                                                        DefaultMutableTreeNode treeModel,
+                                                                                                        TreePath treePath) {
+               DefaultMutableTreeNode residue = null;
+               TreePath residuePath = null;
+
+               List<ChimeraResidue> sortedResidues = new ArrayList<ChimeraResidue>(residues);
+               Collections.sort(sortedResidues);
+
+               // Iterate over all residues & add them to the tree
+               for (ChimeraResidue res: sortedResidues) {
+                       res.setDisplayType(this.residueDisplay);
+                       residue = new DefaultMutableTreeNode(res);
+                       residuePath = treePath.pathByAddingChild(residue);
+                       res.setUserData(residuePath);
+                       treeModel.add(residue);
+               }
+       }       
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
new file mode 100644 (file)
index 0000000..ec956d7
--- /dev/null
@@ -0,0 +1,957 @@
+package ext.edu.ucsf.rbvi.strucviz2;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This object maintains the relationship between Chimera objects and Cytoscape
+ * objects.
+ */
+
+public class StructureManager
+{
+  static final String[] defaultStructureKeys =
+  { "Structure", "pdb", "pdbFileName", "PDB ID", "structure",
+      "biopax.xref.PDB", "pdb_ids", "ModelName", "ModelNumber" };
+
+  static final String[] defaultChemStructKeys =
+  { "Smiles", "smiles", "SMILES" };
+
+  static final String[] defaultResidueKeys =
+  { "FunctionalResidues", "ResidueList", "Residues" };
+
+  private final String chimeraPropertyName = "chimera";
+
+  private final String chimeraPathPropertyKey = "LastChimeraPath";
+
+  public enum ModelType
+  {
+    PDB_MODEL, MODBASE_MODEL, SMILES
+  };
+
+  public static Properties pathProps;
+
+  private String chimeraCommandAttr = "ChimeraCommand";
+
+  private String chimeraOutputTable = "ChimeraTable";
+
+  private String chimeraOutputAttr = "ChimeraOutput";
+
+  private boolean haveGUI = true;
+
+  private ChimeraManager chimeraManager = null;
+
+  static private List<ChimeraStructuralObject> chimSelectionList;
+
+  private boolean ignoreCySelection = false;
+
+  private File configurationDirectory = null;
+
+  private static Logger logger = LoggerFactory
+          .getLogger(ext.edu.ucsf.rbvi.strucviz2.StructureManager.class);
+
+  public StructureManager(boolean haveGUI)
+  {
+    this.haveGUI = haveGUI;
+    // Create the Chimera interface
+    chimeraManager = new ChimeraManager(this);
+    chimSelectionList = new ArrayList<ChimeraStructuralObject>();
+    pathProps = new Properties();
+  }
+
+  public ChimeraManager getChimeraManager()
+  {
+    return chimeraManager;
+  }
+
+  public boolean openStructures(Collection<List<String>> chimObjNames,
+          ModelType type)
+  {
+    // new models
+    Map<String, List<ChimeraModel>> newModels = new HashMap<String, List<ChimeraModel>>();
+    if (chimObjNames.size() > 0)
+    {
+      List<String> names = chimObjNames.iterator().next();
+      if (names == null)
+      {
+        return false;
+      }
+      for (String chimObjName : names)
+      {
+        // get or open the corresponding models if they already exist
+        List<ChimeraModel> currentModels = chimeraManager.getChimeraModels(
+                chimObjName, type);
+        if (currentModels.size() == 0)
+        {
+          // open and return models
+          currentModels = chimeraManager.openModel(chimObjName, type);
+          if (currentModels == null)
+          {
+            // failed to open model, continue with next
+            continue;
+          }
+          // if (type == ModelType.SMILES) {
+          // newModels.put("smiles:" + chimObjName, currentModels);
+          // } else {
+          newModels.put(chimObjName, currentModels);
+          // }
+          // for each model
+          for (ChimeraModel currentModel : currentModels)
+          {
+            // if not RIN then associate new model with the Cytoscape
+            // node
+            // if (!currentChimMap.containsKey(currentModel)) {
+            // currentChimMap.put(currentModel, new HashSet<CyIdentifiable>());
+            // }
+          }
+        }
+      }
+    }
+    else
+    {
+      return false;
+    }
+    // update dialog
+    // if (mnDialog != null) {
+    // mnDialog.modelChanged();
+    // }
+    // aTask.associate();
+    return true;
+
+  }
+
+  // TODO: [Release] Handle case where one network is associated with two models
+  // that are opened
+  // at the same time
+  /*
+   * public boolean openStructures(CyNetwork network, Map<CyIdentifiable,
+   * List<String>> chimObjNames, ModelType type) { if
+   * (!chimeraManager.isChimeraLaunched() &&
+   * !chimeraManager.launchChimera(getChimeraPaths(network))) {
+   * logger.error("Chimera could not be launched."); return false; } else if
+   * (chimObjNames.size() == 0) { return false; } else if (network == null) {
+   * return openStructures(chimObjNames.values(), type); }
+   * 
+   * // potential rins Set<CyNetwork> potentialRINs = new HashSet<CyNetwork>();
+   * // attributes List<String> attrsFound = new ArrayList<String>();
+   * attrsFound.
+   * addAll(CytoUtils.getMatchingAttributes(network.getDefaultNodeTable(),
+   * getCurrentStructureKeys(network)));
+   * attrsFound.addAll(CytoUtils.getMatchingAttributes
+   * (network.getDefaultNodeTable(), getCurrentChemStructKeys(network))); // new
+   * models Map<String, List<ChimeraModel>> newModels = new HashMap<String,
+   * List<ChimeraModel>>(); // for each node that has an associated structure
+   * for (CyIdentifiable cyObj : chimObjNames.keySet()) { // get possible res
+   * specs List<String> specsFound = null; if (cyObj instanceof CyNode) {
+   * specsFound = ChimUtils.getResidueKeys(network.getDefaultNodeTable(), cyObj,
+   * attrsFound); } // save node to track its selection and mapping to chimera
+   * objects if (!currentCyMap.containsKey(cyObj)) { currentCyMap.put(cyObj, new
+   * HashSet<ChimeraStructuralObject>()); } // save node to network mapping to
+   * keep track of selection events if (!networkMap.containsKey(cyObj)) {
+   * networkMap.put(cyObj, new HashSet<CyNetwork>()); }
+   * networkMap.get(cyObj).add(network); // for each structure that has to be
+   * opened for (String chimObjName : chimObjNames.get(cyObj)) { // get or open
+   * the corresponding models if they already exist List<ChimeraModel>
+   * currentModels = chimeraManager.getChimeraModels(chimObjName, type); if
+   * (currentModels.size() == 0) { // open and return models currentModels =
+   * chimeraManager.openModel(chimObjName, type); if (currentModels == null) {
+   * // failed to open model, continue with next continue; } // if (type ==
+   * ModelType.SMILES) { // newModels.put("smiles:" + chimObjName,
+   * currentModels); // } else { newModels.put(chimObjName, currentModels); // }
+   * // for each model for (ChimeraModel currentModel : currentModels) { //
+   * check if it is a RIN boolean foundRIN = false; if
+   * (currentModel.getModelType().equals(ModelType.PDB_MODEL)) { // go through
+   * all node annotations and check if any of them is a residue // or a chain if
+   * (cyObj instanceof CyNode && network.containsNode((CyNode) cyObj) &&
+   * specsFound != null && specsFound.size() > 0) { for (String resSpec :
+   * specsFound) { ChimeraStructuralObject res =
+   * ChimUtils.fromAttribute(resSpec, chimeraManager); if (res != null && (res
+   * instanceof ChimeraResidue || res instanceof ChimeraChain)) { // if so,
+   * assume it might be a RIN potentialRINs.add(network); foundRIN = true;
+   * break; } } } else if (cyObj instanceof CyNetwork) { // if cyObj is a
+   * network, check for residue/chain annotations in an // arbitrary node
+   * CyNetwork rinNet = (CyNetwork) cyObj; if (rinNet.getNodeList().size() > 0)
+   * { specsFound = ChimUtils.getResidueKeys( rinNet.getDefaultNodeTable(),
+   * rinNet.getNodeList().get(0), attrsFound); for (String resSpec : specsFound)
+   * { ChimeraStructuralObject res = ChimUtils.fromAttribute( resSpec,
+   * chimeraManager); if (res != null && (res instanceof ChimeraResidue || res
+   * instanceof ChimeraChain)) { potentialRINs.add(network); foundRIN = true;
+   * break; } } } } } if (foundRIN) { continue; } // if not RIN then associate
+   * new model with the Cytoscape // node if
+   * (!currentChimMap.containsKey(currentModel)) {
+   * currentChimMap.put(currentModel, new HashSet<CyIdentifiable>()); } String
+   * cyObjName = network.getRow(cyObj).get(CyNetwork.NAME, String.class); if
+   * (cyObjName != null && cyObjName.endsWith(currentModel.getModelName())) { //
+   * it is a modbase model, associate directly
+   * currentCyMap.get(cyObj).add(currentModel);
+   * currentChimMap.get(currentModel).add(cyObj);
+   * currentModel.addCyObject(cyObj, network); } else if (specsFound != null &&
+   * specsFound.size() > 0) { for (String resSpec : specsFound) {
+   * ChimeraStructuralObject specModel = ChimUtils.fromAttribute( resSpec,
+   * chimeraManager); if (specModel == null &&
+   * resSpec.equals(currentModel.getModelName())) { specModel =
+   * chimeraManager.getChimeraModel( currentModel.getModelNumber(),
+   * currentModel.getSubModelNumber()); } if (specModel != null &&
+   * currentModel.toSpec().equals(specModel.toSpec()) ||
+   * currentModel.getModelName().equals("smiles:" + resSpec)) {
+   * currentCyMap.get(cyObj).add(currentModel);
+   * currentChimMap.get(currentModel).add(cyObj);
+   * currentModel.addCyObject(cyObj, network);
+   * currentModel.setFuncResidues(ChimUtils.parseFuncRes(
+   * getResidueList(network, cyObj), chimObjName)); } } } } } } } // networks
+   * that contain nodes associated to newly opened models // this will usually
+   * be of length 1 for (CyNetwork net : potentialRINs) {
+   * addStructureNetwork(net); } // update dialog if (mnDialog != null) {
+   * mnDialog.modelChanged(); } aTask.associate(); return true; }
+   */
+  public void closeStructures(Set<String> chimObjNames)
+  {
+    // for each cytoscape object and chimera model pair
+    for (String modelName : chimObjNames)
+    {
+      List<ChimeraModel> models = chimeraManager
+              .getChimeraModels(modelName);
+      for (ChimeraModel model : models)
+      {
+        closeModel(model);
+      }
+    }
+    // if (mnDialog != null) {
+    // mnDialog.modelChanged();
+    // }
+  }
+
+  // TODO: [Optional] Can we make a screenshot of a single molecule?
+  public File saveChimeraImage()
+  {
+    File tmpFile = null;
+    try
+    {
+      // Create the temp file name
+      tmpFile = File.createTempFile("structureViz", ".png");
+      chimeraManager.sendChimeraCommand("set bgTransparency", false);
+      chimeraManager.sendChimeraCommand(
+              "copy file " + tmpFile.getAbsolutePath() + " png", true);
+      chimeraManager.sendChimeraCommand("unset bgTransparency", false);
+    } catch (IOException ioe)
+    {
+      // Log error
+      logger.error("Error writing image", ioe);
+    }
+    return tmpFile;
+  }
+
+  public void closeModel(ChimeraModel model)
+  {
+    // close model in Chimera
+    chimeraManager.closeModel(model);
+    // remove all associations
+    // if (currentChimMap.containsKey(model)) {
+    // for (CyIdentifiable cyObj : model.getCyObjects().keySet()) {
+    // if (cyObj == null) {
+    // continue;
+    // } else if (currentCyMap.containsKey(cyObj)) {
+    // currentCyMap.get(cyObj).remove(model);
+    // } else if (cyObj instanceof CyNetwork) {
+    // for (ChimeraResidue residue : model.getResidues()) {
+    // if (currentChimMap.containsKey(residue)) {
+    // for (CyIdentifiable cyObjRes : currentChimMap.get(residue)) {
+    // if (currentCyMap.containsKey(cyObjRes)) {
+    // currentCyMap.get(cyObjRes).remove(residue);
+    // }
+    // }
+    // currentChimMap.remove(residue);
+    // }
+    // }
+    // }
+    // }
+    // currentChimMap.remove(model);
+    // }
+  }
+
+  // public void addStructureNetwork(CyNetwork rin) {
+  // if (rin == null) {
+  // return;
+  // }
+  // ChimeraModel model = null;
+  // // the network is not added to the model in the currentChimMap
+  // List<String> attrsFound =
+  // CytoUtils.getMatchingAttributes(rin.getDefaultNodeTable(),
+  // getCurrentStructureKeys(rin));
+  // for (CyNode node : rin.getNodeList()) {
+  // if (!networkMap.containsKey(node)) {
+  // networkMap.put(node, new HashSet<CyNetwork>());
+  // }
+  // networkMap.get(node).add(rin);
+  // List<String> specsFound =
+  // ChimUtils.getResidueKeys(rin.getDefaultNodeTable(), node,
+  // attrsFound);
+  // for (String residueSpec : specsFound) {
+  // // if (!rin.getRow(node).isSet(ChimUtils.RESIDUE_ATTR)) {
+  // // continue;
+  // // }
+  // // String residueSpec = rin.getRow(node).get(ChimUtils.RESIDUE_ATTR,
+  // String.class);
+  // ChimeraStructuralObject chimObj = ChimUtils.fromAttribute(residueSpec,
+  // chimeraManager);
+  // // chimObj.getChimeraModel().addCyObject(node, rin);
+  // if (chimObj == null || chimObj instanceof ChimeraModel) {
+  // continue;
+  // }
+  // model = chimObj.getChimeraModel();
+  // if (!currentCyMap.containsKey(node)) {
+  // currentCyMap.put(node, new HashSet<ChimeraStructuralObject>());
+  // }
+  // currentCyMap.get(node).add(chimObj);
+  // if (!currentChimMap.containsKey(chimObj)) {
+  // currentChimMap.put(chimObj, new HashSet<CyIdentifiable>());
+  // }
+  // currentChimMap.get(chimObj).add(node);
+  // }
+  // }
+  // if (model != null) {
+  // model.addCyObject(rin, rin);
+  // if (!currentCyMap.containsKey(rin)) {
+  // currentCyMap.put(rin, new HashSet<ChimeraStructuralObject>());
+  // }
+  // currentCyMap.get(rin).add(model);
+  // }
+  // }
+
+  public void exitChimera()
+  {
+    // // exit chimera, invokes clearOnExitChimera
+    // if (mnDialog != null) {
+    // mnDialog.setVisible(false);
+    // mnDialog = null;
+    // }
+    // if (alDialog != null) {
+    // alDialog.setVisible(false);
+    // }
+    chimeraManager.exitChimera();
+  }
+
+  // invoked by ChimeraManager whenever Chimera exits
+  public void clearOnChimeraExit()
+  {
+    // // clear structures
+    // currentCyMap.clear();
+    // currentChimMap.clear();
+    // networkMap.clear();
+    chimSelectionList.clear();
+    // if (chimTable != null) {
+    // ((CyTableManager)
+    // getService(CyTableManager.class)).deleteTable(chimTable.getSUID());
+    // }
+    // if (mnDialog != null) {
+    // if (mnDialog.isVisible()) {
+    // mnDialog.lostChimera();
+    // mnDialog.setVisible(false);
+    // }
+    // mnDialog = null;
+    // if (alDialog != null) {
+    // alDialog.setVisible(false);
+    // }
+    // }
+  }
+
+  // We need to do this in two passes since some parts of a structure might be
+  // selected and some might not. Our selection model (unfortunately) only
+  // tells
+  // us that something has changed, not what...
+  public void updateCytoscapeSelection()
+  {
+    // List<ChimeraStructuralObject> selectedChimObj
+    ignoreCySelection = true;
+    // System.out.println("update Cytoscape selection");
+    // find all possibly selected Cytoscape objects and unselect them
+    // Set<CyNetwork> networks = new HashSet<CyNetwork>();
+    // for (CyIdentifiable currentCyObj : currentCyMap.keySet()) {
+    // if (!networkMap.containsKey(currentCyObj)) {
+    // continue;
+    // }
+    // Set<CyNetwork> currentCyNetworks = networkMap.get(currentCyObj);
+    // if (currentCyNetworks == null || currentCyNetworks.size() == 0) {
+    //
+    // continue;
+    // }
+    // for (CyNetwork network : currentCyNetworks) {
+    // if ((currentCyObj instanceof CyNode && network.containsNode((CyNode)
+    // currentCyObj))
+    // || (currentCyObj instanceof CyEdge && network
+    // .containsEdge((CyEdge) currentCyObj))) {
+    // network.getRow(currentCyObj).set(CyNetwork.SELECTED, false);
+    // networks.add(network);
+    // }
+    // }
+    // }
+    //
+    // // select only those associated with selected Chimera objects
+    // Set<CyIdentifiable> currentCyObjs = new HashSet<CyIdentifiable>();
+    // for (ChimeraStructuralObject chimObj : chimSelectionList) {
+    // ChimeraModel currentSelModel = chimObj.getChimeraModel();
+    // if (currentChimMap.containsKey(currentSelModel)) {
+    // currentCyObjs.addAll(currentChimMap.get(currentSelModel));
+    // }
+    // if (currentChimMap.containsKey(chimObj)) {
+    // currentCyObjs.addAll(currentChimMap.get(chimObj));
+    // }
+    // // System.out.println(chimObj.toSpec() + ": " +
+    // // currentCyObjs.size());
+    // }
+    // for (CyIdentifiable cyObj : currentCyObjs) {
+    // // System.out.println(cyObj.toString());
+    // if (cyObj == null || !networkMap.containsKey(cyObj)) {
+    // continue;
+    // }
+    // Set<CyNetwork> currentCyNetworks = networkMap.get(cyObj);
+    // if (currentCyNetworks == null || currentCyNetworks.size() == 0) {
+    // continue;
+    // }
+    // for (CyNetwork network : currentCyNetworks) {
+    // if ((cyObj instanceof CyNode && network.containsNode((CyNode) cyObj))
+    // || (cyObj instanceof CyEdge && network.containsEdge((CyEdge) cyObj))) {
+    // network.getRow(cyObj).set(CyNetwork.SELECTED, true);
+    // networks.add(network);
+    // }
+    // }
+    // }
+    //
+    // CyNetworkViewManager cyNetViewManager = (CyNetworkViewManager)
+    // getService(CyNetworkViewManager.class);
+    // // Update network views
+    // for (CyNetwork network : networks) {
+    // Collection<CyNetworkView> views =
+    // cyNetViewManager.getNetworkViews(network);
+    // for (CyNetworkView view : views) {
+    // view.updateView();
+    // }
+    // }
+    ignoreCySelection = false;
+  }
+
+  public void cytoscapeSelectionChanged(Map<Long, Boolean> selectedRows)
+  {
+    // if (ignoreCySelection || currentCyMap.size() == 0) {
+    // return;
+    // }
+    // // clearSelectionList();
+    // // System.out.println("cytoscape selection changed");
+    // // iterate over all cy objects with associated models
+    // for (CyIdentifiable cyObj : currentCyMap.keySet()) {
+    // if (cyObj instanceof CyNetwork ||
+    // !selectedRows.containsKey(cyObj.getSUID())) {
+    // continue;
+    // }
+    // for (ChimeraStructuralObject chimObj : currentCyMap.get(cyObj)) {
+    // if (selectedRows.get(cyObj.getSUID())) {
+    // addChimSelection(chimObj);
+    // if (chimObj instanceof ChimeraResidue) {
+    // if (chimObj.getChimeraModel().isSelected()) {
+    // removeChimSelection(chimObj.getChimeraModel());
+    // } else if (chimObj.getChimeraModel()
+    // .getChain(((ChimeraResidue) chimObj).getChainId()).isSelected()) {
+    // removeChimSelection(chimObj.getChimeraModel().getChain(
+    // ((ChimeraResidue) chimObj).getChainId()));
+    // }
+    // }
+    // } else {
+    // removeChimSelection(chimObj);
+    // if (chimObj.hasSelectedChildren() && chimObj instanceof ChimeraModel) {
+    // for (ChimeraResidue residue : ((ChimeraModel) chimObj)
+    // .getSelectedResidues()) {
+    // removeChimSelection(residue);
+    // }
+    // }
+    // }
+    // }
+    // }
+    // System.out.println("selection list: " + getChimSelectionCount());
+    updateChimeraSelection();
+    selectionChanged();
+  }
+
+  // Save models in a HashMap/Set for better performance?
+  public void updateChimeraSelection()
+  {
+    // System.out.println("update Chimera selection");
+    String selSpec = "";
+    for (int i = 0; i < chimSelectionList.size(); i++)
+    {
+      ChimeraStructuralObject nodeInfo = chimSelectionList.get(i);
+      // we do not care about the model anymore
+      selSpec = selSpec.concat(nodeInfo.toSpec());
+      if (i < chimSelectionList.size() - 1)
+        selSpec.concat("|");
+    }
+    if (selSpec.length() > 0)
+    {
+      chimeraManager.select("sel " + selSpec);
+    }
+    else
+    {
+      chimeraManager.select("~sel");
+    }
+  }
+
+  /**
+   * This is called by the selectionListener to let us know that the user has
+   * changed their selection in Chimera. We need to go back to Chimera to find
+   * out what is currently selected and update our list.
+   */
+  public void chimeraSelectionChanged()
+  {
+    // System.out.println("Chimera selection changed");
+    clearSelectionList();
+    // Execute the command to get the list of models with selections
+    Map<Integer, ChimeraModel> selectedModelsMap = chimeraManager
+            .getSelectedModels();
+    // Now get the residue-level data
+    chimeraManager.getSelectedResidues(selectedModelsMap);
+    // Get the selected objects
+    try
+    {
+      for (ChimeraModel selectedModel : selectedModelsMap.values())
+      {
+        int modelNumber = selectedModel.getModelNumber();
+        int subModelNumber = selectedModel.getSubModelNumber();
+        // Get the corresponding "real" model
+        if (chimeraManager.hasChimeraModel(modelNumber, subModelNumber))
+        {
+          ChimeraModel dataModel = chimeraManager.getChimeraModel(
+                  modelNumber, subModelNumber);
+          if (dataModel.getResidueCount() == selectedModel
+                  .getResidueCount()
+                  || dataModel.getModelType() == StructureManager.ModelType.SMILES)
+          {
+            // Select the entire model
+            addChimSelection(dataModel);
+            // dataModel.setSelected(true);
+          }
+          else
+          {
+            for (ChimeraChain selectedChain : selectedModel.getChains())
+            {
+              ChimeraChain dataChain = dataModel.getChain(selectedChain
+                      .getChainId());
+              if (selectedChain.getResidueCount() == dataChain
+                      .getResidueCount())
+              {
+                addChimSelection(dataChain);
+                // dataChain.setSelected(true);
+              }
+              // else {
+              // Need to select individual residues
+              for (ChimeraResidue res : selectedChain.getResidues())
+              {
+                String residueIndex = res.getIndex();
+                ChimeraResidue residue = dataChain.getResidue(residueIndex);
+                if (residue == null)
+                {
+                  continue;
+                }
+                addChimSelection(residue);
+                // residue.setSelected(true);
+              } // resIter.hasNext
+                // }
+            } // chainIter.hasNext()
+          }
+        }
+      } // modelIter.hasNext()
+    } catch (Exception ex)
+    {
+      logger.warn("Could not update selection", ex);
+    }
+    // System.out.println("selection list: " + getChimSelectionCount());
+    // Finally, update the navigator panel
+    selectionChanged();
+    updateCytoscapeSelection();
+  }
+
+  public void selectFunctResidues(Collection<ChimeraModel> models)
+  {
+    clearSelectionList();
+    for (ChimeraModel model : models)
+    {
+      for (ChimeraResidue residue : model.getFuncResidues())
+      {
+        addChimSelection(residue);
+      }
+    }
+    updateChimeraSelection();
+    updateCytoscapeSelection();
+    selectionChanged();
+  }
+
+  // public void selectFunctResidues(CyNode node, CyNetwork network) {
+  // clearSelectionList();
+  // if (currentCyMap.containsKey(node)) {
+  // Set<ChimeraStructuralObject> chimObjects = currentCyMap.get(node);
+  // for (ChimeraStructuralObject obj : chimObjects) {
+  // if (obj instanceof ChimeraModel) {
+  // ChimeraModel model = (ChimeraModel) obj;
+  // for (ChimeraResidue residue : model.getFuncResidues()) {
+  // addChimSelection(residue);
+  // }
+  // }
+  // }
+  // }
+  // updateChimeraSelection();
+  // updateCytoscapeSelection();
+  // selectionChanged();
+  // }
+
+  public List<ChimeraStructuralObject> getChimSelectionList()
+  {
+    return chimSelectionList;
+  }
+
+  public int getChimSelectionCount()
+  {
+    return chimSelectionList.size();
+  }
+
+  /**
+   * Add a selection to the selection list. This is called primarily by the
+   * Model Navigator Dialog to keep the selections in sync
+   * 
+   * @param selectionToAdd
+   *          the selection to add to our list
+   */
+  public void addChimSelection(ChimeraStructuralObject selectionToAdd)
+  {
+    if (selectionToAdd != null
+            && !chimSelectionList.contains(selectionToAdd))
+    {
+      chimSelectionList.add(selectionToAdd);
+      selectionToAdd.setSelected(true);
+    }
+  }
+
+  /**
+   * Remove a selection from the selection list. This is called primarily by the
+   * Model Navigator Dialog to keep the selections in sync
+   * 
+   * @param selectionToRemove
+   *          the selection to remove from our list
+   */
+  public void removeChimSelection(ChimeraStructuralObject selectionToRemove)
+  {
+    if (selectionToRemove != null
+            && chimSelectionList.contains(selectionToRemove))
+    {
+      chimSelectionList.remove(selectionToRemove);
+      selectionToRemove.setSelected(false);
+    }
+  }
+
+  /**
+   * Clear the list of selected objects
+   */
+  public void clearSelectionList()
+  {
+    for (ChimeraStructuralObject cso : chimSelectionList)
+    {
+      if (cso != null)
+        cso.setSelected(false);
+    }
+    chimSelectionList.clear();
+  }
+
+  /**
+   * Associate a new network with the corresponding Chimera objects.
+   * 
+   * @param network
+   */
+
+  /**
+   * Dump and refresh all of our model/chain/residue info
+   */
+  public void updateModels()
+  {
+    // Stop all of our listeners while we try to handle this
+    chimeraManager.stopListening();
+
+    // Get all of the open models
+    List<ChimeraModel> newModelList = chimeraManager.getModelList();
+
+    // Match them up -- assume that the model #'s haven't changed
+    for (ChimeraModel newModel : newModelList)
+    {
+      // Get the color (for our navigator)
+      newModel.setModelColor(chimeraManager.getModelColor(newModel));
+
+      // Get our model info
+      int modelNumber = newModel.getModelNumber();
+      int subModelNumber = newModel.getSubModelNumber();
+
+      // If we already know about this model number, get the Structure,
+      // which tells us about the associated CyNode
+      if (chimeraManager.hasChimeraModel(modelNumber, subModelNumber))
+      {
+        ChimeraModel oldModel = chimeraManager.getChimeraModel(modelNumber,
+                subModelNumber);
+        chimeraManager.removeChimeraModel(modelNumber, subModelNumber);
+        newModel.setModelType(oldModel.getModelType());
+        if (oldModel.getModelType() == ModelType.SMILES)
+        {
+          newModel.setModelName(oldModel.getModelName());
+        }
+        // re-assign associations to cytoscape objects
+        // Map<CyIdentifiable, CyNetwork> oldModelCyObjs =
+        // oldModel.getCyObjects();
+        // for (CyIdentifiable cyObj : oldModelCyObjs.keySet()) {
+        // // add cy objects to the new model
+        // newModel.addCyObject(cyObj, oldModelCyObjs.get(cyObj));
+        // if (currentCyMap.containsKey(cyObj)) {
+        // currentCyMap.get(cyObj).add(newModel);
+        // if (currentCyMap.get(cyObj).contains(oldModel)) {
+        // currentCyMap.get(cyObj).remove(oldModel);
+        // }
+        // }
+        // }
+        // // add new model to the chimera objects map and remove old model
+        // if (currentChimMap.containsKey(oldModel)) {
+        // currentChimMap.put(newModel, currentChimMap.get(oldModel));
+        // currentChimMap.remove(oldModel);
+        // }
+      }
+      // add new model to ChimeraManager
+      chimeraManager.addChimeraModel(modelNumber, subModelNumber, newModel);
+
+      // Get the residue information
+      if (newModel.getModelType() != ModelType.SMILES)
+      {
+        chimeraManager.addResidues(newModel);
+      }
+      // for (CyIdentifiable cyObj : newModel.getCyObjects().keySet()) {
+      // if (cyObj != null && cyObj instanceof CyNetwork) {
+      // addStructureNetwork((CyNetwork) cyObj);
+      // } else if (cyObj != null && cyObj instanceof CyNode) {
+      // newModel.setFuncResidues(ChimUtils.parseFuncRes(
+      // getResidueList(newModel.getCyObjects().get(cyObj), cyObj),
+      // newModel.getModelName()));
+      // }
+      // }
+    }
+
+    // associate all models with any node or network
+    // aTask.associate();
+
+    // Restart all of our listeners
+    chimeraManager.startListening();
+    // Done
+  }
+
+  public void launchModelNavigatorDialog()
+  {
+    // TODO: [Optional] Use haveGUI flag
+    // if (!haveGUI) {
+    // return;
+    // }
+    // if (mnDialog == null) {
+    // CySwingApplication cyApplication = (CySwingApplication)
+    // getService(CySwingApplication.class);
+    // mnDialog = new ModelNavigatorDialog(cyApplication.getJFrame(), this);
+    // mnDialog.pack();
+    // }
+    // mnDialog.setVisible(true);
+  }
+
+  public boolean isMNDialogOpen()
+  {
+    // if (mnDialog != null && mnDialog.isVisible()) {
+    // return true;
+    // }
+    return false;
+  }
+
+  /**
+   * Invoked by the listener thread.
+   */
+  public void modelChanged()
+  {
+    // if (mnDialog != null) {
+    // mnDialog.modelChanged();
+    // }
+  }
+
+  /**
+   * Inform our interface that the selection has changed
+   */
+  public void selectionChanged()
+  {
+    // if (mnDialog != null) {
+    // // System.out.println("update dialog selection");
+    // mnDialog.updateSelection(new
+    // ArrayList<ChimeraStructuralObject>(chimSelectionList));
+    // }
+  }
+
+  public void launchAlignDialog(boolean useChains)
+  {
+    // TODO: [Optional] Use haveGUI flag
+    // Sometimes it does not appear in Windows
+    // if (!haveGUI) {
+    // return;
+    // }
+    // if (alDialog != null) {
+    // alDialog.setVisible(false);
+    // alDialog.dispose();
+    // }
+    // System.out.println("launch align dialog");
+    List<ChimeraStructuralObject> chimObjectList = new ArrayList<ChimeraStructuralObject>();
+    for (ChimeraModel model : chimeraManager.getChimeraModels())
+    {
+      if (useChains)
+      {
+        for (ChimeraChain chain : model.getChains())
+        {
+          chimObjectList.add(chain);
+        }
+      }
+      else
+      {
+        chimObjectList.add(model);
+      }
+    }
+    // Bring up the dialog
+    // CySwingApplication cyApplication = (CySwingApplication)
+    // getService(CySwingApplication.class);
+    // alDialog = new AlignStructuresDialog(cyApplication.getJFrame(), this,
+    // chimObjectList);
+    // alDialog.pack();
+    // alDialog.setVisible(true);
+  }
+
+  public List<String> getAllStructureKeys()
+  {
+    return Arrays.asList(defaultStructureKeys);
+  }
+
+  public List<String> getAllChemStructKeys()
+  {
+    return Arrays.asList(defaultChemStructKeys);
+  }
+
+  public List<String> getAllResidueKeys()
+  {
+    return Arrays.asList(defaultResidueKeys);
+  }
+
+  public List<String> getAllChimeraResidueAttributes()
+  {
+    List<String> attributes = new ArrayList<String>();
+    // attributes.addAll(rinManager.getResAttrs());
+    attributes.addAll(chimeraManager.getAttrList());
+    return attributes;
+  }
+
+  StructureSettings defaultSettings = null;
+
+  // TODO: [Optional] Change priority of Chimera paths
+  public List<String> getChimeraPaths()
+  {
+    List<String> pathList = new ArrayList<String>();
+
+    // if no network is available and the settings have been modified by the
+    // user, check for a
+    // path to chimera
+    if (defaultSettings != null)
+    {
+      String defaultPath = defaultSettings.getChimeraPath();
+      if (defaultPath != null && !defaultPath.equals(""))
+      {
+        pathList.add(defaultPath);
+        return pathList;
+      }
+    }
+
+    // if no network settings, check if the last chimera path is saved in the
+    // session
+    // String lastPath = CytoUtils.getDefaultChimeraPath(registrar,
+    // chimeraPropertyName,
+    // chimeraPathPropertyKey);
+    // if (lastPath != null && !lastPath.equals("")) {
+    // pathList.add(lastPath);
+    // return pathList;
+    // }
+
+    // if no user settings and no last path, get default system's settings
+    String os = System.getProperty("os.name");
+    if (os.startsWith("Linux"))
+    {
+      pathList.add("/usr/local/chimera/bin/chimera");
+      pathList.add("/usr/local/bin/chimera");
+      pathList.add("/usr/bin/chimera");
+    }
+    else if (os.startsWith("Windows"))
+    {
+      pathList.add("\\Program Files\\Chimera\\bin\\chimera");
+      pathList.add("C:\\Program Files\\Chimera\\bin\\chimera.exe");
+    }
+    else if (os.startsWith("Mac"))
+    {
+      pathList.add("/Applications/Chimera.app/Contents/MacOS/chimera");
+    }
+    return pathList;
+  }
+
+  public void setChimeraPathProperty(String path)
+  {
+    // CytoUtils.setDefaultChimeraPath(registrar, chimeraPropertyName,
+    // chimeraPathPropertyKey,
+    // path);
+  }
+
+  public void setStructureSettings(StructureSettings structureSettings)
+  {
+    this.defaultSettings = structureSettings;
+  }
+
+  public String getCurrentChimeraPath(Object object)
+  {
+    if (defaultSettings != null)
+    {
+      return defaultSettings.getChimeraPath();
+    }
+    else
+    {
+      return "";
+    }
+  }
+
+  // public void initChimTable() {
+  // CyTableManager manager = (CyTableManager) getService(CyTableManager.class);
+  // CyTableFactory factory = (CyTableFactory) getService(CyTableFactory.class);
+  // for (CyTable table : manager.getGlobalTables()) {
+  // if (table.getTitle().equals(chimeraOutputTable)) {
+  // manager.deleteTable(table.getSUID());
+  // }
+  // }
+  // chimTable = factory.createTable(chimeraOutputTable, chimeraCommandAttr,
+  // String.class,
+  // false, true);
+  // manager.addTable(chimTable);
+  // if (chimTable.getColumn(chimeraOutputAttr) == null) {
+  // chimTable.createListColumn(chimeraOutputAttr, String.class, false);
+  // }
+  // }
+
+  // public void addChimReply(String command, List<String> reply) {
+  // chimTable.getRow(command).set(chimeraOutputAttr, reply);
+  // }
+
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/StructureSettings.java b/src/ext/edu/ucsf/rbvi/strucviz2/StructureSettings.java
new file mode 100644 (file)
index 0000000..161ab13
--- /dev/null
@@ -0,0 +1,25 @@
+package ext.edu.ucsf.rbvi.strucviz2;
+
+/**
+ * This object maintains the relationship between Chimera objects and Cytoscape objects.
+ */
+public class StructureSettings {
+
+//     @Tunable(description = "Path to UCSF Chimera application", gravity = 4.0)
+       public String chimeraPath = null;
+
+       public StructureSettings(StructureManager manager) {
+               
+               chimeraPath = manager.getCurrentChimeraPath(null);
+
+               // This seems a little strange, but it has to do with the order of tunable interceptor
+               // handling. We need to set these selectors in our structure manager and dynamically
+               // pull the data out as needed....
+               manager.setStructureSettings( this);
+       }
+
+
+       public String getChimeraPath() {
+               return chimeraPath;
+       }
+}
diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java b/src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java
new file mode 100644 (file)
index 0000000..dee027b
--- /dev/null
@@ -0,0 +1,218 @@
+package ext.edu.ucsf.rbvi.strucviz2.port;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ext.edu.ucsf.rbvi.strucviz2.*;
+
+/***************************************************
+ *                 Thread Classes                  *
+ **************************************************/
+
+/**
+ * Reply listener thread
+ */
+public class ListenerThreads extends Thread {
+       private InputStream readChan = null;
+       private BufferedReader lineReader = null;
+       private Process chimera = null;
+       private Map<String, List<String>> replyLog = null;
+       private Logger logger;
+       private StructureManager structureManager = null;
+
+       /**
+        * Create a new listener thread to read the responses from Chimera
+        * 
+        * @param chimera
+        *            a handle to the Chimera Process
+        * @param log
+        *            a handle to a List to post the responses to
+        * @param chimeraObject
+        *            a handle to the Chimera Object
+        */
+       public ListenerThreads(Process chimera, StructureManager structureManager) {
+               this.chimera = chimera;
+               this.structureManager = structureManager;
+               replyLog = new HashMap<String, List<String>>();
+               // Get a line-oriented reader
+               readChan = chimera.getInputStream();
+               lineReader = new BufferedReader(new InputStreamReader(readChan));
+               logger = LoggerFactory.getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class);
+       }
+
+       /**
+        * Start the thread running
+        */
+       public void run() {
+               // System.out.println("ReplyLogListener running");
+               while (true) {
+                       try {
+                               chimeraRead();
+                       } catch (IOException e) {
+                               logger.warn("UCSF Chimera has exited: " + e.getMessage());
+                               return;
+                       }
+               }
+       }
+
+       public List<String> getResponse(String command) {
+               List<String> reply;
+               // System.out.println("getResponse: "+command);
+               while (!replyLog.containsKey(command)) {
+                       try {
+                               Thread.currentThread().sleep(100);
+                       } catch (InterruptedException e) {
+                       }
+               }
+
+               synchronized (replyLog) {
+                       reply = replyLog.get(command);
+                       // System.out.println("getResponse ("+command+") = "+reply);
+                       replyLog.remove(command);
+               }
+               return reply;
+       }
+
+       public void clearResponse(String command) {
+               try {
+                       Thread.currentThread().sleep(100);
+               } catch (InterruptedException e) {
+               }
+               if (replyLog.containsKey(command))
+                       replyLog.remove(command);
+               return;
+       }
+
+       /**
+        * Read input from Chimera
+        * 
+        * @return a List containing the replies from Chimera
+        */
+       private void chimeraRead() throws IOException {
+               if (chimera == null)
+                       return;
+
+               String line = null;
+               while ((line = lineReader.readLine()) != null) {
+                       // System.out.println("From Chimera-->" + line);
+                       if (line.startsWith("CMD")) {
+                               chimeraCommandRead(line.substring(4));
+                       } else if (line.startsWith("ModelChanged: ")) {
+                               (new ModelUpdater()).start();
+                       } else if (line.startsWith("SelectionChanged: ")) {
+                               (new SelectionUpdater()).start();
+                       } else if (line.startsWith("Trajectory residue network info:")) {
+                               (new NetworkUpdater(line)).start();
+                       }
+               }
+               return;
+       }
+
+       private void chimeraCommandRead(String command) throws IOException {
+               // Generally -- looking for:
+               // CMD command
+               // ........
+               // END
+               // We return the text in between
+               List<String> reply = new ArrayList<String>();
+               boolean updateModels = false;
+               boolean updateSelection = false;
+               boolean importNetwork = false;
+               String line = null;
+
+               synchronized (replyLog) {
+                       while ((line = lineReader.readLine()) != null) {
+                               // System.out.println("From Chimera (" + command + ") -->" + line);
+                               if (line.startsWith("CMD")) {
+                                       logger.warn("Got unexpected command from Chimera: " + line);
+
+                               } else if (line.startsWith("END")) {
+                                       break;
+                               }
+                               if (line.startsWith("ModelChanged: ")) {
+                                       updateModels = true;
+                               } else if (line.startsWith("SelectionChanged: ")) {
+                                       updateSelection = true;
+                               } else if (line.length() == 0) {
+                                       continue;
+                               } else if (!line.startsWith("CMD")) {
+                                       reply.add(line);
+                               } else if (line.startsWith("Trajectory residue network info:")) {
+                                       importNetwork = true;
+                               }
+                       }
+                       replyLog.put(command, reply);
+               }
+               if (updateModels)
+                       (new ModelUpdater()).start();
+               if (updateSelection)
+                       (new SelectionUpdater()).start();
+               if (importNetwork) {
+                       (new NetworkUpdater(line)).start();
+               }
+               return;
+       }
+
+       /**
+        * Model updater thread
+        */
+       class ModelUpdater extends Thread {
+
+               public ModelUpdater() {
+               }
+
+               public void run() {
+                       structureManager.updateModels();
+                       structureManager.modelChanged();
+               }
+       }
+
+       /**
+        * Selection updater thread
+        */
+       class SelectionUpdater extends Thread {
+
+               public SelectionUpdater() {
+               }
+
+               public void run() {
+                       try {
+                         logger.info("Responding to chimera selection");
+                         structureManager.chimeraSelectionChanged();
+                       } catch (Exception e) {
+                               logger.warn("Could not update selection", e);
+                       }
+               }
+       }
+
+       /**
+        * Selection updater thread
+        */
+       class NetworkUpdater extends Thread {
+
+               private String line;
+
+               public NetworkUpdater(String line) {
+                       this.line = line;
+               }
+
+               public void run() {
+                       try {
+//                             ((TaskManager<?, ?>) structureManager.getService(TaskManager.class))
+//                                             .execute(new ImportTrajectoryRINTaskFactory(structureManager, line)
+//                                                             .createTaskIterator());
+                       } catch (Exception e) {
+                               logger.warn("Could not import trajectory network", e);
+                       }
+               }
+       }
+}
index 5047699..32c5bca 100644 (file)
@@ -29,6 +29,23 @@ package jalview.api;
 public interface SequenceStructureBinding
 {
 
-  // todo: decide what this really means - we could return a reference to the
-  // alignment/jmol binding, or some other binding.
+  /**
+   * 
+   * @return true if Jalview or the Viewer is still restoring state or loading
+   *         is still going on (see setFinsihedLoadingFromArchive)
+   */
+  void setLoadingFromArchive(boolean loadingFromArchive);
+
+  boolean isLoadingFromArchive();
+
+  /**
+   * modify flag which controls if sequence colouring events are honoured by the
+   * binding. Should be true for normal operation
+   * 
+   * @param finishedLoading
+   */
+  void setFinishedLoadingFromArchive(boolean finishedLoading);
+
+  boolean isLoadingFinished();
+
 }
diff --git a/src/jalview/api/structures/JalviewStructureDisplayI.java b/src/jalview/api/structures/JalviewStructureDisplayI.java
new file mode 100644 (file)
index 0000000..efb60dd
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.api.structures;
+
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
+import jalview.api.SequenceStructureBinding;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.ext.jmol.JalviewJmolBinding;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.UserColourScheme;
+import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureSelectionManager;
+
+public interface JalviewStructureDisplayI
+{
+
+  SequenceStructureBinding getBinding();
+
+  /**
+   * @return true if there is an active GUI handling a structure display
+   */
+  boolean isVisible();
+
+  /**
+   * enable or disable the structure display - note this might just hide or show
+   * a GUI element, but not actually reset the display
+   * 
+   * @param b
+   */
+  void setVisible(boolean b);
+
+  /**
+   * free up any external resources that were used by this display and collect
+   * garbage
+   */
+  void dispose();
+
+  /**
+   * shutdown any structure viewing processes started by this display
+   */
+  void closeViewer();
+  /**
+   * apply a colourscheme to the structures in the viewer
+   * @param colourScheme
+   */
+  void setJalviewColourScheme(ColourSchemeI colourScheme);
+
+}
index a677eff..02ad0cd 100644 (file)
@@ -559,7 +559,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     fontChanged(); // This is so that the scalePanel is resized correctly
 
     validate();
-    sequenceHolderPanel.revalidate();
+    sequenceHolderPanel.validate();
     repaint();
 
   }
index 3d1d0fb..bf82bca 100644 (file)
@@ -34,7 +34,7 @@ import jalview.util.MessageManager;
 
 public class AppletJmol extends EmbmenuFrame implements
 // StructureListener,
-        KeyListener, ActionListener, ItemListener, SequenceStructureBinding
+        KeyListener, ActionListener, ItemListener
 
 {
   Menu fileMenu = new Menu(MessageManager.getString("action.file"));
index 9659cd7..601339a 100755 (executable)
@@ -1112,4 +1112,9 @@ public class AlignmentAnnotation
   {
     this.calcId = calcId;
   }
+
+  public boolean isRNA()
+  {
+    return isrna;
+  }
 }
index c075bf5..cadb205 100644 (file)
@@ -35,6 +35,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.SequenceStructureBindingModel;
 
 import java.awt.Color;
 import java.awt.Container;
@@ -56,26 +57,13 @@ import org.jmol.api.JmolViewer;
 import org.jmol.constant.EnumCallback;
 import org.jmol.popup.JmolPopup;
 
-public abstract class JalviewJmolBinding implements StructureListener,
+public abstract class JalviewJmolBinding extends SequenceStructureBindingModel implements StructureListener,
         JmolStatusListener, SequenceStructureBinding,
         JmolSelectionListener, ComponentListener,
         StructureSelectionManagerProvider
 
 {
   /**
-   * set if Jmol state is being restored from some source - instructs binding
-   * not to apply default display style when structure set is updated for first
-   * time.
-   */
-  private boolean loadingFromArchive = false;
-
-  /**
-   * second flag to indicate if the jmol viewer should ignore sequence colouring
-   * events from the structure manager because the GUI is still setting up
-   */
-  private boolean loadingFinished = true;
-
-  /**
    * state flag used to check if the Jmol viewer's paint method can be called
    */
   private boolean finishedInit = false;
@@ -641,7 +629,7 @@ public abstract class JalviewJmolBinding implements StructureListener,
   public void colourBySequence(boolean showFeatures,
           jalview.api.AlignmentViewPanel alignmentv)
   {
-    if (!colourBySequence || !loadingFinished)
+    if (!colourBySequence || !isLoadingFinished())
       return;
     if (ssm == null)
     {
@@ -1509,32 +1497,6 @@ public abstract class JalviewJmolBinding implements StructureListener,
     showConsole(false);
   }
 
-  public void setLoadingFromArchive(boolean loadingFromArchive)
-  {
-    this.loadingFromArchive = loadingFromArchive;
-  }
-
-  /**
-   * 
-   * @return true if Jmol is still restoring state or loading is still going on
-   *         (see setFinsihedLoadingFromArchive)
-   */
-  public boolean isLoadingFromArchive()
-  {
-    return loadingFromArchive && !loadingFinished;
-  }
-
-  /**
-   * modify flag which controls if sequence colouring events are honoured by the
-   * binding. Should be true for normal operation
-   * 
-   * @param finishedLoading
-   */
-  public void setFinishedLoadingFromArchive(boolean finishedLoading)
-  {
-    loadingFinished = finishedLoading;
-  }
-
   public void setBackgroundColour(java.awt.Color col)
   {
     jmolHistory(false);
index 6553e26..9672268 100644 (file)
@@ -157,7 +157,7 @@ public class PDBFileWithJmol extends AlignFile implements
                 {
                   char newseq[] = new char[len];
                   System.arraycopy(seq, 0, newseq, 0, len);
-                  Annotation asecstr[] = new Annotation[len];
+                  Annotation asecstr[] = new Annotation[len+firstrnum-1];
                   for (int p = 0; p < len; p++)
                   {
                     if (secstr[p] >= 'A' && secstr[p] <= 'z')
@@ -175,14 +175,22 @@ public class PDBFileWithJmol extends AlignFile implements
                   sq.addPDBId(pdbe);
                   pdbe.setProperty(new Hashtable());
                   pdbe.getProperty().put("CHAIN", "" + _lastChainId);
+                  // JAL-1533
+                  // Need to put the number of models for this polymer somewhere for Chimera/others to grab
+                  //                  pdbe.getProperty().put("PDBMODELS", biopoly.)
                   seqs.add(sq);
                   if (!(biopoly.isDna() || biopoly.isRna()))
                   {
                     AlignmentAnnotation ann = new AlignmentAnnotation(
                             "Secondary Structure",
                             "Secondary Structure from PDB File", asecstr);
+                    ann.belowAlignment=true;
+                    ann.visible=true;
+                    ann.autoCalculated=false;
                     ann.setCalcId(getClass().getName());
                     sq.addAlignmentAnnotation(ann);
+                    ann.adjustForAlignment();
+                    ann.validateRangeAndDisplay();
                     annotations.add(ann);
                   }
                 }
diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java
new file mode 100644 (file)
index 0000000..d3c8c09
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.ext.rbvi.chimera;
+
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
+import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.Format;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+/**
+ * Routines for generating Chimera commands for Jalview/Chimera binding
+ * 
+ * @author JimP
+ * 
+ */
+public class ChimeraCommands
+{
+
+  /**
+   * utility to construct the commands to colour chains by the given alignment
+   * for passing to Chimera
+   * 
+   * @returns Object[] { Object[] { <model being coloured>,
+   * 
+   */
+  public static StructureMappingcommandSet[] getColourBySequenceCommand(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
+          AlignmentI alignment)
+  {
+
+    ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
+    Hashtable<String,StringBuffer> colranges=new Hashtable<String,StringBuffer>();
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      float cols[] = new float[4];
+      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+      StringBuffer command = new StringBuffer();
+      StructureMappingcommandSet smc;
+      ArrayList<String> str = new ArrayList<String>();
+
+      if (mapping == null || mapping.length < 1)
+        continue;
+
+      int startPos = -1, lastPos = -1, startModel = -1, lastModel = -1;
+      String startChain = "", lastChain = "";
+      Color lastCol = null;
+      for (int s = 0; s < sequence[pdbfnum].length; s++)
+      {
+        for (int sp, m = 0; m < mapping.length; m++)
+        {
+          if (mapping[m].getSequence() == sequence[pdbfnum][s]
+                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+          {
+            SequenceI asp = alignment.getSequenceAt(sp);
+            for (int r = 0; r < asp.getLength(); r++)
+            {
+              // no mapping to gaps in sequence
+              if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+              {
+                continue;
+              }
+              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
+
+              if (pos < 1 || pos == lastPos)
+                continue;
+
+              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+
+              if (fr != null)
+                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
+              if (lastCol != col || lastPos + 1 != pos
+                      || pdbfnum != lastModel
+                      || !mapping[m].getChain().equals(lastChain))
+              {
+                if (lastCol != null)
+                {
+                  addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain); 
+                }
+                lastCol = null;
+                startPos = pos;
+                startModel = pdbfnum;
+                startChain = mapping[m].getChain();
+              }
+              lastCol = col;
+              lastPos = pos;
+              lastModel = pdbfnum;
+              lastChain = mapping[m].getChain();
+            }
+            // final colour range
+            if (lastCol != null)
+            {
+              addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain); 
+            }
+            break;
+          }
+        }
+      }
+      // Finally, add the command set ready to be returned.
+      StringBuffer coms=new StringBuffer();
+      for (String cr:colranges.keySet())
+      {
+        coms.append("color #"+cr+" "+colranges.get(cr)+";");
+      }
+      cset.add(new StructureMappingcommandSet(ChimeraCommands.class,
+              files[pdbfnum], new String[] { coms.toString() }));
+    }
+    return cset.toArray(new StructureMappingcommandSet[cset.size()]);
+  }
+
+  private static void addColourRange(Hashtable<String, StringBuffer> colranges, Color lastCol, int startModel,
+          int startPos, int lastPos, String lastChain)
+  {
+    
+    String colstring = ((lastCol.getRed()< 16) ? "0":"")+Integer.toHexString(lastCol.getRed())
+            + ((lastCol.getGreen()< 16) ? "0":"")+Integer.toHexString(lastCol.getGreen())
+            + ((lastCol.getBlue()< 16) ? "0":"")+Integer.toHexString(lastCol.getBlue());
+    StringBuffer currange = colranges.get(colstring);
+    if (currange==null)
+    {
+      colranges.put(colstring,currange = new StringBuffer());
+    }
+    if (currange.length()>0)
+    {
+      currange.append("|");
+    }
+    currange.append("#" + startModel + ":" + ((startPos==lastPos) ? startPos : startPos + "-"
+            + lastPos) + "." + lastChain);
+  }
+
+}
diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
new file mode 100644 (file)
index 0000000..fcf89af
--- /dev/null
@@ -0,0 +1,1448 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.ext.rbvi.chimera;
+
+import static org.junit.Assert.assertTrue;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
+import jalview.api.SequenceStructureBinding;
+import jalview.api.StructureSelectionManagerProvider;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.io.AppletFormatAdapter;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueProperties;
+import jalview.structure.StructureListener;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.SequenceStructureBindingModel;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.io.File;
+import java.net.URL;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.jmol.adapter.smarter.SmarterJmolAdapter;
+import org.jmol.api.JmolAppConsoleInterface;
+import org.jmol.api.JmolSelectionListener;
+import org.jmol.api.JmolStatusListener;
+import org.jmol.api.JmolViewer;
+import org.jmol.constant.EnumCallback;
+import org.jmol.popup.JmolPopup;
+
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+import sun.rmi.runtime.Log;
+
+public abstract class JalviewChimeraBinding extends
+        SequenceStructureBindingModel implements StructureListener,
+        SequenceStructureBinding, StructureSelectionManagerProvider
+
+{
+  private StructureManager csm;
+
+  private ChimeraManager viewer;
+
+  /**
+   * set if chimera state is being restored from some source - instructs binding
+   * not to apply default display style when structure set is updated for first
+   * time.
+   */
+  private boolean loadingFromArchive = false;
+
+  /**
+   * second flag to indicate if the jmol viewer should ignore sequence colouring
+   * events from the structure manager because the GUI is still setting up
+   */
+  private boolean loadingFinished = true;
+
+  /**
+   * state flag used to check if the Jmol viewer's paint method can be called
+   */
+  private boolean finishedInit = false;
+
+  public boolean isFinishedInit()
+  {
+    return finishedInit;
+  }
+
+  public void setFinishedInit(boolean finishedInit)
+  {
+    this.finishedInit = finishedInit;
+  }
+
+  boolean allChainsSelected = false;
+
+  /**
+   * when true, try to search the associated datamodel for sequences that are
+   * associated with any unknown structures in the Jmol view.
+   */
+  private boolean associateNewStructs = false;
+
+  Vector atomsPicked = new Vector();
+
+  public Vector chainNames;
+
+  Hashtable chainFile;
+
+  /**
+   * array of target chains for seuqences - tied to pdbentry and sequence[]
+   */
+  protected String[][] chains;
+
+  boolean colourBySequence = true;
+
+  StringBuffer eval = new StringBuffer();
+
+  public String fileLoadingError;
+
+  private Map<String, List<ChimeraModel>> chimmaps = new HashMap<String, List<ChimeraModel>>();
+
+  private List<String> mdlToFile = new ArrayList<String>();
+
+  /**
+   * the default or current model displayed if the model cannot be identified
+   * from the selection message
+   */
+  int frameNo = 0;
+
+  String lastCommand;
+
+  String lastMessage;
+
+  boolean loadedInline;
+
+  public boolean openFile(PDBEntry pe)
+  {
+    String file = pe.getFile();
+    try
+    {
+      List<ChimeraModel> oldList = viewer.getModelList();
+      viewer.openModel(file, ModelType.PDB_MODEL);
+      List<ChimeraModel> newList = viewer.getModelList();
+      if (oldList.size() < newList.size())
+      {
+        while (oldList.size() > 0)
+        {
+          oldList.remove(0);
+          newList.remove(0);
+        }
+        chimmaps.put(file, newList);
+        for (ChimeraModel cm : newList)
+        {
+          while (mdlToFile.size() < 1 + cm.getModelNumber())
+          {
+            mdlToFile.add(new String(""));
+          }
+          mdlToFile.set(cm.getModelNumber(), file);
+        }
+
+        File fl = new File(file);
+        String protocol = AppletFormatAdapter.URL;
+        try
+        {
+          if (fl.exists())
+          {
+            protocol = AppletFormatAdapter.FILE;
+          }
+        } catch (Exception e)
+        {
+        } catch (Error e)
+        {
+        }
+        // Explicitly map to the filename used by Jmol ;
+        // pdbentry[pe].getFile(), protocol);
+
+        if (ssm != null)
+        {
+          ssm.addStructureViewerListener(this);
+          // ssm.addSelectionListener(this);
+          FeatureRenderer fr = getFeatureRenderer(null);
+          if (fr != null)
+          {
+            fr.featuresAdded();
+          }
+          refreshGUI();
+        }
+        return true;
+      }
+    } catch (Exception q)
+    {
+      log("Exception when trying to open model " + file + "\n"
+              + q.toString());
+      q.printStackTrace();
+    }
+    return false;
+  }
+
+  /**
+   * current set of model filenames loaded
+   */
+  String[] modelFileNames = null;
+
+  public PDBEntry[] pdbentry;
+
+  /**
+   * datasource protocol for access to PDBEntrylatest
+   */
+  String protocol = null;
+
+  StringBuffer resetLastRes = new StringBuffer();
+
+  /**
+   * sequences mapped to each pdbentry
+   */
+  public SequenceI[][] sequence;
+
+  public StructureSelectionManager ssm;
+
+  private List<String> lastReply;
+
+  public JalviewChimeraBinding(StructureSelectionManager ssm,
+          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
+          String protocol)
+  {
+    this.ssm = ssm;
+    this.sequence = sequenceIs;
+    this.chains = chains;
+    this.pdbentry = pdbentry;
+    this.protocol = protocol;
+    if (chains == null)
+    {
+      this.chains = new String[pdbentry.length][];
+    }
+    viewer = new ChimeraManager(
+            csm = new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
+    /*
+     * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
+     * "jalviewJmol", ap.av.applet .getDocumentBase(),
+     * ap.av.applet.getCodeBase(), "", this);
+     * 
+     * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
+     */
+  }
+
+  public JalviewChimeraBinding(StructureSelectionManager ssm,
+          ChimeraManager viewer2)
+  {
+    this.ssm = ssm;
+    viewer = viewer2;
+    csm = viewer.getStructureManager();
+  }
+
+  /**
+   * construct a title string for the viewer window based on the data jalview
+   * knows about
+   * 
+   * @return
+   */
+  public String getViewerTitle()
+  {
+    if (sequence == null || pdbentry == null || sequence.length < 1
+            || pdbentry.length < 1 || sequence[0].length < 1)
+    {
+      return ("Jalview Chimera Window");
+    }
+    // TODO: give a more informative title when multiple structures are
+    // displayed.
+    StringBuffer title = new StringBuffer("Chimera view for "
+            + sequence[0][0].getName() + ":" + pdbentry[0].getId());
+
+    if (pdbentry[0].getProperty() != null)
+    {
+      if (pdbentry[0].getProperty().get("method") != null)
+      {
+        title.append(" Method: ");
+        title.append(pdbentry[0].getProperty().get("method"));
+      }
+      if (pdbentry[0].getProperty().get("chains") != null)
+      {
+        title.append(" Chain:");
+        title.append(pdbentry[0].getProperty().get("chains"));
+      }
+    }
+    return title.toString();
+  }
+
+  /**
+   * prepare the view for a given set of models/chains. chainList contains
+   * strings of the form 'pdbfilename:Chaincode'
+   * 
+   * @param chainList
+   *          list of chains to make visible
+   */
+  public void centerViewer(Vector chainList)
+  {
+    StringBuffer cmd = new StringBuffer();
+    String lbl;
+    int mlength, p;
+    for (int i = 0, iSize = chainList.size(); i < iSize; i++)
+    {
+      mlength = 0;
+      lbl = (String) chainList.elementAt(i);
+      do
+      {
+        p = mlength;
+        mlength = lbl.indexOf(":", p);
+      } while (p < mlength && mlength < (lbl.length() - 2));
+      // TODO: lookup each pdb id and recover proper model number for it.
+      cmd.append("#" + getModelNum((String) chainFile.get(lbl)) + "."
+              + lbl.substring(mlength + 1) + " or ");
+    }
+    if (cmd.length() > 0)
+      cmd.setLength(cmd.length() - 4);
+    evalStateCommand("~display #*; ~ribbon #*; ribbon " + cmd + ";focus "
+            + cmd,false);
+  }
+
+  public void closeViewer()
+  {
+    ssm.removeStructureViewerListener(this, this.getPdbFile());
+    // and shut down Chimera
+    viewer.exitChimera();
+    // viewer.evalStringQuiet("zap");
+    // viewer.setJmolStatusListener(null);
+    lastCommand = null;
+    viewer = null;
+    releaseUIResources();
+  }
+
+  /**
+   * called by JalviewJmolbinding after closeViewer is called - release any
+   * resources and references so they can be garbage collected.
+   */
+  protected abstract void releaseUIResources();
+
+  public void colourByChain()
+  {
+    colourBySequence = false;
+    // TODO: colour by chain should colour each chain distinctly across all
+    // visible models
+    // TODO: http://issues.jalview.org/browse/JAL-628
+    evalStateCommand("select *;color chain",false);
+  }
+
+  public void colourByCharge()
+  {
+    colourBySequence = false;
+    evalStateCommand("colour *;color white;select ASP,GLU;color red;"
+            + "select LYS,ARG;color blue;select CYS;color yellow", false);
+  }
+
+  /**
+   * superpose the structures associated with sequences in the alignment
+   * according to their corresponding positions.
+   */
+  public void superposeStructures(AlignmentI alignment)
+  {
+    superposeStructures(alignment, -1, null);
+  }
+
+  /**
+   * superpose the structures associated with sequences in the alignment
+   * according to their corresponding positions. ded)
+   * 
+   * @param refStructure
+   *          - select which pdb file to use as reference (default is -1 - the
+   *          first structure in the alignment)
+   */
+  public void superposeStructures(AlignmentI alignment, int refStructure)
+  {
+    superposeStructures(alignment, refStructure, null);
+  }
+
+  /**
+   * superpose the structures associated with sequences in the alignment
+   * according to their corresponding positions. ded)
+   * 
+   * @param refStructure
+   *          - select which pdb file to use as reference (default is -1 - the
+   *          first structure in the alignment)
+   * @param hiddenCols
+   *          TODO
+   */
+  public void superposeStructures(AlignmentI alignment, int refStructure,
+          ColumnSelection hiddenCols)
+  {
+    superposeStructures(new AlignmentI[]
+    { alignment }, new int[]
+    { refStructure }, new ColumnSelection[]
+    { hiddenCols });
+  }
+
+  public void superposeStructures(AlignmentI[] _alignment,
+          int[] _refStructure, ColumnSelection[] _hiddenCols)
+  {
+    assert (_alignment.length == _refStructure.length && _alignment.length != _hiddenCols.length);
+    StringBuffer allComs = new StringBuffer(); // whole shebang for superposition
+    String[] files = getPdbFile();
+    // check to see if we are still waiting for Jmol files
+    long starttime = System.currentTimeMillis();
+    boolean waiting = true;
+    do
+    {
+      waiting = false;
+      for (String file : files)
+      {
+        try
+        {
+          // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
+          // every possible exception
+          StructureMapping[] sm = ssm.getMapping(file);
+          if (sm == null || sm.length == 0)
+          {
+            waiting = true;
+          }
+        } catch (Exception x)
+        {
+          waiting = true;
+        } catch (Error q)
+        {
+          waiting = true;
+        }
+      }
+      // we wait around for a reasonable time before we give up
+    } while (waiting
+            && System.currentTimeMillis() < (10000 + 1000 * files.length + starttime));
+    if (waiting)
+    {
+      System.err
+              .println("RUNTIME PROBLEM: Jmol seems to be taking a long time to process all the structures.");
+      return;
+    }
+    refreshPdbEntries();
+    StringBuffer selectioncom = new StringBuffer();
+    for (int a = 0; a < _alignment.length; a++)
+    {
+      int refStructure = _refStructure[a];
+      AlignmentI alignment = _alignment[a];
+      ColumnSelection hiddenCols = _hiddenCols[a];
+      if (a > 0
+              && selectioncom.length() > 0
+              && !selectioncom.substring(selectioncom.length() - 1).equals(
+                      " "))
+      {
+        selectioncom.append(" ");
+      }
+      // process this alignment
+      if (refStructure >= files.length)
+      {
+        System.err.println("Invalid reference structure value "
+                + refStructure);
+        refStructure = -1;
+      }
+      if (refStructure < -1)
+      {
+        refStructure = -1;
+      }
+      StringBuffer command = new StringBuffer();
+
+      boolean matched[] = new boolean[alignment.getWidth()];
+      for (int m = 0; m < matched.length; m++)
+      {
+
+        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+      }
+
+      int commonrpositions[][] = new int[files.length][alignment.getWidth()];
+      String isel[] = new String[files.length];
+      // reference structure - all others are superposed in it
+      String[] targetC = new String[files.length];
+      String[] chainNames = new String[files.length];
+      String[] atomS = new String[files.length];
+      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+      {
+        StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+        // RACE CONDITION - getMapping only returns Jmol loaded filenames once
+        // Jmol callback has completed.
+        if (mapping == null || mapping.length < 1)
+        {
+          throw new Error(
+                  "Implementation error - Chimera seems to be still working on getting its data - report at http://issues.jalview.org/browse/JAL-1016");
+        }
+        int lastPos = -1;
+        for (int s = 0; s < sequence[pdbfnum].length; s++)
+        {
+          for (int sp, m = 0; m < mapping.length; m++)
+          {
+            if (mapping[m].getSequence() == sequence[pdbfnum][s]
+                    && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+            {
+              if (refStructure == -1)
+              {
+                refStructure = pdbfnum;
+              }
+              SequenceI asp = alignment.getSequenceAt(sp);
+              for (int r = 0; r < matched.length; r++)
+              {
+                if (!matched[r])
+                {
+                  continue;
+                }
+                matched[r] = false; // assume this is not a good site
+                if (r >= asp.getLength())
+                {
+                  continue;
+                }
+
+                if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+                {
+                  // no mapping to gaps in sequence
+                  continue;
+                }
+                int t = asp.findPosition(r); // sequence position
+                int apos = mapping[m].getAtomNum(t);
+                int pos = mapping[m].getPDBResNum(t);
+
+                if (pos < 1 || pos == lastPos)
+                {
+                  // can't align unmapped sequence
+                  continue;
+                }
+                matched[r] = true; // this is a good ite
+                lastPos = pos;
+                // just record this residue position
+                commonrpositions[pdbfnum][r] = pos;
+              }
+              // create model selection suffix
+              isel[pdbfnum] = "#" + pdbfnum;
+              if (mapping[m].getChain() == null
+                      || mapping[m].getChain().trim().length() == 0)
+              {
+                targetC[pdbfnum] = "";
+              }
+              else
+              {
+                targetC[pdbfnum] = "." + mapping[m].getChain();
+              }
+              chainNames[pdbfnum] = mapping[m].getPdbId()
+                      + targetC[pdbfnum];
+              atomS[pdbfnum] = asp.getRNA()!=null ? "P" : "CA";
+              // move on to next pdb file
+              s = sequence[pdbfnum].length;
+              break;
+            }
+          }
+        }
+      }
+
+      // TODO: consider bailing if nmatched less than 4 because superposition
+      // not
+      // well defined.
+      // TODO: refactor superposable position search (above) from jmol selection
+      // construction (below)
+
+      String[] selcom = new String[files.length];
+      int nmatched = 0;
+      String sep = "";
+      // generate select statements to select regions to superimpose structures
+      {
+        for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+        {
+          String chainCd = targetC[pdbfnum];
+          int lpos = -1;
+          boolean run = false;
+          StringBuffer molsel = new StringBuffer();
+          for (int r = 0; r < matched.length; r++)
+          {
+            if (matched[r])
+            {
+              if (pdbfnum == 0)
+              {
+                nmatched++;
+              }
+              if (lpos != commonrpositions[pdbfnum][r] - 1)
+              {
+                // discontinuity
+                if (lpos != -1)
+                {
+                  molsel.append((run ? "" : ":") + lpos);
+                  molsel.append(chainCd);
+                  molsel.append(",");
+                }
+              }
+              else
+              {
+                // continuous run - and lpos >-1
+                if (!run)
+                {
+                  // at the beginning, so add dash
+                  molsel.append(":" + lpos);
+                  molsel.append("-");
+                }
+                run = true;
+              }
+              lpos = commonrpositions[pdbfnum][r];
+              // molsel.append(lpos);
+            }
+          }
+          // add final selection phrase
+          if (lpos != -1)
+          {
+            molsel.append((run ? "" : ":") + lpos);
+            molsel.append(chainCd);
+            // molsel.append("");
+          }
+          if (molsel.length() > 1)
+          {
+            selcom[pdbfnum] = molsel.toString();
+            selectioncom.append("#" + pdbfnum);
+            selectioncom.append(selcom[pdbfnum]);
+            selectioncom.append(" ");
+            if (pdbfnum < files.length - 1)
+            {
+              selectioncom.append("| ");
+            }
+          }
+          else
+          {
+            selcom[pdbfnum] = null;
+          }
+        }
+      }
+      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+      {
+        if (pdbfnum == refStructure || selcom[pdbfnum] == null
+                || selcom[refStructure] == null)
+        {
+          continue;
+        }
+        if (command.length()>0)
+        {
+          command.append(";");
+        }
+        command.append("match");
+
+        // form the matched pair strings
+        for (int s = 0; s < 2; s++)
+        {
+          command.append(" #"+(s == 0 ? pdbfnum : refStructure)+".1");
+          // note - need to select on first model, otherwise it all goes wrong!
+          command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
+          command.append("@"+atomS[(s == 0 ? pdbfnum : refStructure)]); // match on backbone alpha/polyphosphate
+        }
+      }
+      if (selectioncom.length() > 0)
+      {
+        System.out.println("Select regions:\n" + selectioncom.toString());
+        System.out
+                .println("Superimpose command(s):\n" + command.toString());
+        allComs.append("~display all; chain @CA|P; ribbon "
+                + selectioncom.toString() + ";"+command.toString());
+        // selcom.append("; ribbons; ");
+      }
+    }
+    if (selectioncom.length() > 0)
+    {// finally, mark all regions that were superposed.
+      if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
+      {
+        selectioncom.setLength(selectioncom.length() - 1);
+      }
+      System.out.println("Select regions:\n" + selectioncom.toString());
+      allComs.append("; ~display all; chain @CA|P; ribbon "
+              + selectioncom.toString() + "");
+      // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
+      evalStateCommand(allComs.toString(),false);
+    }
+    
+  }
+
+  private void checkLaunched()
+  {
+    if (!viewer.isChimeraLaunched())
+    {
+      viewer.launchChimera(csm.getChimeraPaths());
+    }
+    if (!viewer.isChimeraLaunched())
+    {
+      log("Failed to launch Chimera!");
+    }
+  }
+
+  public void evalStateCommand(final String command, boolean resp)
+  {
+    viewerCommandHistory(false);
+    checkLaunched();
+    if (lastCommand == null || !lastCommand.equals(command))
+    {
+//      Thread t = new Thread(new Runnable()
+//      {
+//        @Override
+//        public void run()
+//        {
+          lastReply = viewer.sendChimeraCommand(command, resp);
+          if (debug)
+          {
+            log("Response from command ('" + command + "') was:\n"
+                    + lastReply);
+          }
+//        }
+//      });
+      // TODO - use j7/8 thread management
+//      try
+//      {
+//        t.join();
+//      } catch (InterruptedException foo)
+//      {
+//      }
+//      ;
+    }
+    viewerCommandHistory(true);
+    lastCommand = command;
+  }
+
+  /**
+   * colour any structures associated with sequences in the given alignment
+   * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
+   * if colourBySequence is enabled.
+   */
+  public void colourBySequence(boolean showFeatures,
+          jalview.api.AlignmentViewPanel alignmentv)
+  {
+    if (!colourBySequence || !loadingFinished)
+      return;
+    if (ssm == null)
+    {
+      return;
+    }
+    String[] files = getPdbFile();
+
+    SequenceRenderer sr = getSequenceRenderer(alignmentv);
+
+    FeatureRenderer fr = null;
+    if (showFeatures)
+    {
+      fr = getFeatureRenderer(alignmentv);
+    }
+    AlignmentI alignment = alignmentv.getAlignment();
+
+    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : ChimeraCommands
+            .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
+                    alignment))
+      for (String cbyseq : cpdbbyseq.commands)
+      {
+        waitForChimera();
+        evalStateCommand(cbyseq, false);
+        waitForChimera();
+      }
+  }
+
+  private void waitForChimera()
+  {
+    while (viewer.isBusy())
+    {
+      try {
+        Thread.sleep(15);
+      } catch (InterruptedException q)
+      {}
+    }
+  }
+
+  public boolean isColourBySequence()
+  {
+    return colourBySequence;
+  }
+
+  public void setColourBySequence(boolean colourBySequence)
+  {
+    this.colourBySequence = colourBySequence;
+  }
+
+  public void createImage(String file, String type, int quality)
+  {
+    System.out.println("JMOL CREATE IMAGE");
+  }
+
+  public String createImage(String fileName, String type,
+          Object textOrBytes, int quality)
+  {
+    System.out.println("JMOL CREATE IMAGE");
+    return null;
+  }
+
+  public String eval(String strEval)
+  {
+    // System.out.println(strEval);
+    // "# 'eval' is implemented only for the applet.";
+    return null;
+  }
+
+  // End StructureListener
+  // //////////////////////////
+
+  public float[][] functionXY(String functionName, int x, int y)
+  {
+    return null;
+  }
+
+  public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  public Color getColour(int atomIndex, int pdbResNum, String chain,
+          String pdbfile)
+  {
+    if (getModelNum(pdbfile) < 0)
+      return null;
+    log("get model / residue colour attribute unimplemented");
+    return null;
+  }
+
+  /**
+   * returns the current featureRenderer that should be used to colour the
+   * structures
+   * 
+   * @param alignment
+   * 
+   * @return
+   */
+  public abstract FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment);
+
+  /**
+   * instruct the Jalview binding to update the pdbentries vector if necessary
+   * prior to matching the jmol view's contents to the list of structure files
+   * Jalview knows about.
+   */
+  public abstract void refreshPdbEntries();
+
+  private int getModelNum(String modelFileName)
+  {
+    String[] mfn = getPdbFile();
+    if (mfn == null)
+    {
+      return -1;
+    }
+    for (int i = 0; i < mfn.length; i++)
+    {
+      if (mfn[i].equalsIgnoreCase(modelFileName))
+        return i;
+    }
+    return -1;
+  }
+
+  /**
+   * map between index of model filename returned from getPdbFile and the first
+   * index of models from this file in the viewer. Note - this is not trimmed -
+   * use getPdbFile to get number of unique models.
+   */
+  private int _modelFileNameMap[];
+
+  // ////////////////////////////////
+  // /StructureListener
+  public synchronized String[] getPdbFile()
+  {
+    if (viewer == null)
+    {
+      return new String[0];
+    }
+    // if (modelFileNames == null)
+    // {
+    // Collection<ChimeraModel> chimodels = viewer.getChimeraModels();
+    // _modelFileNameMap = new int[chimodels.size()];
+    // int j = 0;
+    // for (ChimeraModel chimodel : chimodels)
+    // {
+    // String mdlName = chimodel.getModelName();
+    // }
+    // modelFileNames = new String[j];
+    // // System.arraycopy(mset, 0, modelFileNames, 0, j);
+    // }
+
+    return chimmaps.keySet().toArray(
+            modelFileNames = new String[chimmaps.size()]);
+  }
+
+  /**
+   * map from string to applet
+   */
+  public Map getRegistryInfo()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  /**
+   * returns the current sequenceRenderer that should be used to colour the
+   * structures
+   * 
+   * @param alignment
+   * 
+   * @return
+   */
+  public abstract SequenceRenderer getSequenceRenderer(
+          AlignmentViewPanel alignment);
+
+  // jmol/ssm only
+  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
+          String pdbfile)
+  {
+    List<ChimeraModel> cms = chimmaps.get(pdbfile);
+    if (cms != null)
+    {
+      int mdlNum = cms.get(0).getModelNumber();
+
+      viewerCommandHistory(false);
+      // viewer.stopListening();
+      if (resetLastRes.length() > 0)
+      {
+        eval.setLength(0);
+        eval.append(resetLastRes.toString() + ";");
+      }
+
+      eval.append("display "); // +modelNum
+
+      resetLastRes.setLength(0);
+      resetLastRes.append("~display ");
+      {
+        eval.append(" #" + (mdlNum));
+        resetLastRes.append(" #" + (mdlNum));
+      }
+      // complete select string
+
+      eval.append(":" + pdbResNum);
+      resetLastRes.append(":" + pdbResNum);
+      if (!chain.equals(" "))
+      {
+        eval.append("." + chain);
+        resetLastRes.append("." + chain);
+      }
+      
+      viewer.sendChimeraCommand(eval.toString(), false);
+      viewerCommandHistory(true);
+      // viewer.startListening();
+    }
+  }
+
+  boolean debug = true;
+
+  private void log(String message)
+  {
+    System.err.println("## Chimera log: " + message);
+  }
+
+  private void viewerCommandHistory(boolean enable)
+  {
+    log("(Not yet implemented) History "
+            + ((debug || enable) ? "on" : "off"));
+  }
+
+  public void loadInline(String string)
+  {
+    loadedInline = true;
+    // TODO: re JAL-623
+    // viewer.loadInline(strModel, isAppend);
+    // could do this:
+    // construct fake fullPathName and fileName so we can identify the file
+    // later.
+    // Then, construct pass a reader for the string to Jmol.
+    // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
+    // fileName, null, reader, false, null, null, 0);
+    // viewer.openStringInline(string);
+    log("cannot load inline in Chimera, yet");
+  }
+
+  public void mouseOverStructure(int atomIndex, String strInfo)
+  {
+    // function to parse a mouseOver event from Chimera
+    //
+    int pdbResNum;
+    int alocsep = strInfo.indexOf("^");
+    int mdlSep = strInfo.indexOf("/");
+    int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
+
+    if (chainSeparator == -1)
+    {
+      chainSeparator = strInfo.indexOf(".");
+      if (mdlSep > -1 && mdlSep < chainSeparator)
+      {
+        chainSeparator1 = chainSeparator;
+        chainSeparator = mdlSep;
+      }
+    }
+    // handle insertion codes
+    if (alocsep != -1)
+    {
+      pdbResNum = Integer.parseInt(strInfo.substring(
+              strInfo.indexOf("]") + 1, alocsep));
+
+    }
+    else
+    {
+      pdbResNum = Integer.parseInt(strInfo.substring(
+              strInfo.indexOf("]") + 1, chainSeparator));
+    }
+    String chainId;
+
+    if (strInfo.indexOf(":") > -1)
+      chainId = strInfo.substring(strInfo.indexOf(":") + 1,
+              strInfo.indexOf("."));
+    else
+    {
+      chainId = " ";
+    }
+
+    String pdbfilename = modelFileNames[frameNo]; // default is first or current
+    // model
+    if (mdlSep > -1)
+    {
+      if (chainSeparator1 == -1)
+      {
+        chainSeparator1 = strInfo.indexOf(".", mdlSep);
+      }
+      String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
+              chainSeparator1) : strInfo.substring(mdlSep + 1);
+      try
+      {
+        // recover PDB filename for the model hovered over.
+        int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
+                .intValue() - 1;
+        while (mnumber < _modelFileNameMap[_mp])
+        {
+          _mp--;
+        }
+        pdbfilename = modelFileNames[_mp];
+        if (pdbfilename == null)
+        {
+          // pdbfilename = new File(viewer.getModelFileName(mnumber))
+          // .getAbsolutePath();
+        }
+
+      } catch (Exception e)
+      {
+      }
+      ;
+    }
+    if (lastMessage == null || !lastMessage.equals(strInfo))
+      ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
+
+    lastMessage = strInfo;
+  }
+
+  public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
+  {
+    /**
+     * this implements the toggle label behaviour copied from the original
+     * structure viewer, MCView
+     */
+    if (strData != null)
+    {
+      System.err.println("Ignoring additional pick data string " + strData);
+    }
+    // rewrite these selections for chimera (DNA, RNA and protein)
+    int chainSeparator = strInfo.indexOf(":");
+    int p = 0;
+    if (chainSeparator == -1)
+      chainSeparator = strInfo.indexOf(".");
+
+    String picked = strInfo.substring(strInfo.indexOf("]") + 1,
+            chainSeparator);
+    String mdlString = "";
+    if ((p = strInfo.indexOf(":")) > -1)
+      picked += strInfo.substring(p + 1, strInfo.indexOf("."));
+
+    if ((p = strInfo.indexOf("/")) > -1)
+    {
+      mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
+    }
+    picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
+            + mdlString + "))";
+    viewerCommandHistory(false);
+
+    if (!atomsPicked.contains(picked))
+    {
+      viewer.select(picked);
+      atomsPicked.addElement(picked);
+    }
+    else
+    {
+      viewer.select("not " + picked);
+      atomsPicked.removeElement(picked);
+    }
+    viewerCommandHistory(true);
+    // TODO: in application this happens
+    //
+    // if (scriptWindow != null)
+    // {
+    // scriptWindow.sendConsoleMessage(strInfo);
+    // scriptWindow.sendConsoleMessage("\n");
+    // }
+
+  }
+
+  // incremented every time a load notification is successfully handled -
+  // lightweight mechanism for other threads to detect when they can start
+  // referrring to new structures.
+  private long loadNotifiesHandled = 0;
+
+  public long getLoadNotifiesHandled()
+  {
+    return loadNotifiesHandled;
+  }
+
+  public void notifyFileLoaded(String fullPathName, String fileName2,
+          String modelName, String errorMsg, int modelParts)
+  {
+    if (errorMsg != null)
+    {
+      fileLoadingError = errorMsg;
+      refreshGUI();
+      return;
+    }
+    // TODO: deal sensibly with models loaded inLine:
+    // modelName will be null, as will fullPathName.
+
+    // the rest of this routine ignores the arguments, and simply interrogates
+    // the Jmol view to find out what structures it contains, and adds them to
+    // the structure selection manager.
+    fileLoadingError = null;
+    String[] oldmodels = modelFileNames;
+    modelFileNames = null;
+    chainNames = new Vector();
+    chainFile = new Hashtable();
+    boolean notifyLoaded = false;
+    String[] modelfilenames = getPdbFile();
+    // first check if we've lost any structures
+    if (oldmodels != null && oldmodels.length > 0)
+    {
+      int oldm = 0;
+      for (int i = 0; i < oldmodels.length; i++)
+      {
+        for (int n = 0; n < modelfilenames.length; n++)
+        {
+          if (modelfilenames[n] == oldmodels[i])
+          {
+            oldmodels[i] = null;
+            break;
+          }
+        }
+        if (oldmodels[i] != null)
+        {
+          oldm++;
+        }
+      }
+      if (oldm > 0)
+      {
+        String[] oldmfn = new String[oldm];
+        oldm = 0;
+        for (int i = 0; i < oldmodels.length; i++)
+        {
+          if (oldmodels[i] != null)
+          {
+            oldmfn[oldm++] = oldmodels[i];
+          }
+        }
+        // deregister the Jmol instance for these structures - we'll add
+        // ourselves again at the end for the current structure set.
+        ssm.removeStructureViewerListener(this, oldmfn);
+      }
+    }
+
+    // register ourselves as a listener and notify the gui that it needs to
+    // update itself.
+    ssm.addStructureViewerListener(this);
+
+    if (notifyLoaded)
+    {
+      FeatureRenderer fr = getFeatureRenderer(null);
+      if (fr != null)
+      {
+        fr.featuresAdded();
+      }
+      refreshGUI();
+      loadNotifiesHandled++;
+    }
+    setLoadingFromArchive(false);
+  }
+
+  public void setJalviewColourScheme(ColourSchemeI cs)
+  {
+    colourBySequence = false;
+
+    if (cs == null)
+      return;
+
+    String res;
+    int index;
+    Color col;
+    viewerCommandHistory(false);
+    // TODO: Switch between nucleotide or aa selection expressions
+    Enumeration en = ResidueProperties.aa3Hash.keys();
+    StringBuffer command = new StringBuffer("select *;color white;");
+    while (en.hasMoreElements())
+    {
+      res = en.nextElement().toString();
+      index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
+      if (index > 20)
+        continue;
+
+      col = cs.findColour(ResidueProperties.aa[index].charAt(0));
+      // TODO: need colour string function and res selection here
+      command.append("select " + res + ";color[" + col.getRed() + ","
+              + col.getGreen() + "," + col.getBlue() + "];");
+    }
+
+    evalStateCommand(command.toString(),false);
+    viewerCommandHistory(true);
+  }
+
+  public void showHelp()
+  {
+    // chimera help
+    showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
+  }
+
+  /**
+   * open the URL somehow
+   * 
+   * @param target
+   */
+  public abstract void showUrl(String url, String target);
+
+  /**
+   * called when the binding thinks the UI needs to be refreshed after a Jmol
+   * state change. this could be because structures were loaded, or because an
+   * error has occured.
+   */
+  public abstract void refreshGUI();
+
+  public void componentResized(ComponentEvent e)
+  {
+
+  }
+
+  public void componentMoved(ComponentEvent e)
+  {
+
+  }
+
+  public void componentShown(ComponentEvent e)
+  {
+  }
+
+  public void componentHidden(ComponentEvent e)
+  {
+  }
+
+  public void setLoadingFromArchive(boolean loadingFromArchive)
+  {
+    this.loadingFromArchive = loadingFromArchive;
+  }
+
+  /**
+   * 
+   * @return true if Jmol is still restoring state or loading is still going on
+   *         (see setFinsihedLoadingFromArchive)
+   */
+  public boolean isLoadingFromArchive()
+  {
+    return loadingFromArchive && !loadingFinished;
+  }
+
+  /**
+   * modify flag which controls if sequence colouring events are honoured by the
+   * binding. Should be true for normal operation
+   * 
+   * @param finishedLoading
+   */
+  public void setFinishedLoadingFromArchive(boolean finishedLoading)
+  {
+    loadingFinished = finishedLoading;
+  }
+
+  public void setBackgroundColour(java.awt.Color col)
+  {
+    viewerCommandHistory(false);
+    // todo set background colour
+    viewer.sendChimeraCommand(
+            "background [" + col.getRed() + "," + col.getGreen() + ","
+                    + col.getBlue() + "];", false);
+    viewerCommandHistory(true);
+  }
+
+  /**
+   * add structures and any known sequence associations
+   * 
+   * @returns the pdb entries added to the current set.
+   */
+  public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
+          SequenceI[][] seq, String[][] chns)
+  {
+    int pe = -1;
+    Vector v = new Vector();
+    Vector rtn = new Vector();
+    for (int i = 0; i < pdbentry.length; i++)
+    {
+      v.addElement(pdbentry[i]);
+    }
+    for (int i = 0; i < pdbe.length; i++)
+    {
+      int r = v.indexOf(pdbe[i]);
+      if (r == -1 || r >= pdbentry.length)
+      {
+        rtn.addElement(new int[]
+        { v.size(), i });
+        v.addElement(pdbe[i]);
+      }
+      else
+      {
+        // just make sure the sequence/chain entries are all up to date
+        addSequenceAndChain(r, seq[i], chns[i]);
+      }
+    }
+    pdbe = new PDBEntry[v.size()];
+    v.copyInto(pdbe);
+    pdbentry = pdbe;
+    if (rtn.size() > 0)
+    {
+      // expand the tied seuqence[] and string[] arrays
+      SequenceI[][] sqs = new SequenceI[pdbentry.length][];
+      String[][] sch = new String[pdbentry.length][];
+      System.arraycopy(sequence, 0, sqs, 0, sequence.length);
+      System.arraycopy(chains, 0, sch, 0, this.chains.length);
+      sequence = sqs;
+      chains = sch;
+      pdbe = new PDBEntry[rtn.size()];
+      for (int r = 0; r < pdbe.length; r++)
+      {
+        int[] stri = ((int[]) rtn.elementAt(r));
+        // record the pdb file as a new addition
+        pdbe[r] = pdbentry[stri[0]];
+        // and add the new sequence/chain entries
+        addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
+      }
+    }
+    else
+    {
+      pdbe = null;
+    }
+    return pdbe;
+  }
+
+  public void addSequence(int pe, SequenceI[] seq)
+  {
+    // add sequences to the pe'th pdbentry's seuqence set.
+    addSequenceAndChain(pe, seq, null);
+  }
+
+  private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
+  {
+    if (pe < 0 || pe >= pdbentry.length)
+    {
+      throw new Error(
+              "Implementation error - no corresponding pdbentry (for index "
+                      + pe + ") to add sequences mappings to");
+    }
+    final String nullChain = "TheNullChain";
+    Vector s = new Vector();
+    Vector c = new Vector();
+    if (chains == null)
+    {
+      chains = new String[pdbentry.length][];
+    }
+    if (sequence[pe] != null)
+    {
+      for (int i = 0; i < sequence[pe].length; i++)
+      {
+        s.addElement(sequence[pe][i]);
+        if (chains[pe] != null)
+        {
+          if (i < chains[pe].length)
+          {
+            c.addElement(chains[pe][i]);
+          }
+          else
+          {
+            c.addElement(nullChain);
+          }
+        }
+        else
+        {
+          if (tchain != null && tchain.length > 0)
+          {
+            c.addElement(nullChain);
+          }
+        }
+      }
+    }
+    for (int i = 0; i < seq.length; i++)
+    {
+      if (!s.contains(seq[i]))
+      {
+        s.addElement(seq[i]);
+        if (tchain != null && i < tchain.length)
+        {
+          c.addElement(tchain[i] == null ? nullChain : tchain[i]);
+        }
+      }
+    }
+    SequenceI[] tmp = new SequenceI[s.size()];
+    s.copyInto(tmp);
+    sequence[pe] = tmp;
+    if (c.size() > 0)
+    {
+      String[] tch = new String[c.size()];
+      c.copyInto(tch);
+      for (int i = 0; i < tch.length; i++)
+      {
+        if (tch[i] == nullChain)
+        {
+          tch[i] = null;
+        }
+      }
+      chains[pe] = tch;
+    }
+    else
+    {
+      chains[pe] = null;
+    }
+  }
+
+  /**
+   * 
+   * @param pdbfile
+   * @return text report of alignment between pdbfile and any associated
+   *         alignment sequences
+   */
+  public String printMapping(String pdbfile)
+  {
+    return ssm.printMapping(pdbfile);
+  }
+
+}
index 9e9434c..6a32f30 100644 (file)
@@ -25,8 +25,9 @@ import java.awt.event.*;
 import jalview.api.SequenceStructureBinding;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.structure.*;
+import jalview.structures.models.SequenceStructureBindingModel;
 
-public abstract class JalviewVarnaBinding implements StructureListener,
+public abstract class JalviewVarnaBinding extends SequenceStructureBindingModel implements StructureListener,
         SequenceStructureBinding, ComponentListener,
         StructureSelectionManagerProvider
 
index bf9888c..e6b7af4 100644 (file)
@@ -263,15 +263,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           int width, int height, String sequenceSetId, String viewId)
   {
     setSize(width, height);
-    viewport = new AlignViewport(al, hiddenColumns, sequenceSetId, viewId);
-
-    alignPanel = new AlignmentPanel(this, viewport);
 
     if (al.getDataset() == null)
     {
       al.setDataset(null);
     }
 
+    viewport = new AlignViewport(al, hiddenColumns, sequenceSetId, viewId);
+
+    alignPanel = new AlignmentPanel(this, viewport);
+
+
     addAlignmentPanel(alignPanel, true);
     init();
   }
index 6eb6ac3..bcdc2de 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import java.util.*;
 import java.awt.*;
+
 import javax.swing.*;
 import javax.swing.event.*;
 
@@ -30,17 +31,19 @@ import java.io.*;
 
 import jalview.jbgui.GStructureViewer;
 import jalview.api.SequenceStructureBinding;
+import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.*;
 import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 import jalview.datamodel.PDBEntry;
+import jalview.ext.jmol.JalviewJmolBinding;
 import jalview.io.*;
 import jalview.schemes.*;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 public class AppJmol extends GStructureViewer implements Runnable,
-        SequenceStructureBinding, ViewSetProvider
+        ViewSetProvider, JalviewStructureDisplayI
 
 {
   AppJmolBinding jmb;
@@ -688,7 +691,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
     jmb.centerViewer(toshow);
   }
 
-  void closeViewer()
+  public void closeViewer()
   {
     jmb.closeViewer();
     ap = null;
@@ -1356,4 +1359,9 @@ public class AppJmol extends GStructureViewer implements Runnable,
     return !jmb.isColourBySequence();
   }
 
+  public JalviewJmolBinding getBinding()
+  {
+    return jmb;
+  }
+
 }
diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java
new file mode 100644 (file)
index 0000000..10082eb
--- /dev/null
@@ -0,0 +1,1229 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import jalview.api.SequenceStructureBinding;
+import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Cache;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.jbgui.GStructureViewer;
+import jalview.schemes.BuriedColourScheme;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.HelixColourScheme;
+import jalview.schemes.HydrophobicColourScheme;
+import jalview.schemes.PurinePyrimidineColourScheme;
+import jalview.schemes.StrandColourScheme;
+import jalview.schemes.TaylorColourScheme;
+import jalview.schemes.TurnColourScheme;
+import jalview.schemes.ZappoColourScheme;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JColorChooser;
+import javax.swing.JInternalFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+/**
+ * GUI elements for handlnig an external chimera display
+ * 
+ * @author jprocter
+ *
+ */
+public class ChimeraViewFrame extends GStructureViewer implements Runnable,
+        ViewSetProvider, JalviewStructureDisplayI
+
+{
+  JalviewChimeraBindingModel jmb;
+
+  AlignmentPanel ap;
+
+  Vector atomsPicked = new Vector();
+
+  private boolean addingStructures = false;
+
+  ViewSelectionMenu seqColourBy;
+
+  /**
+   * 
+   * @param files
+   * @param ids
+   * @param seqs
+   * @param ap
+   * @param usetoColour
+   *          - add the alignment panel to the list used for colouring these
+   *          structures
+   * @param useToAlign
+   *          - add the alignment panel to the list used for aligning these
+   *          structures
+   * @param leaveColouringToJmol
+   *          - do not update the colours from any other source. Jmol is
+   *          handling them
+   * @param loadStatus
+   * @param bounds
+   * @param viewid
+   * 
+   *          public ChimeraViewFrame(String[] files, String[] ids,
+   *          SequenceI[][] seqs, AlignmentPanel ap, boolean usetoColour,
+   *          boolean useToAlign, boolean leaveColouringToJmol, String
+   *          loadStatus, Rectangle bounds, String viewid) { PDBEntry[]
+   *          pdbentrys = new PDBEntry[files.length]; for (int i = 0; i <
+   *          pdbentrys.length; i++) { PDBEntry pdbentry = new PDBEntry();
+   *          pdbentry.setFile(files[i]); pdbentry.setId(ids[i]); pdbentrys[i] =
+   *          pdbentry; } // / TODO: check if protocol is needed to be set, and
+   *          if chains are // autodiscovered. jmb = new
+   *          JalviewChimeraBindingModel(this,
+   *          ap.getStructureSelectionManager(), pdbentrys, seqs, null, null);
+   * 
+   *          jmb.setLoadingFromArchive(true); addAlignmentPanel(ap); if
+   *          (useToAlign) { useAlignmentPanelForSuperposition(ap); } if
+   *          (leaveColouringToJmol || !usetoColour) {
+   *          jmb.setColourBySequence(false); seqColour.setSelected(false);
+   *          jmolColour.setSelected(true); } if (usetoColour) {
+   *          useAlignmentPanelForColourbyseq(ap);
+   *          jmb.setColourBySequence(true); seqColour.setSelected(true);
+   *          jmolColour.setSelected(false); } this.setBounds(bounds);
+   *          initMenus(); viewId = viewid; //
+   *          jalview.gui.Desktop.addInternalFrame(this, "Loading File", //
+   *          bounds.width,bounds.height);
+   * 
+   *          this.addInternalFrameListener(new InternalFrameAdapter() { public
+   *          void internalFrameClosing(InternalFrameEvent internalFrameEvent) {
+   *          closeViewer(); } }); initJmol(loadStatus); // pdbentry, seq,
+   *          JBPCHECK!
+   * 
+   *          }
+   */
+  private void initMenus()
+  {
+    seqColour.setSelected(jmb.isColourBySequence());
+    jmolColour.setSelected(!jmb.isColourBySequence());
+    if (_colourwith == null)
+    {
+      _colourwith = new Vector<AlignmentPanel>();
+    }
+    if (_alignwith == null)
+    {
+      _alignwith = new Vector<AlignmentPanel>();
+    }
+
+    seqColourBy = new ViewSelectionMenu("Colour by ..", this, _colourwith,
+            new ItemListener()
+            {
+
+              @Override
+              public void itemStateChanged(ItemEvent e)
+              {
+                if (!seqColour.isSelected())
+                {
+                  seqColour.doClick();
+                }
+                else
+                {
+                  // update the jmol display now.
+                  seqColour_actionPerformed(null);
+                }
+              }
+            });
+    viewMenu.add(seqColourBy);
+    final ItemListener handler;
+    JMenu alpanels = new ViewSelectionMenu("Superpose with ..", this,
+            _alignwith, handler = new ItemListener()
+            {
+
+              @Override
+              public void itemStateChanged(ItemEvent e)
+              {
+                alignStructs.setEnabled(_alignwith.size() > 0);
+                alignStructs.setToolTipText(MessageManager
+                        .formatMessage(
+                                "label.align_structures_using_linked_alignment_views",
+                                new String[]
+                                { new Integer(_alignwith.size()).toString() }));
+              }
+            });
+    handler.itemStateChanged(null);
+    jmolActionMenu.add(alpanels);
+    jmolActionMenu.addMenuListener(new MenuListener()
+    {
+
+      @Override
+      public void menuSelected(MenuEvent e)
+      {
+        handler.itemStateChanged(null);
+      }
+
+      @Override
+      public void menuDeselected(MenuEvent e)
+      {
+        // TODO Auto-generated method stub
+
+      }
+
+      @Override
+      public void menuCanceled(MenuEvent e)
+      {
+        // TODO Auto-generated method stub
+
+      }
+    });
+  }
+
+  IProgressIndicator progressBar = null;
+
+  /**
+   * add a single PDB structure to a new or existing Jmol view
+   * 
+   * @param pdbentry
+   * @param seq
+   * @param chains
+   * @param ap
+   */
+  public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
+          String[] chains, final AlignmentPanel ap)
+  {
+    progressBar = ap.alignFrame;
+    // ////////////////////////////////
+    // Is the pdb file already loaded?
+    String alreadyMapped = ap.getStructureSelectionManager()
+            .alreadyMappedToFile(pdbentry.getId());
+
+    if (alreadyMapped != null)
+    {
+      int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
+              MessageManager.formatMessage(
+                      "label.pdb_entry_is_already_displayed", new String[]
+                      { pdbentry.getId() }), MessageManager.formatMessage(
+                      "label.map_sequences_to_visible_window", new String[]
+                      { pdbentry.getId() }), JOptionPane.YES_NO_OPTION);
+
+      if (option == JOptionPane.YES_OPTION)
+      {
+        // TODO : Fix multiple seq to one chain issue here.
+        ap.getStructureSelectionManager().setMapping(seq, chains,
+                alreadyMapped, AppletFormatAdapter.FILE);
+        if (ap.seqPanel.seqCanvas.fr != null)
+        {
+          ap.seqPanel.seqCanvas.fr.featuresAdded();
+          ap.paintAlignment(true);
+        }
+
+        // Now this AppJmol is mapped to new sequences. We must add them to
+        // the exisiting array
+        JInternalFrame[] frames = Desktop.instance.getAllFrames();
+
+        for (int i = 0; i < frames.length; i++)
+        {
+          if (frames[i] instanceof ChimeraViewFrame)
+          {
+            final ChimeraViewFrame topJmol = ((ChimeraViewFrame) frames[i]);
+            // JBPNOTE: this looks like a binding routine, rather than a gui
+            // routine
+            for (int pe = 0; pe < topJmol.jmb.pdbentry.length; pe++)
+            {
+              if (topJmol.jmb.pdbentry[pe].getFile().equals(alreadyMapped))
+              {
+                topJmol.jmb.addSequence(pe, seq);
+                topJmol.addAlignmentPanel(ap);
+                // add it to the set used for colouring
+                topJmol.useAlignmentPanelForColourbyseq(ap);
+                topJmol.buildChimeraActionMenu();
+                ap.getStructureSelectionManager()
+                        .sequenceColoursChanged(ap);
+                break;
+              }
+            }
+          }
+        }
+
+        return;
+      }
+    }
+    // /////////////////////////////////
+    // Check if there are other Jmol views involving this alignment
+    // and prompt user about adding this molecule to one of them
+    Vector existingViews = getJmolsFor(ap);
+    if (existingViews.size() > 0)
+    {
+      Enumeration jm = existingViews.elements();
+      while (jm.hasMoreElements())
+      {
+        ChimeraViewFrame topJmol = (ChimeraViewFrame) jm.nextElement();
+        // TODO: highlight topJmol in view somehow
+        int option = JOptionPane
+                .showInternalConfirmDialog(
+                        Desktop.desktop,
+                        MessageManager.formatMessage(
+                                "label.add_pdbentry_to_view", new String[]
+                                { pdbentry.getId(), topJmol.getTitle() }),
+                        MessageManager
+                                .getString("label.align_to_existing_structure_view"),
+                        JOptionPane.YES_NO_OPTION);
+        if (option == JOptionPane.YES_OPTION)
+        {
+          topJmol.useAlignmentPanelForSuperposition(ap);
+          topJmol.addStructure(pdbentry, seq, chains, true, ap.alignFrame);
+          return;
+        }
+      }
+    }
+    // /////////////////////////////////
+    openNewJmol(ap, new PDBEntry[]
+    { pdbentry }, new SequenceI[][]
+    { seq });
+  }
+
+  private void openNewJmol(AlignmentPanel ap, PDBEntry[] pdbentrys,
+          SequenceI[][] seqs)
+  {
+    progressBar = ap.alignFrame;
+    jmb = new JalviewChimeraBindingModel(this,
+            ap.getStructureSelectionManager(), pdbentrys, seqs, null, null);
+    addAlignmentPanel(ap);
+    useAlignmentPanelForColourbyseq(ap);
+    if (pdbentrys.length > 1)
+    {
+      alignAddedStructures = true;
+      useAlignmentPanelForSuperposition(ap);
+    }
+    jmb.setColourBySequence(true);
+    setSize(400, 400); // probably should be a configurable/dynamic default here
+    initMenus();
+    worker = null;
+    {
+      addingStructures = false;
+      worker = new Thread(this);
+      worker.start();
+    }
+    this.addInternalFrameListener(new InternalFrameAdapter()
+    {
+      public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
+      {
+        closeViewer();
+      }
+    });
+
+  }
+
+  /**
+   * create a new Jmol containing several structures superimposed using the
+   * given alignPanel.
+   * 
+   * @param ap
+   * @param pe
+   * @param seqs
+   */
+  public ChimeraViewFrame(AlignmentPanel ap, PDBEntry[] pe,
+          SequenceI[][] seqs)
+  {
+    openNewJmol(ap, pe, seqs);
+  }
+
+  /**
+   * list of sequenceSet ids associated with the view
+   */
+  ArrayList<String> _aps = new ArrayList();
+
+  public AlignmentPanel[] getAllAlignmentPanels()
+  {
+    AlignmentPanel[] t, list = new AlignmentPanel[0];
+    for (String setid : _aps)
+    {
+      AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
+      if (panels != null)
+      {
+        t = new AlignmentPanel[list.length + panels.length];
+        System.arraycopy(list, 0, t, 0, list.length);
+        System.arraycopy(panels, 0, t, list.length, panels.length);
+        list = t;
+      }
+    }
+
+    return list;
+  }
+
+  /**
+   * list of alignment panels to use for superposition
+   */
+  Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
+
+  /**
+   * list of alignment panels that are used for colouring structures by aligned
+   * sequences
+   */
+  Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
+
+  /**
+   * set the primary alignmentPanel reference and add another alignPanel to the
+   * list of ones to use for colouring and aligning
+   * 
+   * @param nap
+   */
+  public void addAlignmentPanel(AlignmentPanel nap)
+  {
+    if (ap == null)
+    {
+      ap = nap;
+    }
+    if (!_aps.contains(nap.av.getSequenceSetId()))
+    {
+      _aps.add(nap.av.getSequenceSetId());
+    }
+  }
+
+  /**
+   * remove any references held to the given alignment panel
+   * 
+   * @param nap
+   */
+  public void removeAlignmentPanel(AlignmentPanel nap)
+  {
+    try
+    {
+      _alignwith.remove(nap);
+      _colourwith.remove(nap);
+      if (ap == nap)
+      {
+        ap = null;
+        for (AlignmentPanel aps : getAllAlignmentPanels())
+        {
+          if (aps != nap)
+          {
+            ap = aps;
+            break;
+          }
+        }
+      }
+    } catch (Exception ex)
+    {
+    }
+    if (ap != null)
+    {
+      buildChimeraActionMenu();
+    }
+  }
+
+  public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
+  {
+    addAlignmentPanel(nap);
+    if (!_alignwith.contains(nap))
+    {
+      _alignwith.add(nap);
+    }
+  }
+
+  public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
+  {
+    if (_alignwith.contains(nap))
+    {
+      _alignwith.remove(nap);
+    }
+  }
+
+  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
+          boolean enableColourBySeq)
+  {
+    useAlignmentPanelForColourbyseq(nap);
+    jmb.setColourBySequence(enableColourBySeq);
+    seqColour.setSelected(enableColourBySeq);
+    jmolColour.setSelected(!enableColourBySeq);
+  }
+
+  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
+  {
+    addAlignmentPanel(nap);
+    if (!_colourwith.contains(nap))
+    {
+      _colourwith.add(nap);
+    }
+  }
+
+  public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
+  {
+    if (_colourwith.contains(nap))
+    {
+      _colourwith.remove(nap);
+    }
+  }
+
+  /**
+   * pdb retrieval thread.
+   */
+  private Thread worker = null;
+
+  /**
+   * add a new structure (with associated sequences and chains) to this viewer,
+   * retrieving it if necessary first.
+   * 
+   * @param pdbentry
+   * @param seq
+   * @param chains
+   * @param alignFrame
+   * @param align
+   *          if true, new structure(s) will be align using associated alignment
+   */
+  private void addStructure(final PDBEntry pdbentry, final SequenceI[] seq,
+          final String[] chains, final boolean b,
+          final IProgressIndicator alignFrame)
+  {
+    if (pdbentry.getFile() == null)
+    {
+      if (worker != null && worker.isAlive())
+      {
+        // a retrieval is in progress, wait around and add ourselves to the
+        // queue.
+        new Thread(new Runnable()
+        {
+          public void run()
+          {
+            while (worker != null && worker.isAlive() && _started)
+            {
+              try
+              {
+                Thread.sleep(100 + ((int) Math.random() * 100));
+
+              } catch (Exception e)
+              {
+              }
+
+            }
+            // and call ourselves again.
+            addStructure(pdbentry, seq, chains, b, alignFrame);
+          }
+        }).start();
+        return;
+      }
+    }
+    // otherwise, start adding the structure.
+    jmb.addSequenceAndChain(new PDBEntry[]
+    { pdbentry }, new SequenceI[][]
+    { seq }, new String[][]
+    { chains });
+    addingStructures = true;
+    _started = false;
+    alignAddedStructures = b;
+    progressBar = alignFrame; // visual indication happens on caller frame.
+    (worker = new Thread(this)).start();
+    return;
+  }
+
+  private Vector getJmolsFor(AlignmentPanel ap2)
+  {
+    Vector otherJmols = new Vector();
+    // Now this AppJmol is mapped to new sequences. We must add them to
+    // the exisiting array
+    JInternalFrame[] frames = Desktop.instance.getAllFrames();
+
+    for (int i = 0; i < frames.length; i++)
+    {
+      if (frames[i] instanceof ChimeraViewFrame)
+      {
+        ChimeraViewFrame topJmol = ((ChimeraViewFrame) frames[i]);
+        if (topJmol.isLinkedWith(ap2))
+        {
+          otherJmols.addElement(topJmol);
+        }
+      }
+    }
+    return otherJmols;
+  }
+
+  void initChimera(String command)
+  {
+    jmb.setFinishedInit(false);
+    // TODO: consider waiting until the structure/view is fully loaded before
+    // displaying
+    jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
+            getBounds().width, getBounds().height);
+    if (command == null)
+    {
+      command = "";
+    }
+    jmb.evalStateCommand(command, false);
+    jmb.setFinishedInit(true);
+  }
+
+  void setChainMenuItems(Vector chains)
+  {
+    chainMenu.removeAll();
+    if (chains == null)
+    {
+      return;
+    }
+    JMenuItem menuItem = new JMenuItem(
+            MessageManager.getString("label.all"));
+    menuItem.addActionListener(new ActionListener()
+    {
+      public void actionPerformed(ActionEvent evt)
+      {
+        allChainsSelected = true;
+        for (int i = 0; i < chainMenu.getItemCount(); i++)
+        {
+          if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+            ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
+        }
+        centerViewer();
+        allChainsSelected = false;
+      }
+    });
+
+    chainMenu.add(menuItem);
+
+    for (int c = 0; c < chains.size(); c++)
+    {
+      menuItem = new JCheckBoxMenuItem(chains.elementAt(c).toString(), true);
+      menuItem.addItemListener(new ItemListener()
+      {
+        public void itemStateChanged(ItemEvent evt)
+        {
+          if (!allChainsSelected)
+            centerViewer();
+        }
+      });
+
+      chainMenu.add(menuItem);
+    }
+  }
+
+  boolean allChainsSelected = false;
+
+  private boolean alignAddedStructures = false;
+
+  void centerViewer()
+  {
+    Vector toshow = new Vector();
+    String lbl;
+    int mlength, p, mnum;
+    for (int i = 0; i < chainMenu.getItemCount(); i++)
+    {
+      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+      {
+        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
+        if (item.isSelected())
+        {
+          toshow.addElement(item.getText());
+        }
+      }
+    }
+    jmb.centerViewer(toshow);
+  }
+
+  public void closeViewer()
+  {
+    jmb.closeViewer();
+    ap = null;
+    _aps.clear();
+    _alignwith.clear();
+    _colourwith.clear();
+    // TODO: check for memory leaks where instance isn't finalised because jmb
+    // holds a reference to the window
+    jmb = null;
+  }
+
+  /**
+   * state flag for PDB retrieval thread
+   */
+  private boolean _started = false;
+
+  public void run()
+  {
+    _started = true;
+    String pdbid = "";
+    // todo - record which pdbids were successfuly imported.
+    StringBuffer errormsgs = new StringBuffer(), files = new StringBuffer();
+    List<String> fileToLoad=new ArrayList<String>();
+    List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
+    List<Integer> filePDBpos =new ArrayList<Integer>();
+    try
+    {
+      String[] curfiles = jmb.getPdbFile(); // files currently in viewer
+      // TODO: replace with reference fetching/transfer code (validate PDBentry
+      // as a DBRef?)
+      jalview.ws.dbsources.Pdb pdbclient = new jalview.ws.dbsources.Pdb();
+      for (int pi = 0; pi < jmb.pdbentry.length; pi++)
+      {
+        String file = null;
+        if (jmb.pdbentry[pi].getFile()==null) 
+        {
+          // retrieve the pdb and store it locally
+          AlignmentI pdbseq = null;
+          pdbid = jmb.pdbentry[pi].getId();
+          long hdl = pdbid.hashCode() - System.currentTimeMillis();
+          if (progressBar != null)
+          {
+            progressBar.setProgressBar("Fetching PDB " + pdbid, hdl);
+          }
+          try
+          {
+            pdbseq = pdbclient.getSequenceRecords(pdbid = jmb.pdbentry[pi]
+                    .getId());
+          } catch (OutOfMemoryError oomerror)
+          {
+            new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
+          } catch (Exception ex)
+          {
+            ex.printStackTrace();
+            errormsgs.append("'" + pdbid + "'");
+          }
+          if (progressBar != null)
+          {
+            progressBar.setProgressBar("Finished.", hdl);
+          }
+          if (pdbseq != null)
+          {
+            // just transfer the file name from the first sequence's first
+            // PDBEntry
+            file = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
+                    .elementAt(0)).getFile()).getAbsolutePath();
+            jmb.pdbentry[pi].setFile(file);
+
+            files.append(" \"" + Platform.escapeString(file) + "\"");
+          }
+          else
+          {
+            errormsgs.append("'" + pdbid + "' ");
+          }
+        }
+        else
+        {
+          file = new File(jmb.pdbentry[pi].getFile())
+          .getAbsoluteFile().getPath();
+          if (curfiles != null && curfiles.length > 0)
+          {
+            addingStructures = true; // already files loaded.
+            for (int c = 0; c < curfiles.length; c++)
+            {
+              if (curfiles[c].equals(file))
+              {
+                file = null;
+                break;
+              }
+            }
+          }
+          
+          if (file != null)
+          {
+            fileToLoad.add(file);
+            filePDB.add(jmb.pdbentry[pi]);
+            filePDBpos.add(Integer.valueOf(pi));
+            files.append(" \"" + Platform.escapeString(file) + "\"");
+          }
+        }
+      }
+    } catch (OutOfMemoryError oomerror)
+    {
+      new OOMWarning("Retrieving PDB files: " + pdbid, oomerror);
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+      errormsgs.append("When retrieving pdbfiles : current was: '" + pdbid
+              + "'");
+    }
+    if (errormsgs.length() > 0)
+    {
+
+      JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
+              .formatMessage("label.pdb_entries_couldnt_be_retrieved",
+                      new String[]
+                      { errormsgs.toString() }), MessageManager
+              .getString("label.couldnt_load_file"),
+              JOptionPane.ERROR_MESSAGE);
+
+    }
+    long lastnotify = jmb.getLoadNotifiesHandled();
+    if (files.length() > 0)
+    {
+      if (!addingStructures)
+      {
+        try
+        {
+          initChimera("");
+        } catch (Exception ex)
+        {
+          Cache.log.error("Couldn't open Chimera viewer!", ex);
+        }
+      } 
+      int num=-1;
+      for (PDBEntry pe : filePDB)
+      {
+        num++;
+        if (pe.getFile() != null)
+        {
+          try
+          {
+            int pos=filePDBpos.get(num).intValue();
+            jmb.openFile(pe);
+            jmb.addSequence(pos, jmb.sequence[pos]);
+            File fl=new File(pe.getFile());
+            String protocol = AppletFormatAdapter.URL;
+            try
+            {
+              if (fl.exists())
+                {
+                  protocol = AppletFormatAdapter.FILE;
+                }
+              } catch (Exception e)
+              {
+              } catch (Error e)
+              {
+              }
+              // Explicitly map to the filename used by Jmol ;
+              jmb.ssm.setMapping(jmb.sequence[pos], null, pe.getFile(),
+                      protocol);
+              // pdbentry[pe].getFile(), protocol);
+          } catch (OutOfMemoryError oomerror)
+          {
+            new OOMWarning(
+                    "When trying to open and map structures from Chimera!",
+                    oomerror);
+          } catch (Exception ex)
+          {
+            Cache.log.error("Couldn't open " + pe.getFile()
+                    + " in Chimera viewer!", ex);
+          } finally
+          {
+            Cache.log.debug("File locations are " + files);
+          }
+        }
+      }
+      // jmb.getPdbFile();
+      jmb.setFinishedInit(true);
+      jmb.setLoadingFromArchive(false);
+      
+      // refresh the sequence colours for the new structure(s)
+      for (AlignmentPanel ap : _colourwith)
+      {
+        jmb.updateColours(ap);
+      }
+      // do superposition if asked to
+      if (alignAddedStructures)
+      {
+        new Thread(new Runnable()
+        {
+          public void run()
+          {
+            alignStructs_withAllAlignPanels();
+          }
+        }).start();
+        alignAddedStructures = false;
+      }
+      addingStructures = false;
+    }
+    _started = false;
+    worker = null;
+  }
+
+  public void pdbFile_actionPerformed(ActionEvent actionEvent)
+  {
+    JalviewFileChooser chooser = new JalviewFileChooser(
+            jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
+
+    chooser.setFileView(new JalviewFileView());
+    chooser.setDialogTitle("Save PDB File");
+    chooser.setToolTipText(MessageManager.getString("action.save"));
+
+    int value = chooser.showSaveDialog(this);
+
+    if (value == JalviewFileChooser.APPROVE_OPTION)
+    {
+      try
+      {
+        // TODO: cope with multiple PDB files in view
+        BufferedReader in = new BufferedReader(new FileReader(
+                jmb.getPdbFile()[0]));
+        File outFile = chooser.getSelectedFile();
+
+        PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
+        String data;
+        while ((data = in.readLine()) != null)
+        {
+          if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
+          {
+            out.println(data);
+          }
+        }
+        out.close();
+      } catch (Exception ex)
+      {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+  public void viewMapping_actionPerformed(ActionEvent actionEvent)
+  {
+    jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
+    try
+    {
+      for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++)
+      {
+        cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile()));
+        cap.appendText("\n");
+      }
+    } catch (OutOfMemoryError e)
+    {
+      new OOMWarning(
+              "composing sequence-structure alignments for display in text box.",
+              e);
+      cap.dispose();
+      return;
+    }
+    jalview.gui.Desktop.addInternalFrame(cap,
+            MessageManager.getString("label.pdb_sequence_mapping"), 550,
+            600);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  public void eps_actionPerformed(ActionEvent e)
+  {
+    throw new Error("EPS Generation not yet implemented.");
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  public void png_actionPerformed(ActionEvent e)
+  {
+    throw new Error("PNG Generation not yet implemented.");
+  }
+
+  public void jmolColour_actionPerformed(ActionEvent actionEvent)
+  {
+    if (jmolColour.isSelected())
+    {
+      // disable automatic sequence colouring.
+      jmb.setColourBySequence(false);
+    }
+  }
+
+  public void seqColour_actionPerformed(ActionEvent actionEvent)
+  {
+    jmb.setColourBySequence(seqColour.isSelected());
+    if (_colourwith == null)
+    {
+      _colourwith = new Vector<AlignmentPanel>();
+    }
+    if (jmb.isColourBySequence())
+    {
+      if (!jmb.isLoadingFromArchive())
+      {
+        if (_colourwith.size() == 0 && ap != null)
+        {
+          // Make the currently displayed alignment panel the associated view
+          _colourwith.add(ap.alignFrame.alignPanel);
+        }
+      }
+      // Set the colour using the current view for the associated alignframe
+      for (AlignmentPanel ap : _colourwith)
+      {
+        jmb.colourBySequence(ap.av.showSequenceFeatures, ap);
+      }
+    }
+  }
+
+  public void chainColour_actionPerformed(ActionEvent actionEvent)
+  {
+    chainColour.setSelected(true);
+    jmb.colourByChain();
+  }
+
+  public void chargeColour_actionPerformed(ActionEvent actionEvent)
+  {
+    chargeColour.setSelected(true);
+    jmb.colourByCharge();
+  }
+
+  public void zappoColour_actionPerformed(ActionEvent actionEvent)
+  {
+    zappoColour.setSelected(true);
+    jmb.setJalviewColourScheme(new ZappoColourScheme());
+  }
+
+  public void taylorColour_actionPerformed(ActionEvent actionEvent)
+  {
+    taylorColour.setSelected(true);
+    jmb.setJalviewColourScheme(new TaylorColourScheme());
+  }
+
+  public void hydroColour_actionPerformed(ActionEvent actionEvent)
+  {
+    hydroColour.setSelected(true);
+    jmb.setJalviewColourScheme(new HydrophobicColourScheme());
+  }
+
+  public void helixColour_actionPerformed(ActionEvent actionEvent)
+  {
+    helixColour.setSelected(true);
+    jmb.setJalviewColourScheme(new HelixColourScheme());
+  }
+
+  public void strandColour_actionPerformed(ActionEvent actionEvent)
+  {
+    strandColour.setSelected(true);
+    jmb.setJalviewColourScheme(new StrandColourScheme());
+  }
+
+  public void turnColour_actionPerformed(ActionEvent actionEvent)
+  {
+    turnColour.setSelected(true);
+    jmb.setJalviewColourScheme(new TurnColourScheme());
+  }
+
+  public void buriedColour_actionPerformed(ActionEvent actionEvent)
+  {
+    buriedColour.setSelected(true);
+    jmb.setJalviewColourScheme(new BuriedColourScheme());
+  }
+
+  public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
+  {
+    setJalviewColourScheme(new PurinePyrimidineColourScheme());
+  }
+
+  public void userColour_actionPerformed(ActionEvent actionEvent)
+  {
+    userColour.setSelected(true);
+    new UserDefinedColours(this, null);
+  }
+
+  public void backGround_actionPerformed(ActionEvent actionEvent)
+  {
+    java.awt.Color col = JColorChooser.showDialog(this,
+            "Select Background Colour", null);
+    if (col != null)
+    {
+      jmb.setBackgroundColour(col);
+    }
+  }
+
+  public void jmolHelp_actionPerformed(ActionEvent actionEvent)
+  {
+    try
+    {
+      jalview.util.BrowserLauncher
+              .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
+    } catch (Exception ex)
+    {
+    }
+  }
+
+  String viewId = null;
+
+  public String getViewId()
+  {
+    if (viewId == null)
+    {
+      viewId = System.currentTimeMillis() + "." + this.hashCode();
+    }
+    return viewId;
+  }
+
+  public void updateTitleAndMenus()
+  {
+    if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
+    {
+      repaint();
+      return;
+    }
+    setChainMenuItems(jmb.chainNames);
+
+    this.setTitle(jmb.getViewerTitle());
+    if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1)
+    {
+      jmolActionMenu.setVisible(true);
+    }
+    if (!jmb.isLoadingFromArchive())
+    {
+      seqColour_actionPerformed(null);
+    }
+  }
+
+  protected void buildChimeraActionMenu()
+  {
+    if (_alignwith == null)
+    {
+      _alignwith = new Vector<AlignmentPanel>();
+    }
+    if (_alignwith.size() == 0 && ap != null)
+    {
+      _alignwith.add(ap);
+    }
+    ;
+    for (Component c : jmolActionMenu.getMenuComponents())
+    {
+      if (c != alignStructs)
+      {
+        jmolActionMenu.remove((JMenuItem) c);
+      }
+    }
+    final ItemListener handler;
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see
+   * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
+   * .ActionEvent)
+   */
+  @Override
+  protected void alignStructs_actionPerformed(ActionEvent actionEvent)
+  {
+    alignStructs_withAllAlignPanels();
+  }
+
+  private void alignStructs_withAllAlignPanels()
+  {
+    if (ap == null)
+    {
+      return;
+    }
+    ;
+    if (_alignwith.size() == 0)
+    {
+      _alignwith.add(ap);
+    }
+    ;
+    try
+    {
+      AlignmentI[] als = new Alignment[_alignwith.size()];
+      ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
+      int[] alm = new int[_alignwith.size()];
+      int a = 0;
+
+      for (AlignmentPanel ap : _alignwith)
+      {
+        als[a] = ap.av.getAlignment();
+        alm[a] = -1;
+        alc[a++] = ap.av.getColumnSelection();
+      }
+      jmb.superposeStructures(als, alm, alc);
+    } catch (Exception e)
+    {
+      StringBuffer sp = new StringBuffer();
+      for (AlignmentPanel ap : _alignwith)
+      {
+        sp.append("'" + ap.alignFrame.getTitle() + "' ");
+      }
+      Cache.log.info("Couldn't align structures with the " + sp.toString()
+              + "associated alignment panels.", e);
+
+    }
+
+  }
+
+  public void setJalviewColourScheme(ColourSchemeI ucs)
+  {
+    jmb.setJalviewColourScheme(ucs);
+
+  }
+
+  /**
+   * 
+   * @param alignment
+   * @return first alignment panel displaying given alignment, or the default
+   *         alignment panel
+   */
+  public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
+  {
+    for (AlignmentPanel ap : getAllAlignmentPanels())
+    {
+      if (ap.av.getAlignment() == alignment)
+      {
+        return ap;
+      }
+    }
+    return ap;
+  }
+
+  /**
+   * 
+   * @param ap2
+   * @return true if this Jmol instance is linked with the given alignPanel
+   */
+  public boolean isLinkedWith(AlignmentPanel ap2)
+  {
+    return _aps.contains(ap2.av.getSequenceSetId());
+  }
+
+  public boolean isUsedforaligment(AlignmentPanel ap2)
+  {
+
+    return (_alignwith != null) && _alignwith.contains(ap2);
+  }
+
+  public boolean isUsedforcolourby(AlignmentPanel ap2)
+  {
+    return (_colourwith != null) && _colourwith.contains(ap2);
+  }
+
+  /**
+   * 
+   * @return TRUE if the view is NOT being coloured by sequence associations.
+   */
+  public boolean isColouredByJmol()
+  {
+    return !jmb.isColourBySequence();
+  }
+
+  public SequenceStructureBinding getBinding()
+  {
+    return jmb;
+  }
+
+}
index 8cad9ca..f3bc42e 100644 (file)
@@ -32,6 +32,7 @@ import javax.swing.*;
 
 import org.exolab.castor.xml.*;
 
+import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
@@ -1795,7 +1796,7 @@ public class Jalview2XML
     try
     {
       // create list to store references for any new Jmol viewers created
-      newStructureViewers = new Vector<AppJmol>();
+      newStructureViewers = new Vector<JalviewStructureDisplayI>();
       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
       // Workaround is to make sure caller implements the JarInputStreamProvider
       // interface
@@ -3132,10 +3133,11 @@ public class Jalview2XML
                   @Override
                   public void run()
                   {
-                    AppJmol sview = null;
+                    JalviewStructureDisplayI sview = null;
                     try
                     {
-                      sview = new AppJmol(pdbf, id, sq, alf.alignPanel,
+                      // JAL-1333 note - we probably can't migrate Jmol views to UCSF Chimera!
+                      sview = new StructureViewer(alf.alignPanel.getStructureSelectionManager()).createView(StructureViewer.Viewer.JMOL, pdbf, id, sq, alf.alignPanel,
                               useinJmolsuperpos, usetoColourbyseq,
                               jmolColouring, fileloc, rect, vid);
                       addNewStructureViewer(sview);
@@ -3265,13 +3267,13 @@ public class Jalview2XML
     return true;
   }
 
-  Vector<AppJmol> newStructureViewers = null;
+  Vector<JalviewStructureDisplayI> newStructureViewers = null;
 
-  protected void addNewStructureViewer(AppJmol sview)
+  protected void addNewStructureViewer(JalviewStructureDisplayI sview)
   {
     if (newStructureViewers != null)
     {
-      sview.jmb.setFinishedLoadingFromArchive(false);
+      sview.getBinding().setFinishedLoadingFromArchive(false);
       newStructureViewers.add(sview);
     }
   }
@@ -3280,9 +3282,9 @@ public class Jalview2XML
   {
     if (newStructureViewers != null)
     {
-      for (AppJmol sview : newStructureViewers)
+      for (JalviewStructureDisplayI sview : newStructureViewers)
       {
-        sview.jmb.setFinishedLoadingFromArchive(true);
+        sview.getBinding().setFinishedLoadingFromArchive(true);
       }
       newStructureViewers.clear();
       newStructureViewers = null;
diff --git a/src/jalview/gui/JalviewChimeraBindingModel.java b/src/jalview/gui/JalviewChimeraBindingModel.java
new file mode 100644 (file)
index 0000000..f7c4878
--- /dev/null
@@ -0,0 +1,102 @@
+package jalview.gui;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
+import jalview.structure.StructureSelectionManager;
+
+public class JalviewChimeraBindingModel extends JalviewChimeraBinding
+{
+  private ChimeraViewFrame cvf;
+
+  public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame,
+          StructureSelectionManager ssm, PDBEntry[] pdbentry,
+          SequenceI[][] sequenceIs, String[][] chains, String protocol)
+  {
+    super(ssm, pdbentry, sequenceIs, chains, protocol);
+    cvf = chimeraViewFrame;
+  }
+
+  FeatureRenderer fr = null;
+
+  @Override
+  public jalview.api.FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment)
+  {
+    AlignmentPanel ap = (alignment == null) ? cvf.ap
+            : (AlignmentPanel) alignment;
+    if (ap.av.showSequenceFeatures)
+    {
+      if (fr == null)
+      {
+        fr = ap.cloneFeatureRenderer();
+      }
+      else
+      {
+        ap.updateFeatureRenderer(fr);
+      }
+    }
+
+    return fr;
+  }
+
+  @Override
+  public jalview.api.SequenceRenderer getSequenceRenderer(
+          AlignmentViewPanel alignment)
+  {
+    return new SequenceRenderer(((AlignmentPanel) alignment).av);
+  }
+  @Override
+  public void refreshGUI()
+  {
+    // appJmolWindow.repaint();
+    javax.swing.SwingUtilities.invokeLater(new Runnable()
+    {
+      public void run()
+      {
+        cvf.updateTitleAndMenus();
+        cvf.revalidate();
+      }
+    });
+  }
+
+  public void updateColours(Object source)
+  {
+    AlignmentPanel ap = (AlignmentPanel) source, topap;
+    // ignore events from panels not used to colour this view
+    if (!cvf.isUsedforcolourby(ap))
+      return;
+    if (!isLoadingFromArchive())
+    {
+      colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+    }
+  }
+  @Override
+  public void releaseReferences(Object svl)
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  protected void releaseUIResources()
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void refreshPdbEntries()
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void showUrl(String url, String target)
+  {
+    // TODO Auto-generated method stub
+
+  }
+}
index ace4762..44860a1 100644 (file)
@@ -256,9 +256,12 @@ public class PopupMenu extends JPopupMenu
             {
               // TODO re JAL-860: optionally open dialog or provide a menu entry
               // allowing user to open just one structure per sequence
-              new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
-              { pdb })[0], null, ap);
-              // new PDBViewer(pdb, seqs2, null, ap, AppletFormatAdapter.FILE);
+              //new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
+              //{ pdb })[0], null, ap);
+              new StructureViewer(ap.getStructureSelectionManager())
+                      .viewStructures(pdb,
+                              ap.av.collateForPDB(new PDBEntry[]
+                              { pdb })[0], null, ap);
             }
 
           });
@@ -558,7 +561,7 @@ public class PopupMenu extends JPopupMenu
           @Override
           public void actionPerformed(ActionEvent e)
           {
-            new AppJmol(ap, pe, ap.av.collateForPDB(pe));
+            new StructureViewer(ap.getStructureSelectionManager()).viewStructures(ap, pe, ap.av.collateForPDB(pe));
           }
         });
         if (reppdb.size() > 1 && reppdb.size() < pdbe.size())
@@ -576,7 +579,7 @@ public class PopupMenu extends JPopupMenu
             @Override
             public void actionPerformed(ActionEvent e)
             {
-              new AppJmol(ap, pr, ap.av.collateForPDB(pr));
+              new StructureViewer(ap.getStructureSelectionManager()).viewStructures(ap, pr, ap.av.collateForPDB(pr));
             }
           });
         }
diff --git a/src/jalview/gui/StructureViewer.java b/src/jalview/gui/StructureViewer.java
new file mode 100644 (file)
index 0000000..ccb3c18
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import java.awt.Rectangle;
+
+import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Cache;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.Viewer;
+import jalview.structure.StructureSelectionManager;
+
+/**
+ * proxy for handling structure viewers.
+ * 
+ * this allows new views to be created with the currently configured viewer, the
+ * preferred viewer to be set/read and existing views created previously with a
+ * particular viewer to be recovered
+ * 
+ * @author jprocter
+ */
+public class StructureViewer
+{
+  StructureSelectionManager ssm;
+
+  public enum Viewer
+  {
+    JMOL, CHIMERA
+  };
+
+  public Viewer getViewerType()
+  {
+    String viewType = Cache.getDefault("STRUCTURE_DISPLAY", "JMOL");
+    return Viewer.valueOf(viewType);
+  }
+
+  public void setViewerType(Viewer type)
+  {
+    Cache.setProperty("STRUCTURE_DISPLAY", type.toString());
+  }
+
+  public StructureViewer(StructureSelectionManager structureSelectionManager)
+  {
+    ssm = structureSelectionManager;
+  }
+
+  public JalviewStructureDisplayI viewStructures(AlignmentPanel ap,
+          PDBEntry[] pr, SequenceI[][] collateForPDB)
+  {
+    return viewStructures(getViewerType(), ap, pr, collateForPDB);
+  }
+
+  public JalviewStructureDisplayI viewStructures(Viewer viewerType,
+          AlignmentPanel ap, PDBEntry[] pr, SequenceI[][] collateForPDB)
+  {
+    JalviewStructureDisplayI sview = null;
+    if (viewerType.equals(Viewer.JMOL))
+    {
+      sview = new AppJmol(ap, pr, ap.av.collateForPDB(pr));
+    }
+    else if (viewerType.equals(Viewer.CHIMERA))
+    {
+      sview = new ChimeraViewFrame(ap, pr, ap.av.collateForPDB(pr));
+    }
+    else
+    {
+      Cache.log.error("Unknown structure viewer type "
+              + getViewerType().toString());
+    }
+    return sview;
+  }
+
+  public JalviewStructureDisplayI viewStructures(Viewer viewerType,
+          AlignmentPanel ap, PDBEntry pr, SequenceI[] collateForPDB)
+  {
+    JalviewStructureDisplayI sview = null;
+    if (viewerType.equals(Viewer.JMOL))
+    {
+      sview = new AppJmol(pr, collateForPDB, null, ap);
+    }
+    else if (viewerType.equals(Viewer.CHIMERA))
+    {
+      sview = new ChimeraViewFrame(pr, collateForPDB, null, ap);
+    }
+    else
+    {
+      Cache.log.error("Unknown structure viewer type "
+              + getViewerType().toString());
+    }
+    return sview;
+  }
+
+  public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
+          SequenceI[] sequenceIs, Object object, AlignmentPanel ap)
+  {
+    return viewStructures(getViewerType(), ap, pdb, sequenceIs);
+  }
+
+  public JalviewStructureDisplayI createView(Viewer jmol, String[] pdbf,
+          String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
+          boolean useinJmolsuperpos, boolean usetoColourbyseq,
+          boolean jmolColouring, String fileloc, Rectangle rect, String vid)
+  {
+    JalviewStructureDisplayI sview = null;
+    switch (getViewerType())
+    {
+    case JMOL:
+
+      sview = new AppJmol(pdbf, id, sq, alignPanel, useinJmolsuperpos,
+              usetoColourbyseq, jmolColouring, fileloc, rect, vid);
+
+      break;
+    case CHIMERA:
+      break;
+    default:
+      Cache.log.error("Unknown structure viewer type "
+              + getViewerType().toString());
+    }
+    return sview;
+  }
+
+}
index 3803fa1..f719d27 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.datamodel.SequenceGroup;
 import jalview.io.JalviewFileChooser;
 import jalview.jbgui.GUserDefinedColours;
@@ -67,7 +68,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
   JInternalFrame frame;
 
-  AppJmol jmol;
+  JalviewStructureDisplayI jmol;
 
   Vector upperCaseButtons;
 
@@ -121,7 +122,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
     showFrame();
   }
 
-  public UserDefinedColours(AppJmol jmol, ColourSchemeI oldcs)
+  public UserDefinedColours(JalviewStructureDisplayI jmol, ColourSchemeI oldcs)
   {
     super();
     this.jmol = jmol;
index 5b89772..e731910 100644 (file)
@@ -297,6 +297,7 @@ public class AnnotationRenderer
       profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
               : new jalview.schemes.ZappoColourScheme();
     }
+    boolean rna = av.getAlignment().isNucleotide();
     columnSelection = av.getColumnSelection();
     hconsensus = av.getSequenceConsensusHash();// hconsensus;
     hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
@@ -353,6 +354,8 @@ public class AnnotationRenderer
     return null;
   }
 
+  boolean rna = false;
+
   /**
    * Render the annotation rows associated with an alignment.
    * 
@@ -402,7 +405,7 @@ public class AnnotationRenderer
     AlignmentAnnotation consensusAnnot = av
             .getAlignmentConsensusAnnotation(), structConsensusAnnot = av
             .getAlignmentStrucConsensusAnnotation();
-    boolean renderHistogram = true, renderProfile = true, normaliseProfile = false;
+    boolean renderHistogram = true, renderProfile = true, normaliseProfile = false, isRNA = rna;
 
     BitSet graphGroupDrawn = new BitSet();
     int charOffset = 0; // offset for a label
@@ -416,6 +419,7 @@ public class AnnotationRenderer
     for (int i = 0; i < aa.length; i++)
     {
       AlignmentAnnotation row = aa[i];
+      isRNA = row.isRNA();
       {
         // check if this is a consensus annotation row and set the display
         // settings appropriately
@@ -696,23 +700,31 @@ public class AnnotationRenderer
                 // System.out.println("\t type :"+lastSS+"\t x :"+x+"\t nbre annot :"+nb_annot);
                 switch (lastSS)
                 {
-
-                case '$':
-                  drawHelixAnnot(g, row_annotations, lastSSX, x, y,
-                          iconOffset, startRes, column, validRes, validEnd);
-                  break;
-
-                case 0xCE:
-                  drawSheetAnnot(g, row_annotations, lastSSX, x, y,
-                          iconOffset, startRes, column, validRes, validEnd);
-                  break;
-
                 case '(': // Stem case for RNA secondary structure
                 case ')': // and opposite direction
                   drawStemAnnot(g, row_annotations, lastSSX, x, y,
                           iconOffset, startRes, column, validRes, validEnd);
                   temp = x;
                   break;
+
+                case 'H':
+                  if (!isRNA)
+                  {
+                    drawHelixAnnot(g, row_annotations, lastSSX, x, y,
+                            iconOffset, startRes, column, validRes,
+                            validEnd);
+                    break;
+                  }
+
+                case 'E':
+                  if (!isRNA)
+                  {
+                    drawSheetAnnot(g, row_annotations, lastSSX, x, y,
+                            iconOffset, startRes, column, validRes,
+                            validEnd);
+                    break;
+                  }
+
                 case '{':
                 case '}':
                 case '[':
@@ -727,13 +739,11 @@ public class AnnotationRenderer
                 case 'c':
                 case 'D':
                 case 'd':
-                case 'E':
                 case 'e':
                 case 'F':
                 case 'f':
                 case 'G':
                 case 'g':
-                case 'H':
                 case 'h':
                 case 'I':
                 case 'i':
@@ -828,17 +838,25 @@ public class AnnotationRenderer
         {
           switch (lastSS)
           {
-          case '$':
-            drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
-                    startRes, column, validRes, validEnd);
-            break;
 
-          case 0xCE:
-            drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
-                    startRes, column, validRes, validEnd);
-            break;
-          case 's':
-          case 'S': // Stem case for RNA secondary structure
+          case 'H':
+            if (!isRNA)
+            {
+              drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
+                      startRes, column, validRes, validEnd);
+              break;
+            }
+
+          case 'E':
+            if (!isRNA)
+            {
+              drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
+                      startRes, column, validRes, validEnd);
+              break;
+            }
+
+          case '(':
+          case ')': // Stem case for RNA secondary structure
 
             drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
                     startRes, column, validRes, validEnd);
@@ -858,13 +876,11 @@ public class AnnotationRenderer
           case 'c':
           case 'D':
           case 'd':
-          case 'E':
           case 'e':
           case 'F':
           case 'f':
           case 'G':
           case 'g':
-          case 'H':
           case 'h':
           case 'I':
           case 'i':
@@ -1008,13 +1024,13 @@ public class AnnotationRenderer
     return !usedFaded;
   }
 
-  private final Color GLYPHLINE_COLOR = Color.gray;
+  public static final Color GLYPHLINE_COLOR = Color.gray;
 
-  private final Color SHEET_COLOUR = Color.green;
+  public static final Color SHEET_COLOUR = Color.green;
 
-  private final Color HELIX_COLOUR = Color.red;
+  public static final Color HELIX_COLOUR = Color.red;
 
-  private final Color STEM_COLOUR = Color.blue;
+  public static final Color STEM_COLOUR = Color.blue;
 
   private Color sdNOTCANONICAL_COLOUR;
 
index f467cb8..e975884 100755 (executable)
@@ -233,14 +233,20 @@ public class AnnotationColourGradient extends FollowerColourScheme
               && !jalview.util.Comparison.isGap(c))
       {
 
-        if (predefinedColours)
+        if (annotation.annotations[j].colour != null)
         {
-          if (annotation.annotations[j].colour != null)
+          if (predefinedColours || annotation.hasIcons)
+          {
             return annotation.annotations[j].colour;
-          else
+          }
+        }
+        else
+        {
+          if (predefinedColours)
+          {
             return currentColour;
+          }
         }
-
         if (aboveAnnotationThreshold == NO_THRESHOLD
                 || (annotationThreshold != null
                         && aboveAnnotationThreshold == ABOVE_THRESHOLD && annotation.annotations[j].value >= annotationThreshold.value)
diff --git a/src/jalview/structures/models/SequenceStructureBindingModel.java b/src/jalview/structures/models/SequenceStructureBindingModel.java
new file mode 100644 (file)
index 0000000..7d023a8
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.structures.models;
+
+import jalview.api.SequenceStructureBinding;
+
+public class SequenceStructureBindingModel implements
+        SequenceStructureBinding
+{
+
+  /**
+   * set if Structure Viewer state is being restored from some source -
+   * instructs binding not to apply default display style when structure set is
+   * updated for first time.
+   */
+  private boolean loadingFromArchive = false;
+
+  /**
+   * second flag to indicate if the Structure viewer should ignore sequence
+   * colouring events from the structure manager because the GUI is still
+   * setting up
+   */
+  private boolean loadingFinished = true;
+
+  @Override
+  public void setLoadingFromArchive(boolean loadingFromArchive)
+  {
+    this.loadingFromArchive = loadingFromArchive;
+  }
+
+  /**
+   * 
+   * @return true if Jmol is still restoring state or loading is still going on
+   *         (see setFinsihedLoadingFromArchive)
+   */
+  @Override
+  public boolean isLoadingFromArchive()
+  {
+    return loadingFromArchive && !loadingFinished;
+  }
+
+  @Override
+  public boolean isLoadingFinished()
+  {
+    return loadingFinished;
+  }
+
+  @Override
+  public void setFinishedLoadingFromArchive(boolean finishedLoading)
+  {
+    loadingFinished = finishedLoading;
+  }
+
+}
index 71cbb7e..8328d45 100644 (file)
@@ -30,6 +30,7 @@ import jalview.ws.params.OptionI;
 import jalview.ws.params.WsParamSetI;
 import jalview.ws.uimodel.AlignAnalysisUIText;
 
+import java.awt.Color;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -50,14 +51,14 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
   public List<Argument> selectDefaultArgs()
   {
     List<ArgumentI> rgs = new ArrayList<ArgumentI>();
-    for (ArgumentI argi: service.getParamStore().getServiceParameters())
+    for (ArgumentI argi : service.getParamStore().getServiceParameters())
     {
       if (argi instanceof OptionI)
       {
         List<String> o = ((OptionI) argi).getPossibleValues();
         if (o.contains("-pred-nohits"))
         {
-          OptionI cpy = ((OptionI)argi).copy();
+          OptionI cpy = ((OptionI) argi).copy();
           cpy.setValue("-pred-nohits");
           rgs.add(cpy);
         }
@@ -88,15 +89,20 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
   {
     return "calculating consensus secondary structure prediction using JPred service";
   }
-  private static HashMap<String, String[]> jpredRowLabels = new HashMap<String,String[]>();
-  private static HashSet<String>jpredRes_graph,jpredRes_ssonly;
+
+  private static HashMap<String, String[]> jpredRowLabels = new HashMap<String, String[]>();
+
+  private static HashSet<String> jpredRes_graph, jpredRes_ssonly;
   {
-    jpredRes_ssonly=new HashSet();
+    jpredRes_ssonly = new HashSet();
     jpredRes_ssonly.add("jnetpred".toLowerCase());
-    jpredRes_graph=new HashSet();
+    jpredRes_ssonly.add("jnetpssm".toLowerCase());
+    jpredRes_ssonly.add("jnethmm".toLowerCase());
+    jpredRes_graph = new HashSet();
     jpredRes_graph.add("jnetconf".toLowerCase());
-    
+    jpredRes_graph.add("jnet burial".toLowerCase());
   }
+
   /**
    * update the consensus annotation from the sequence profile data using
    * current visualization settings.
@@ -110,27 +116,62 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
         JpredAlignment jpres = (JpredAlignment) msascoreset;
         int alWidth = alignViewport.getAlignment().getWidth();
         ArrayList<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
-        for (FastaSequence fsq:jpres.getJpredSequences())
+        char[] sol = new char[jpres.getJpredSequences().get(0).getLength()];
+        boolean firstsol = true;
+        for (FastaSequence fsq : jpres.getJpredSequences())
         {
           String[] k = jpredRowLabels.get(fsq.getId());
-          if (k==null)
+          if (k == null)
+          {
+            k = new String[]
+            { fsq.getId(), "JNet Output" };
+          }
+          if (fsq.getId().startsWith("JNETSOL"))
+          {
+            char amnt = (fsq.getId().endsWith("25") ? "3" : fsq.getId()
+                    .endsWith("5") ? "6" : "9").charAt(0);
+            char[] vseq = fsq.getSequence().toCharArray();
+            for (int spos = 0, sposL = fsq.getLength(); spos < sposL; spos++)
+            {
+              if (firstsol)
+              {
+                sol[spos] = '0';
+              }
+              if (vseq[spos] == 'B' && (sol[spos]=='0' || sol[spos] < amnt))
+              {
+                sol[spos] = amnt;
+              }
+            }
+            firstsol = false;
+          }
+          else
           {
-            k = new String[] { fsq.getId(), "JNet Output"};
+            createAnnotationRowFromString(
+                    ourAnnot,
+                    getCalcId(),
+                    alWidth,
+                    k[0],
+                    k[1],
+                    jpredRes_graph.contains(fsq.getId()) ? AlignmentAnnotation.BAR_GRAPH
+                            : AlignmentAnnotation.NO_GRAPH, 0f, 9f,
+                    fsq.getSequence());
           }
-          createAnnotationRowFromString(ourAnnot, getCalcId(), alWidth,
-                  k[0],k[1],
-                  jpredRes_graph.contains(fsq.getId()) ?  AlignmentAnnotation.BAR_GRAPH : AlignmentAnnotation.NO_GRAPH, 0f, 0f,
-                  fsq.getSequence());
 
         }
-        for (FastaSequence fsq: jpres.getSequences())
+        createAnnotationRowFromString(
+                ourAnnot,
+                getCalcId(),
+                alWidth,
+                "Jnet Burial",
+                "<html>Prediction of Solvent Accessibility<br/>levels are<ul><li>0 - Exposed</li><li>3 - 25% or more S.A. accessible</li><li>6 - 5% or more S.A. accessible</li><li>9 - Buried (<5% exposed)</li></ul>",
+                AlignmentAnnotation.BAR_GRAPH, 0f, 9f, new String(sol));
+        for (FastaSequence fsq : jpres.getSequences())
         {
           if (fsq.getId().equalsIgnoreCase("QUERY"))
           {
             createAnnotationRowFromString(ourAnnot, getCalcId(), alWidth,
                     "Query", "JPred Reference Sequence",
-                    AlignmentAnnotation.NO_GRAPH, 0f, 0f,
-                    fsq.getSequence());
+                    AlignmentAnnotation.NO_GRAPH, 0f, 0f, fsq.getSequence());
           }
         }
         if (ourAnnot.size() > 0)
@@ -161,6 +202,7 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
       {
         // created a valid annotation from the data
         ourAnnot.add(annotation);
+        // annotation.validateRangeAndDisplay();
       }
     }
   }
@@ -169,11 +211,19 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
           AlignmentAnnotation annotation, String sourceData, int alWidth,
           int rowType)
   {
-    if (sourceData.length()==0 && alWidth>0)
+    if (sourceData.length() == 0 && alWidth > 0)
     {
       return false;
     }
     Annotation[] elm = new Annotation[alWidth];
+    boolean ssOnly = jpredRes_ssonly.contains(annotation.label
+            .toLowerCase());
+    boolean graphOnly = rowType != AlignmentAnnotation.NO_GRAPH;
+    if (!ssOnly && !graphOnly)
+    {
+      // for burial 'B'
+      annotation.showAllColLabels = true;
+    }
 
     for (int i = 0, iSize = sourceData.length(); i < iSize; i++)
     {
@@ -189,13 +239,15 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
       switch (rowType)
       {
       case AlignmentAnnotation.NO_GRAPH:
-        elm[i] = new Annotation("" + annot, "" + annot, annot, Float.NaN);
+        elm[i] = ssOnly ? new Annotation("", "", annot, Float.NaN,
+                colourSS(annot)) : new Annotation("" + annot, "" + annot,
+                '\0', Float.NaN);
         break;
       default:
         try
         {
           elm[i] = new Annotation("" + annot, "" + annot, annot,
-                  Integer.valueOf(annot));
+                  Integer.valueOf(""+annot));
         } catch (Exception x)
         {
           System.err.println("Expected numeric value in character '"
@@ -210,6 +262,18 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
     return true;
   }
 
+  private Color colourSS(char annot)
+  {
+    switch (annot)
+    {
+    case 'H':
+      return jalview.renderer.AnnotationRenderer.HELIX_COLOUR;
+    case 'E':
+      return jalview.renderer.AnnotationRenderer.SHEET_COLOUR;
+    }
+    return jalview.renderer.AnnotationRenderer.GLYPHLINE_COLOR;
+  }
+
   @Override
   public String getCalcId()
   {
index 900e47c..ef14287 100644 (file)
@@ -27,6 +27,9 @@ import java.util.Vector;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.FileLoader;
 
 import org.junit.Test;
 
@@ -36,32 +39,63 @@ import org.junit.Test;
  */
 public class PDBFileWithJmolTest
 {
+  String[] testFile = new String[]
+  { "./examples/1GAQ.txt" }; // , "./examples/DNMT1_MOUSE.pdb" };
 
   @Test
-  public void test() throws Exception
+   public void testAlignmentLoader() throws Exception
+   {
+    for (String f:testFile) {
+      FileLoader fl = new jalview.io.FileLoader(false);
+      AlignFrame af = fl.LoadFileWaitTillLoaded(f, AppletFormatAdapter.FILE);
+      validateSecStrRows(af.getViewport().getAlignment());
+    }
+   }
+  @Test
+  public void testFileParser() throws Exception
+  {
+    for (String pdbStr : testFile)
+    {
+      PDBFileWithJmol jtest = new PDBFileWithJmol(pdbStr,
+              jalview.io.AppletFormatAdapter.FILE);
+      Vector<SequenceI> seqs = jtest.getSeqs();
+
+      assertTrue(
+              "No sequences extracted from testfile\n"
+                      + (jtest.hasWarningMessage() ? jtest.getWarningMessage()
+                              : "(No warnings raised)"), seqs != null
+                      && seqs.size() > 0);
+      for (SequenceI sq : seqs)
+      {
+        AlignmentI al = new Alignment(new SequenceI[]
+        { sq });
+        validateSecStrRows(al);
+      }
+    }
+  }
+
+  private void validateSecStrRows(AlignmentI al)
   {
-    PDBFileWithJmol jtest = new PDBFileWithJmol("./examples/1GAQ.txt",
-            jalview.io.AppletFormatAdapter.FILE);
-    Vector<SequenceI> seqs = jtest.getSeqs();
 
-    assertTrue(
-            "No sequences extracted from testfile\n"
-                    + (jtest.hasWarningMessage() ? jtest.getWarningMessage()
-                            : "(No warnings raised)"),
-            seqs != null && seqs.size() > 0);
-    for (SequenceI sq : seqs)
+    if (!al.isNucleotide())
     {
-      AlignmentI al = new Alignment(new SequenceI[]
-      { sq });
-      if (!al.isNucleotide())
+      for (SequenceI asq : al.getSequences())
       {
+        SequenceI sq = asq;
+        while (sq.getDatasetSequence()!=null && sq.getAnnotation()==null)
+        {
+          sq = asq.getDatasetSequence();
+        }
         assertTrue(
                 "No secondary structure assigned for protein sequence.",
                 sq.getAnnotation() != null
                         && sq.getAnnotation().length >= 1
                         && sq.getAnnotation()[0].hasIcons);
+        assertTrue(
+                "Secondary structure not associated for sequence "
+                        + sq.getName(),
+                sq.getAnnotation()[0].sequenceRef == sq);
       }
     }
   }
-
 }
diff --git a/test/jalview/ext/rbvi/chimera/AllTests.java b/test/jalview/ext/rbvi/chimera/AllTests.java
new file mode 100644 (file)
index 0000000..1d14751
--- /dev/null
@@ -0,0 +1,13 @@
+package jalview.ext.rbvi.chimera;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses(
+{})
+public class AllTests
+{
+
+}
diff --git a/test/jalview/ext/rbvi/chimera/ChimeraConnect.java b/test/jalview/ext/rbvi/chimera/ChimeraConnect.java
new file mode 100644 (file)
index 0000000..be4e5ea
--- /dev/null
@@ -0,0 +1,38 @@
+package jalview.ext.rbvi.chimera;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Test;
+
+import ext.edu.ucsf.rbvi.strucviz2.*;
+
+public class ChimeraConnect
+{
+
+  @Test
+  public void test()
+  {
+    StructureManager csm; 
+            ext.edu.ucsf.rbvi.strucviz2.ChimeraManager cm = new ChimeraManager(csm = new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
+    assertTrue("Couldn't launch chimera",cm.launchChimera(csm.getChimeraPaths()));
+    int n=0;
+    while (n++<100)
+    {
+      try { 
+        Thread.sleep(1000);
+      } catch (Exception q)
+      {
+        
+      }
+      Collection<ChimeraModel> cms = cm.getChimeraModels();
+      for (ChimeraModel cmod :cms) {
+        System.out.println(cmod.getModelName());
+      }
+    }
+    cm.exitChimera();
+  }
+
+}
diff --git a/test/jalview/ext/rbvi/chimera/JalviewChimeraView.java b/test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
new file mode 100644 (file)
index 0000000..1736af5
--- /dev/null
@@ -0,0 +1,82 @@
+package jalview.ext.rbvi.chimera;
+
+import static org.junit.Assert.*;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.StructureViewer;
+import jalview.gui.StructureViewer.Viewer;
+import jalview.io.FormatAdapter;
+
+import java.awt.Desktop;
+import java.io.File;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JalviewChimeraView
+{
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception
+  {
+    jalview.bin.Jalview.main(new String[]
+    { "-noquestionnaire -nonews -props", "test/src/jalview/ext/rbvi/chimera/testProps.jvprops" });
+  }
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @AfterClass
+  public static void tearDownAfterClass() throws Exception
+  {
+    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+
+  }
+
+
+  @Test
+  public void testSingleSeqView()
+  {
+    String inFile = "examples/1gaq.txt";
+    AlignFrame af = new jalview.io.FileLoader().LoadFileWaitTillLoaded(
+            inFile, FormatAdapter.FILE);
+    assertTrue("Didn't read input file " + inFile, af != null);
+    for (SequenceI sq:af.getViewport().getAlignment().getSequences())
+    {
+      SequenceI dsq=sq.getDatasetSequence();
+      while (dsq.getDatasetSequence()!=null)
+      {
+        dsq=dsq.getDatasetSequence();
+      }
+      if (dsq.getPDBId()!=null && dsq.getPDBId().size()>0) {
+        for (int q=0;q<dsq.getPDBId().size();q++) 
+        {
+          new StructureViewer(af.getViewport().getStructureSelectionManager()).viewStructures(Viewer.JMOL,
+                  af.getCurrentView().getAlignPanel(),
+                  new PDBEntry[] { (PDBEntry)dsq.getPDBId().elementAt(q) },
+                  new SequenceI[][] { new SequenceI[] { sq } });
+
+          new StructureViewer(af.getViewport().getStructureSelectionManager()).viewStructures(Viewer.CHIMERA,
+                  af.getCurrentView().getAlignPanel(),
+                  new PDBEntry[] { (PDBEntry)dsq.getPDBId().elementAt(q) },
+                  new SequenceI[][] { new SequenceI[] { sq } });
+          break;
+        }
+        break;
+      }
+   }
+    try {
+      Thread.sleep(200000);
+    } catch (InterruptedException q)
+    {
+      
+    }
+  }
+}
diff --git a/test/jalview/ext/rbvi/chimera/testProps.jvprops b/test/jalview/ext/rbvi/chimera/testProps.jvprops
new file mode 100644 (file)
index 0000000..d5a6c4c
--- /dev/null
@@ -0,0 +1,84 @@
+#---JalviewX Properties File---
+#Fri Apr 25 09:54:25 BST 2014
+SCREEN_Y=768
+SCREEN_X=936
+SHOW_WSDISCOVERY_ERRORS=true
+LATEST_VERSION=2.8.0b1
+SHOW_CONSERVATION=true
+JALVIEW_RSS_WINDOW_SCREEN_WIDTH=550
+JAVA_CONSOLE_SCREEN_WIDTH=450
+LAST_DIRECTORY=/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples
+ID_ITALICS=true
+SORT_ALIGNMENT=No sort
+SHOW_IDENTITY=true
+WSMENU_BYHOST=false
+SEQUENCE_LINKS=EMBL-EBI Search|http\://www.ebi.ac.uk/ebisearch/search.ebi?db\=allebi&query\=$SEQUENCE_ID$
+SHOW_FULLSCREEN=false
+RECENT_URL=http\://www.jalview.org/examples/exampleFile_2_7.jar
+FONT_NAME=SansSerif
+BLC_JVSUFFIX=true
+VERSION_CHECK=false
+YEAR=2011
+SHOW_DBREFS_TOOLTIP=true
+MSF_JVSUFFIX=true
+SCREENGEOMETRY_HEIGHT=1600
+JAVA_CONSOLE_SCREEN_Y=475
+JAVA_CONSOLE_SCREEN_X=830
+PFAM_JVSUFFIX=true
+PIR_JVSUFFIX=true
+STARTUP_FILE=http\://www.jalview.org/examples/exampleFile_2_3.jar
+JAVA_CONSOLE_SCREEN_HEIGHT=162
+PIR_MODELLER=false
+GAP_SYMBOL=-
+SHOW_QUALITY=true
+SHOW_GROUP_CONSERVATION=false
+SHOW_JWS2_SERVICES=true
+SHOW_NPFEATS_TOOLTIP=true
+FONT_STYLE=plain
+ANTI_ALIAS=false
+SORT_BY_TREE=false
+RSBS_SERVICES=|Multi-Harmony|Analysis|Sequence Harmony and Multi-Relief (Brandt et al. 2010)|hseparable,gapCharacter\='-',returns\='ANNOTATION'|?tool\=jalview|http\://zeus.few.vu.nl/programs/shmrwww/index.php?tool\=jalview&groups\=$PARTITION\:min\='2',minsize\='2',sep\=' '$&ali_file\=$ALIGNMENT\:format\='FASTA',writeasfile$
+AUTHORFNAMES=Jim Procter, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
+JALVIEW_RSS_WINDOW_SCREEN_HEIGHT=328
+SHOW_GROUP_CONSENSUS=false
+SHOW_CONSENSUS_HISTOGRAM=true
+SHOW_OVERVIEW=false
+AUTHORS=J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
+FIGURE_AUTOIDWIDTH=false
+SCREEN_WIDTH=900
+ANNOTATIONCOLOUR_MIN=ffc800
+SHOW_STARTUP_FILE=false
+RECENT_FILE=examples/uniref50.fa\t/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples/RF00031_folded.stk\t/Volumes/Data/Users/jimp/bs_ig_mult.out
+DEFAULT_FILE_FORMAT=FASTA
+SHOW_JAVA_CONSOLE=false
+VERSION=2.8b1
+FIGURE_USERIDWIDTH=
+WSMENU_BYTYPE=false
+DEFAULT_COLOUR=None
+NOQUESTIONNAIRES=true
+JALVIEW_NEWS_RSS_LASTMODIFIED=Apr 23, 2014 2\:53\:26 PM
+BUILD_DATE=01 November 2013
+PILEUP_JVSUFFIX=true
+SHOW_CONSENSUS_LOGO=false
+SCREENGEOMETRY_WIDTH=2560
+SHOW_ANNOTATIONS=true
+JALVIEW_RSS_WINDOW_SCREEN_Y=0
+USAGESTATS=false
+JALVIEW_RSS_WINDOW_SCREEN_X=0
+SHOW_UNCONSERVED=false
+SHOW_JVSUFFIX=true
+DAS_LOCAL_SOURCE=
+SCREEN_HEIGHT=650
+ANNOTATIONCOLOUR_MAX=ff0000
+AUTO_CALC_CONSENSUS=true
+FASTA_JVSUFFIX=true
+DAS_ACTIVE_SOURCE=uniprot\t
+JWS2HOSTURLS=http\://www.compbio.dundee.ac.uk/jabaws
+PAD_GAPS=false
+CLUSTAL_JVSUFFIX=true
+SHOW_ENFIN_SERVICES=true
+FONT_SIZE=10
+RIGHT_ALIGN_IDS=false
+USE_PROXY=false
+WRAP_ALIGNMENT=false
+DAS_REGISTRY_URL=http\://www.dasregistry.org/das/
diff --git a/test/jalview/ws/jabaws/JpredJabaStructExportImport.java b/test/jalview/ws/jabaws/JpredJabaStructExportImport.java
new file mode 100644 (file)
index 0000000..f0b8f99
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.ws.jabaws;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.Jalview2XML;
+import jalview.io.AnnotationFile;
+import jalview.io.FormatAdapter;
+import jalview.io.StockholmFileTest;
+import jalview.ws.jws2.JPred301Client;
+import jalview.ws.jws2.JabaParamStore;
+import jalview.ws.jws2.Jws2Discoverer;
+import jalview.ws.jws2.SequenceAnnotationWSClient;
+import jalview.ws.jws2.jabaws2.Jws2Instance;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.AutoCalcSetting;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import compbio.metadata.Argument;
+import compbio.metadata.WrongParameterException;
+
+public class JpredJabaStructExportImport
+{
+  public static String testseqs = "examples/uniref50.fa";
+
+  public static Jws2Discoverer disc;
+
+  public static Jws2Instance jpredws;
+
+  jalview.ws.jws2.JPred301Client jpredClient;
+
+  public static jalview.gui.AlignFrame af = null;
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception
+  {
+
+    jalview.bin.Cache.initLogger();
+    disc = JalviewJabawsTestUtils.getJabawsDiscoverer(false);
+
+    for (Jws2Instance svc : disc.getServices())
+    {
+
+      if (svc.getServiceTypeURI().toLowerCase().contains("jpred"))
+      {
+        jpredws = svc;
+      }
+    }
+
+    System.out.println("State of jpredws: " + jpredws);
+
+    if (jpredws == null)
+      System.exit(0);
+
+    jalview.io.FileLoader fl = new jalview.io.FileLoader(false);
+
+    af = fl.LoadFileWaitTillLoaded(testseqs, jalview.io.FormatAdapter.FILE);
+
+    assertNotNull("Couldn't load test data ('" + testseqs + "')", af);
+
+  }
+
+  @AfterClass
+  public static void tearDownAfterClass() throws Exception
+  {
+    if (af != null)
+    {
+      af.setVisible(false);
+      af.dispose();
+    }
+  }
+
+  @Test
+  public void testJPredStructOneSeqOnly()
+  {
+    af.selectAllSequenceMenuItem_actionPerformed(null);
+    af.getViewport()
+            .getSelectionGroup()
+            .addOrRemove(
+                    af.getViewport().getSelectionGroup().getSequenceAt(0),
+                    false);
+    af.hideSelSequences_actionPerformed(null);
+    jpredClient = new JPred301Client(jpredws, af, null, null);
+
+    assertTrue(
+            "Didn't find any default args to check for. Buggy implementation of hardwired arguments in client.",
+            jpredClient.selectDefaultArgs().size() > 0);
+
+    boolean success = false;
+    af.getViewport().getCalcManager().startWorker(jpredClient);
+    do
+    {
+      try
+      {
+        Thread.sleep(500);
+        List<Argument> args = JabaParamStore.getJabafromJwsArgs(af
+                .getViewport()
+                .getCalcIdSettingsFor(jpredClient.getCalcId())
+                .getArgumentSet()), defargs = jpredClient
+                .selectDefaultArgs();
+        for (Argument rg : args)
+        {
+          for (Argument defg : defargs)
+          {
+            if (defg.equals(rg))
+            {
+              success = true;
+            }
+          }
+        }
+        if (!success)
+        {
+          jpredClient.cancelCurrentJob();
+          fail("Jpred Client didn't run with hardwired default parameters.");
+        }
+
+      } catch (InterruptedException x)
+      {
+      }
+      ;
+    } while (af.getViewport().getCalcManager().isWorking());
+
+  }
+
+  @Test
+  public void testJPredStructExport()
+  {
+
+    jpredClient = new JPred301Client(jpredws, af, null, null);
+
+    af.getViewport().getCalcManager().startWorker(jpredClient);
+
+    do
+    {
+      try
+      {
+        Thread.sleep(50);
+      } catch (InterruptedException x)
+      {
+      }
+      ;
+    } while (af.getViewport().getCalcManager().isWorking());
+
+    AlignmentI orig_alig = af.getViewport().getAlignment();
+
+    testAnnotationFileIO("Testing JPredWS Annotation IO", orig_alig);
+
+  }
+
+  public static void testAnnotationFileIO(String testname, AlignmentI al)
+  {
+    try
+    {
+      // what format would be appropriate for RNAalifold annotations?
+      String aligfileout = new FormatAdapter().formatSequences("PFAM",
+              al.getSequencesArray());
+
+      String anfileout = new AnnotationFile().printAnnotations(
+              al.getAlignmentAnnotation(), al.getGroups(),
+              al.getProperties());
+      assertTrue(
+              "Test "
+                      + testname
+                      + "\nAlignment annotation file was not regenerated. Null string",
+              anfileout != null);
+      assertTrue(
+              "Test "
+                      + testname
+                      + "\nAlignment annotation file was not regenerated. Empty string",
+              anfileout.length() > "JALVIEW_ANNOTATION".length());
+
+      System.out.println("Output annotation file:\n" + anfileout
+              + "\n<<EOF\n");
+
+      // again what format would be appropriate?
+      AlignmentI al_new = new FormatAdapter().readFile(aligfileout,
+              FormatAdapter.PASTE, "PFAM");
+      assertTrue(
+              "Test "
+                      + testname
+                      + "\nregenerated annotation file did not annotate alignment.",
+              new AnnotationFile().readAnnotationFile(al_new, anfileout,
+                      FormatAdapter.PASTE));
+
+      // test for consistency in io
+      StockholmFileTest.testAlignmentEquivalence(al, al_new);
+      return;
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+    fail("Test "
+            + testname
+            + "\nCouldn't complete Annotation file roundtrip input/output/input test.");
+  }
+
+  // @Test
+  public void testJpredwsSettingsRecovery()
+  {
+    fail("not implemnented");
+    List<compbio.metadata.Argument> opts = new ArrayList<compbio.metadata.Argument>();
+    for (compbio.metadata.Argument rg : (List<compbio.metadata.Argument>) jpredws
+            .getRunnerConfig().getArguments())
+    {
+      if (rg.getDescription().contains("emperature"))
+      {
+        try
+        {
+          rg.setValue("292");
+        } catch (WrongParameterException q)
+        {
+          fail("Couldn't set the temperature parameter "
+                  + q.getStackTrace());
+        }
+        opts.add(rg);
+      }
+      if (rg.getDescription().contains("max"))
+      {
+        opts.add(rg);
+      }
+    }
+    jpredClient = new JPred301Client(jpredws, af, null, opts);
+
+    af.getViewport().getCalcManager().startWorker(jpredClient);
+
+    do
+    {
+      try
+      {
+        Thread.sleep(50);
+      } catch (InterruptedException x)
+      {
+      }
+      ;
+    } while (af.getViewport().getCalcManager().isWorking());
+    AutoCalcSetting oldacs = af.getViewport().getCalcIdSettingsFor(
+            jpredClient.getCalcId());
+    String oldsettings = oldacs.getWsParamFile();
+    // write out parameters
+    jalview.gui.AlignFrame nalf = null;
+    assertTrue("Couldn't write out the Jar file",
+            new Jalview2XML(false).SaveAlignment(af,
+                    "testJPredWS_param.jar", "trial parameter writeout"));
+    assertTrue("Couldn't read back the Jar file", (nalf = new Jalview2XML(
+            false).LoadJalviewAlign("testJpredWS_param.jar")) != null);
+    if (nalf != null)
+    {
+      AutoCalcSetting acs = af.getViewport().getCalcIdSettingsFor(
+              jpredClient.getCalcId());
+      assertTrue("Calc ID settings not recovered from viewport stash",
+              acs.equals(oldacs));
+      assertTrue(
+              "Serialised Calc ID settings not identical to those recovered from viewport stash",
+              acs.getWsParamFile().equals(oldsettings));
+      JMenu nmenu = new JMenu();
+      new SequenceAnnotationWSClient()
+              .attachWSMenuEntry(nmenu, jpredws, af);
+      assertTrue("Couldn't get menu entry for service",
+              nmenu.getItemCount() > 0);
+      for (Component itm : nmenu.getMenuComponents())
+      {
+        if (itm instanceof JMenuItem)
+        {
+          JMenuItem i = (JMenuItem) itm;
+          if (i.getText().equals(
+                  jpredws.getAlignAnalysisUI().getAAconToggle()))
+          {
+            i.doClick();
+            break;
+          }
+        }
+      }
+      while (af.getViewport().isCalcInProgress())
+      {
+        try
+        {
+          Thread.sleep(200);
+        } catch (Exception x)
+        {
+        }
+        ;
+      }
+      AutoCalcSetting acs2 = af.getViewport().getCalcIdSettingsFor(
+              jpredClient.getCalcId());
+      assertTrue(
+              "Calc ID settings after recalculation has not been recovered.",
+              acs2.getWsParamFile().equals(oldsettings));
+    }
+  }
+}