Revision: 20776 http://sourceforge.net/p/jmol/code/20776 Author: hansonr Date: 2015-09-16 04:39:24 +0000 (Wed, 16 Sep 2015) Log Message: ----------- Jmol.___JmolVersion="14.3.16_2015.09.15"
bug fix: JSME's noncanonical SMILES does not match Jmol's standard SMILES for aromatics -- solution is to use /noncanonical/ with find(). bug fix: JSmol does not automatically start non-English language new feature: SMILES /noncanonical/ -- matches Jmol's aromatic with JSME's "noncanonical" aromatic $ load $aspirin $ show smiles c1=cc=cc2=c1C(O)=O.O2C(C)=O $ print {*}.find("smiles", "/noncanonical/C1=CC=CC(OC(C)=O)=C1C(O)=O") ({0:20}) $ print "c1=cc=cc2=c1C(O)=O.O2C(C)=O".find("smiles", "/aromaticImplied/C1=CC=CC(OC(C)=O)=C1C(O)=O") 13 new feature: {*}.find("SMILES", "NOAROMATIC") -- creates nonaromatic (noncanonical) SMILES new feature: JmolSQL x.select("...") -- shortcut for x.getProperty("[select ... ]") -- x should by an associative array {key:value,..} or a sequential array [a,b,c,d] new feature: JmolSQL {associative array}.select("xxx** where...") -- double * indicates desire to return full sub-arrays, with keys -- "all keys starting with "xxx"; -- uses LIKE -- can be at beginning or end or both: xxx**, **xxx, **xxx** -- WHERE clause is optional -- similar to [array].select("** where Modified Paths: -------------- trunk/Jmol/src/org/jmol/adapter/readers/cif/MMCifValidationParser.java trunk/Jmol/src/org/jmol/script/T.java trunk/Jmol/src/org/jmol/scriptext/MathExt.java trunk/Jmol/src/org/jmol/scriptext/SmilesExt.java trunk/Jmol/src/org/jmol/smiles/SmilesAtom.java trunk/Jmol/src/org/jmol/smiles/SmilesParser.java trunk/Jmol/src/org/jmol/smiles/SmilesSearch.java trunk/Jmol/src/org/jmol/util/Edge.java trunk/Jmol/src/org/jmol/util/GenericApplet.java trunk/Jmol/src/org/jmol/viewer/ActionManager.java trunk/Jmol/src/org/jmol/viewer/Jmol.properties trunk/Jmol/src/org/jmol/viewer/PropertyManager.java trunk/Jmol/src/org/jmol/viewer/Viewer.java Modified: trunk/Jmol/src/org/jmol/adapter/readers/cif/MMCifValidationParser.java =================================================================== --- trunk/Jmol/src/org/jmol/adapter/readers/cif/MMCifValidationParser.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/adapter/readers/cif/MMCifValidationParser.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -77,12 +77,17 @@ Lst<SV> hpins = hairpins.getList(); Lst<SV> loops = kloops.getList(); for (int i = loops.size(); --i >= 0;) { - Lst<SV> hlist = loops.get(i).getMap().get("hairpin_indices").getList(); - for (int j = hlist.size(); --j >= 0;) { - // mutate integer pointer to actual array using a pointer - SV item = hlist.get(j); - item.tok = T.hash; - item.value = hpins.get(item.intValue - 1).value; + Map<String, SV> kmap = loops.get(i).getMap(); + Lst<SV> khlist = kmap.get("hairpin_indices") + .getList(); + int n = khlist.size(); + if (n > 0) { + Lst<SV> khpins = new Lst<SV>(); + kmap.put("hairpins", SV.newV(T.varray, khpins)); + for (int j = n; --j >= 0;) + khpins.addLast(SV.newV(T.hash, + hpins.get(khlist.get(j).intValue - 1).value)); + } } } Modified: trunk/Jmol/src/org/jmol/script/T.java =================================================================== --- trunk/Jmol/src/org/jmol/script/T.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/script/T.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -798,7 +798,7 @@ public final static int intersection = 2 | 3 << 9 | mathfunc; public final static int prompt = 3 | 3 << 9 | mathfunc | mathExpressionCommand; public final static int random = 4 | 3 << 9 | mathfunc; - public final static int select = 5 | 3 << 9 | mathfunc | atomExpressionCommand; + public final static int select = 5 | 3 << 9 | mathfunc | mathproperty | atomExpressionCommand; // ___.xxx(a,b,c) Modified: trunk/Jmol/src/org/jmol/scriptext/MathExt.java =================================================================== --- trunk/Jmol/src/org/jmol/scriptext/MathExt.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/scriptext/MathExt.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -161,8 +161,9 @@ return evaluateUserFunction(mp, (String) op.value, args, op.intValue, op.tok == T.propselector); case T._: + case T.select: case T.getproperty: - return evaluateGetProperty(mp, args, tok == T._, op.tok == T.propselector); + return evaluateGetProperty(mp, args, tok, op.tok == T.propselector); case T.helix: return evaluateHelix(mp, args); case T.hkl: @@ -1263,44 +1264,44 @@ } /** - * _ by itself, not as a function, is shorthand for getProperty("auxiliaryInfo") + * _ by itself, not as a function, is shorthand for + * getProperty("auxiliaryInfo") * * $ print _.keys * - * boundbox - * group3Counts - * group3Lists - * modelLoadNote - * models - * properties - * someModelsHaveFractionalCoordinates - * someModelsHaveSymmetry - * someModelsHaveUnitcells - * symmetryRange + * boundbox group3Counts group3Lists modelLoadNote models properties + * someModelsHaveFractionalCoordinates someModelsHaveSymmetry + * someModelsHaveUnitcells symmetryRange * * - * _m by itself, not as a function, is shorthand for getProperty("auxiliaryInfo.models")[_currentFrame] + * _m by itself, not as a function, is shorthand for + * getProperty("auxiliaryInfo.models")[_currentFrame] * * $ print format("json",_m.unitCellParams) - * - * [ 0.0,0.0,0.0,0.0,0.0,0.0,0.0,-2.1660376,-2.1660376,0.0,-2.1660376,2.1660376,-4.10273,0.0,0.0,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN ] - * - * - * {atomset}._ by itself delivers a subset array of auxiliaryInfo.models for all models in {atomset} - * + * + * [ 0.0,0.0,0.0,0.0,0.0,0.0,0.0,-2.1660376,-2.1660376,0.0,-2.1660376, + * 2.1660376,-4.10273,0.0,0.0,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN ] + * + * + * {atomset}._ by itself delivers a subset array of auxiliaryInfo.models for + * all models in {atomset} + * * $ print {*}._..1..aflowInfo * * (first model's aflowInfo) * * - * _(key) prepends "auxiliaryInfo.models", delivering a modelCount-length array of information + * _(key) prepends "auxiliaryInfo.models", delivering a modelCount-length + * array of information * * $ print _("aflowInfo[SELECT auid WHERE H__eV___VASP_ < 0]") * * - * {atomset}._(key) selects for model Auxiliary info related to models of the specified atoms + * {atomset}._(key) selects for model Auxiliary info related to models of the + * specified atoms * - * {atomset}.getProperty(key) defaults to atomInfo, but also allows key to start with "bondInfo" + * {atomset}.getProperty(key) defaults to atomInfo, but also allows key to + * start with "bondInfo" * * Examples: * @@ -1316,12 +1317,16 @@ * @throws ScriptException */ private boolean evaluateGetProperty(ScriptMathProcessor mp, SV[] args, - boolean isAuxiliary, boolean isAtomProperty) + int tok0, boolean isAtomProperty) throws ScriptException { + boolean isSelect = (isAtomProperty && tok0 == T.select); + boolean isAuxiliary = (tok0 == T._); int pt = 0; int tok = (args.length == 0 ? T.nada : args[0].tok); - if (args.length == 2 && (tok == T.varray || tok == T.hash || tok == T.context)) { - return mp.addXObj(vwr.extractProperty(args[0].value, args[1].value.toString(), -1)); + if (args.length == 2 + && (tok == T.varray || tok == T.hash || tok == T.context)) { + return mp.addXObj(vwr.extractProperty(args[0].value, + args[1].value.toString(), -1)); } String propertyName = (args.length > 0 ? SV.sValue(args[pt++]) : ""); String lc = propertyName.toLowerCase(); @@ -1335,10 +1340,14 @@ SV x = null; if (isAtomProperty) { x = mp.getX(); - if (x.tok != T.bitset) - return mp.addXObj(vwr.extractProperty(x, propertyName, -1)); + if (x.tok != T.bitset) { + if (isSelect) + propertyName = "[SELECT " + propertyName + "]"; + return mp.addXObj(vwr.extractProperty(x, propertyName, -1)); + } } - if (isAtomProperty && !lc.startsWith("bondinfo") && !lc.startsWith("atominfo")) + if (isAtomProperty && !lc.startsWith("bondinfo") + && !lc.startsWith("atominfo")) propertyName = "atomInfo." + propertyName; Object propertyValue = ""; if (propertyName.equalsIgnoreCase("fileContents") && args.length > 2) { @@ -1372,7 +1381,7 @@ } if (isAuxiliary && !isAtomProperty) propertyName = "auxiliaryInfo.models." + propertyName; - propertyName = PT.rep(propertyName, ".[", "["); + propertyName = PT.rep(propertyName, ".[", "["); Object property = vwr.getProperty(null, propertyName, propertyValue); if (pt < args.length) property = vwr.extractProperty(property, args, pt); Modified: trunk/Jmol/src/org/jmol/scriptext/SmilesExt.java =================================================================== --- trunk/Jmol/src/org/jmol/scriptext/SmilesExt.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/scriptext/SmilesExt.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -172,14 +172,16 @@ boolean firstMatchOnly) throws ScriptException { // just retrieving the SMILES or bioSMILES string - if (pattern.length() == 0 || pattern.equals("H") || pattern.equals("*")) { + if (pattern.length() == 0 || pattern.equals("H") || pattern.equals("*") + || (firstMatchOnly = pattern.equalsIgnoreCase("NOAROMATIC"))) { try { return e.vwr.getSmilesOpt( bsSelected, 0, 0, flags | (pattern.equals("H") ? JC.SMILES_EXPLICIT_H : 0) - | (pattern.equals("*") ? JC.SMILES_TOPOLOGY : 0)); + | (pattern.equals("*") ? JC.SMILES_TOPOLOGY : 0) + | (firstMatchOnly ? JC.SMILES_NOAROMATIC : 0)); } catch (Exception ex) { e.evalError(ex.getMessage(), null); } Modified: trunk/Jmol/src/org/jmol/smiles/SmilesAtom.java =================================================================== --- trunk/Jmol/src/org/jmol/smiles/SmilesAtom.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/smiles/SmilesAtom.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -238,7 +238,7 @@ } } - if (count > 0) + if (count >= 0) missingHydrogenCount = count; return true; } @@ -474,6 +474,7 @@ } public int getExplicitHydrogenCount() { + System.out.println(this + " " + missingHydrogenCount); return missingHydrogenCount; } Modified: trunk/Jmol/src/org/jmol/smiles/SmilesParser.java =================================================================== --- trunk/Jmol/src/org/jmol/smiles/SmilesParser.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/smiles/SmilesParser.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -157,7 +157,9 @@ flags = 0; while (pattern.startsWith("/")) { String strFlags = getSubPattern(pattern, 0, '/').toUpperCase(); - pattern = pattern.substring(strFlags.length()); + pattern = pattern.substring(strFlags.length() + 2); + if (strFlags.indexOf("NONCANONICAL") >= 0) + flags |= Edge.FLAG_AROMATIC_NONCANONICAL; if (strFlags.indexOf("NOAROMATIC") >= 0) flags |= Edge.FLAG_NO_AROMATIC; if (strFlags.indexOf("AROMATICSTRICT") >= 0) Modified: trunk/Jmol/src/org/jmol/smiles/SmilesSearch.java =================================================================== --- trunk/Jmol/src/org/jmol/smiles/SmilesSearch.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/smiles/SmilesSearch.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -130,6 +130,7 @@ boolean invertStereochemistry; private boolean noAromatic; private boolean aromaticDouble; + private boolean noncanonical; void setAtomArray() { @@ -398,6 +399,7 @@ ignoreStereochemistry = ((flags & Edge.FLAG_IGNORE_STEREOCHEMISTRY) != 0); invertStereochemistry = ((flags & Edge.FLAG_INVERT_STEREOCHEMISTRY) != 0); noAromatic = ((flags & Edge.FLAG_NO_AROMATIC) != 0); + noncanonical = ((flags & Edge.FLAG_AROMATIC_NONCANONICAL) != 0); aromaticDouble = ((flags & Edge.FLAG_AROMATIC_DOUBLE) != 0); if (Logger.debugging && !isSilent) @@ -857,7 +859,8 @@ if (patternAtom.residueChar != null || patternAtom.elementNumber == -2) { char atype = a.getBioSmilesType(); char ptype = patternAtom.getBioSmilesType(); - char resChar = (patternAtom.residueChar == null ? '*' : patternAtom.residueChar.charAt(0)); + char resChar = (patternAtom.residueChar == null ? '*' + : patternAtom.residueChar.charAt(0)); boolean ok = true; boolean isNucleic = false; switch (ptype) { @@ -900,7 +903,8 @@ if (patternAtom.isBioAtom) { // BIOSMARTS // cross linking, residueChar, - if (patternAtom.notCrossLinked && a.getCrossLinkVector(null, true, true)) + if (patternAtom.notCrossLinked + && a.getCrossLinkVector(null, true, true)) break; } } else { @@ -911,8 +915,12 @@ // Check aromatic boolean isAromatic = patternAtom.isAromatic(); if (!noAromatic && !patternAtom.aromaticAmbiguous - && isAromatic != bsAromatic.get(iAtom)) - break; + && isAromatic != bsAromatic.get(iAtom)) { + if (!noncanonical + || patternAtom.getExplicitHydrogenCount() != + atom.getCovalentHydrogenCount()) + break; + } // <n> Check isotope if ((n = patternAtom.getAtomicMass()) != Integer.MIN_VALUE) { Modified: trunk/Jmol/src/org/jmol/util/Edge.java =================================================================== --- trunk/Jmol/src/org/jmol/util/Edge.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/util/Edge.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -104,6 +104,7 @@ public int index = -1; public int order; + final public static int FLAG_AROMATIC_NONCANONICAL = 64; final public static int FLAG_AROMATIC_DOUBLE = 32; final public static int FLAG_AROMATIC_DEFINED = 16; final public static int FLAG_AROMATIC_STRICT = 8; Modified: trunk/Jmol/src/org/jmol/util/GenericApplet.java =================================================================== --- trunk/Jmol/src/org/jmol/util/GenericApplet.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/util/GenericApplet.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -117,6 +117,9 @@ vwrOptions.put("applet", Boolean.TRUE); if (getJmolParameter("statusListener") == null) vwrOptions.put("statusListener", this); + language = getJmolParameter("language"); + if (language != null) + vwrOptions.put("language", language); viewer = new Viewer(vwrOptions); viewer.pushHoldRepaint(); String emulate = getValueLowerCase("emulate", "jmol"); @@ -128,8 +131,6 @@ for (CBK item : CBK.values()) setValue(item.name() + "Callback", null); loading = false; - language = getJmolParameter("language"); - new GT(viewer, language); if (language != null) System.out.print("requested language=" + language + "; "); doTranslate = (!"none".equals(language) && getBooleanValue("doTranslate", Modified: trunk/Jmol/src/org/jmol/viewer/ActionManager.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/ActionManager.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/viewer/ActionManager.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -75,6 +75,8 @@ */ public void setViewer(Viewer vwr, String commandOptions) { this.vwr = vwr; + if (!vwr.isJS) + createActions(); setBinding(jmolBinding = new JmolBinding()); LEFT_CLICKED = Binding.getMouseAction(1, Binding.LEFT, Event.CLICKED); LEFT_DRAGGED = Binding.getMouseAction(1, Binding.LEFT, Event.DRAGGED); @@ -221,7 +223,9 @@ actionNames[i] = name; } - static { + void createActions() { + if (actionInfo[ACTION_assignNew] != null) + return; // OK for J2S because actionInfo and actionNames are both private newAction(ACTION_assignNew, "_assignNew", GT.o(GT._( "assign/new atom or bond (requires {0})"), @@ -343,7 +347,6 @@ ._("spin model (swipe and release button and stop motion simultaneously)")); newAction(ACTION_translate, "_translate", GT._("translate")); newAction(ACTION_wheelZoom, "_wheelZoom", GT._("zoom")); - } public static String getActionName(int i) { Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Jmol.properties 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties 2015-09-16 04:39:24 UTC (rev 20776) @@ -56,10 +56,49 @@ appear at a different location. An interesting challenge to figure out the algorithm that still places it in the "proper" place even when perspective is on. (Daniele Tomerini) + TODO: image off stops JSmol -Jmol.___JmolVersion="14.3.16_2015.09.14" +TODO: Switch to dssr1 prior to full release +TODO: JSME/JSmol 2D/3D can sometimes reload the model +TODO: Smol, you mean then. Applet or Application? language issue + +Jmol.___JmolVersion="14.3.16_2015.09.15" + +bug fix: JSME's noncanonical SMILES does not match Jmol's standard SMILES for aromatics + -- solution is to use /noncanonical/ with find(). + +bug fix: JSmol does not automatically start non-English language + +new feature: SMILES /noncanonical/ + -- matches Jmol's aromatic with JSME's "noncanonical" aromatic + $ load $aspirin + $ show smiles + c1=cc=cc2=c1C(O)=O.O2C(C)=O + $ print {*}.find("smiles", "/noncanonical/C1=CC=CC(OC(C)=O)=C1C(O)=O") + ({0:20}) + $ print "c1=cc=cc2=c1C(O)=O.O2C(C)=O".find("smiles", "/aromaticImplied/C1=CC=CC(OC(C)=O)=C1C(O)=O") + 13 + +new feature: {*}.find("SMILES", "NOAROMATIC") + -- creates nonaromatic (noncanonical) SMILES + +new feature: JmolSQL x.select("...") + -- shortcut for x.getProperty("[select ... ]") + -- x should by an associative array {key:value,..} or a sequential array [a,b,c,d] + +new feature: JmolSQL {associative array}.select("xxx** where...") + -- double * indicates desire to return full sub-arrays, with keys + -- "all keys starting with "xxx"; + -- uses LIKE + -- can be at beginning or end or both: xxx**, **xxx, **xxx** + -- WHERE clause is optional + -- similar to [array].select("** where + +JmolVersion="14.3.16_2015.09.14" + +bug fix: load data [2D mol structure...] filter "2D" not working bug fix: Crystallographic Open Database CIF files have unusual atom_site_label ("MgT") and no atom_site_type_symbol, leading to missing element assignments Modified: trunk/Jmol/src/org/jmol/viewer/PropertyManager.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -433,23 +433,39 @@ boolean asMap = false; if (arg.tok == T.select) { key = arg.myName; - if (key.equals("**")) { - key = ""; + if (key.contains("**")) { + boolean isAll = key.equals("**"); + key = PT.rep(key, "**", "*"); + String newKey = ""; for (Entry<?, ?> e : h.entrySet()) { String k = (String) e.getKey(); + if (!isAll && !PT.isLike(k, key)) + continue; Object o = e.getValue(); if ((o instanceof SV)) { o = ((SV) o).getMap(); - } else if (!(o instanceof Map<?,?>)){ + } else if (!(o instanceof Map<?, ?>)) { o = null; } - if (o != null && vwr.checkSelect((Map<String, SV>) o, (T[]) arg.value)) - key += "," + k; + /* +Token[string(4/0x4) value="[select bond** where outliers.value<-7.2]"] +Token[keyword(17/0x10100011) value=")"] + +END + +checkSelect invalid argument +---- + e_x_p_r_e_s_s_i_o_n = outliers . value <<<<< << -7.2 */ + if (o != null + && vwr.checkSelect((Map<String, SV>) o, (T[]) arg.value)) + newKey += "," + k; } - if (key.length() == 0) + if (newKey.length() == 0) return ""; + key = newKey; asMap = true; - } else if (!vwr.checkSelect((Map<String, SV>) property, (T[]) arg.value)) + } else if (!vwr.checkSelect((Map<String, SV>) property, + (T[]) arg.value)) return ""; } else { key = arg.asString(); @@ -460,16 +476,21 @@ return extractProperty(keys, args, ptr, null, true); } } - boolean isWild = (key.startsWith("*") || key.endsWith("*") || key.indexOf(",") >= 0); - if (isWild && v2 == null) - v2 = new Lst<Object>(); - if (isWild && key.length() == 1) { - if (ptr == ((SV[]) args).length) { - v2.addLast(property); - return v2; - } - return extractProperty(property, args, ptr, v2, true); + boolean isWild = (key.startsWith("*") || key.endsWith("*") || key + .indexOf(",") >= 0); + if (isWild) { + if (v2 == null) + v2 = new Lst<Object>(); + if (key.length() == 1) { + if (ptr == ((SV[]) args).length) { + v2.addLast(property); + return v2; + } + return extractProperty(property, args, ptr, v2, true); + } } + if (key.contains("**")) + key = PT.rep(key, "**", "*") + ","; if (key.contains(",")) { if (asMap) key = key.substring(1); Modified: trunk/Jmol/src/org/jmol/viewer/Viewer.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Viewer.java 2015-09-14 22:41:00 UTC (rev 20775) +++ trunk/Jmol/src/org/jmol/viewer/Viewer.java 2015-09-16 04:39:24 UTC (rev 20776) @@ -515,6 +515,7 @@ tm = TransformManager.getTransformManager(this, Integer.MAX_VALUE, 0, is4D); slm = new SelectionManager(this); if (haveDisplay) { + // must have language by now, as ActionManager uses GT._() acm = (multiTouch ? (ActionManager) Interface.getOption( "multitouch.ActionManagerMT", null, null) : new ActionManager()); acm.setViewer(this, @@ -573,6 +574,10 @@ } else if (!isJS) { logFilePath = null; } + new GT(this, (String) info.get("language")); + // deferred here so that language is set + if (isJS) + acm.createActions(); } else { // not an applet -- used to pass along command line options gdata This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Monitor Your Dynamic Infrastructure at Any Scale With Datadog! Get real-time metrics from all of your servers, apps and tools in one place. SourceForge users - Click here to start your Free Trial of Datadog now! http://pubads.g.doubleclick.net/gampad/clk?id=241902991&iu=/4140 _______________________________________________ Jmol-commits mailing list Jmol-commits@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jmol-commits