UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,154 lines (1,098 loc) 33 kB
/** * @fileoverview * Implementation of repository for editor. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /core/kekule.common.js * requires /render/kekule.render.base.js * requires /render/kekule.render.extensions.js * requires /render/kekule.render.kekule.render.utils.js * requires /widgets/chem/editor/kekule.chemEditor.extensions.js */ (function(){ "use strict"; /** * Base repository item class. * @class * @augments ObjectEx * * @property {String} name The unique name of this repository item. */ Kekule.Editor.AbstractRepositoryItem = Class.create(ObjectEx, /** @lends Kekule.Editor.AbstractRepositoryItem# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.AbstractRepositoryItem', /** @construct */ initialize: function(/*$super*/) { this.tryApplySuper('initialize') /* $super() */; }, /** @private */ initProperties: function() { //this.defineProp('repository', {'dataType': 'Kekule.ChemObject', 'serializable': false}); this.defineProp('name', {'dataType': DataType.STRING}); }, /** * Returns this repository item can be used in 2D/3D or both editor. * @returns {Array} */ getAvailableCoordModes: function() { return []; }, /** * Returns chem objects which can stored in this repository. * All objects will be be inserted into chem editor when execute this repository item. * @param {Kekule.ChemObject} targetObj Existing object in editor that the repository objects based on. * Set it to null to indicate that the repository object should be created in blank space. * @param {Hash} options Options to create new objects. Different repository item may has different options. * @returns {Hash} A hash object that should containing the following fields: * { * objects: Array, identical. Objects created. * mergeObj: Kekule.ChemObject, optional. If created objects can merge with targetObj, this field decide the merge part. * mergeDest: Kekule.ChemObject, optional. Which object should this repository item merge to. If not set, means merge to targetObj. * baseObjCoord: The base object system coord to rotate objects added to editor after inserting immediately. * //centerCoord: Center coord of repository object. * } */ createObjects: function(targetObj, options) { // do nothing here }, /** * Returns default scale level of this repository object when adding to editor. * @returns {Float} */ getScale: function() { return this.doGetScale() || 1; }, /** @private */ doGetScale: function() { return 1; }, /** * Returns ref length to adjust repository object coord and size in editor. * @returns {Float} */ getRefLength: function() { return this.doGetRefLength() / (this.getScale() || 1); }, /** @private */ doGetRefLength: function() { // do nothing here }, /** * Returns if repository object created is one molecule (structure fragment). * @returns {Bool} */ isOneStructureFragmentObj: function() { return false; } }); /** * A base class for molecule structure based repository item. * @class * @augments Kekule.Editor.AbstractRepositoryItem * * @property {Hash} molManipulationCenterCoord The center 2D coord in rotation in editor. Set to null to use the default one. * @property {Hash} molManipulationDefDirectionCoord The default reference direction vector (from center) during rotation in editor. */ Kekule.Editor.MolRepositoryItem2D = Class.create(Kekule.Editor.AbstractRepositoryItem, /** @lends Kekule.Editor.MolRepositoryItem2D# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.MolRepositoryItem2D', /** @construct */ initialize: function(/*$super*/) { this.tryApplySuper('initialize') /* $super() */; }, /** @private */ initProperties: function() { this.defineProp('molScale', {'dataType': DataType.FLOAT}); this.defineProp('molManipulationCenterCoord', {'dataType': DataType.HASH}); this.defineProp('molManipulationDefDirectionCoord', {'dataType': DataType.HASH}); }, /** @ignore */ doGetScale: function(/*$super*/) { return this.getMolScale() || this.tryApplySuper('doGetScale') /* $super() */; }, /** @ignore */ getAvailableCoordModes: function() { return Kekule.CoordMode.COORD2D; // only support 2D }, /** @ignore */ isOneStructureFragmentObj: function() { return true; }, /** @ignore */ createObjects: function(targetObj, options) { var mol = this.doCreateObjects(targetObj, options); if (!mol) return null; var mergeObj = null; var mergeDest = null; var baseCoord = {'x': 0, 'y': 0}; if (targetObj) // targetObj not empty, and a new mol is created, need to merge { mergeObj = (targetObj instanceof Kekule.ChemStructureNode)? this.doGetMergableNode(mol, targetObj): (targetObj instanceof Kekule.ChemStructureConnector)? this.doGetMergableConnector(mol, targetObj): null; if (mergeObj) { mergeDest = targetObj; baseCoord = mergeObj.getAbsBaseCoord2D(); } } return { 'objects': [mol], 'mergeObj': mergeObj, 'mergeDest': mergeDest, 'baseObjCoord': baseCoord, 'centerCoord': {'x': 0, 'y': 0} }; }, /** * Do actual work of createObjects. * Descendants should override this method. * @param {Object} targetObj * @private */ doCreateObjects: function(targetObj, options) { return null; // do nothing here }, /** * Returns node in repository molecule that need to be merged with target. * Descendants should override this method. * @returns {Kekule.ChemStructureNode} * @private */ doGetMergableNode: function(mol, targetNode) { return null; }, /** * Returns connector in repository molecule that need to be merged with target. * Descendants should override this method. * @returns {Kekule.ChemStructureConnector} * @private */ doGetMergableConnector: function(mol, targetNode) { return null; } }); /** * A class to store predefined molecule template. * @class * @augments Kekule.Editor.MolRepositoryItem2D * @param {Variant} structData Source data of structfragment. * @param {String} dataFormat Format id of structData. * * @property {Variant} structData Source data of structfragment. * @property {String} dataFormat Format id of structData. */ Kekule.Editor.StoredStructFragmentRepositoryItem2D = Class.create(Kekule.Editor.MolRepositoryItem2D, /** @lends Kekule.Editor.StoredStructFragmentRepositoryItem2D# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.StoredStructFragmentRepositoryItem2D', /** @construct */ initialize: function(/*$super, */structData, dataFormat, defScale) { this.tryApplySuper('initialize') /* $super() */; this.beginUpdate(); try { if (dataFormat) { this.setDataFormat(dataFormat); } if (structData) // create stored structfragment { this.setStructData(structData); } if (defScale) this.setMolScale(defScale); } finally { this.endUpdate(); } }, /** @private */ initProperties: function() { this.defineProp('structureFragment', {'dataType': 'Kekule.StructureFragment', 'setter': null, 'serializable': false}); this.defineProp('structData', {'dataType': DataType.STRING}); this.defineProp('dataFormat', {'dataType': DataType.STRING}); }, /** @ignore */ objectChange: function(/*$super, */modifiedProps) { this.tryApplySuper('objectChange', [modifiedProps]) /* $super(modifiedProps) */; if ((modifiedProps.indexOf('structData') >= 0) || (modifiedProps.indexOf('dataFormat') >= 0)) { var structFragment = null; if (this.getStructData()) structFragment = this._createTemplateStructFragment(); this.setPropStoreFieldValue('structureFragment', structFragment); } }, /** @private */ _createTemplateStructFragment: function() { var structFragment = null; if (this.getStructData()) structFragment = Kekule.IO.loadFormatData(this.getStructData(), this.getDataFormat() || Kekule.IO.DataFormat.KEKULE_JSON); if (structFragment) { // adjust coord this._adjustTemplateStructureFragmentCoords(structFragment); } return structFragment; }, /** @private */ _adjustTemplateStructureFragmentCoords: function(structFragment) { structFragment.setCoord2D({'x': 0, 'y': 0}); var objBox = Kekule.Render.ObjUtils.getContainerBox(structFragment, Kekule.CoordMode.COORD2D, true); if (objBox) { var oldObjCoord = structFragment.getCoordOfMode ? structFragment.getCoordOfMode(Kekule.CoordMode.COORD2D, true) || {} : {}; var delta = {}; delta.x = -(objBox.x2 + objBox.x1) / 2; delta.y = -(objBox.y2 + objBox.y1) / 2; /* var newObjCoord = Kekule.CoordUtils.add(oldObjCoord, delta); if (structFragment.setCoordOfMode) structFragment.setCoordOfMode(newObjCoord, Kekule.CoordMode.COORD2D); */ // transform coords of children structFragment.beginUpdate(); try { for (var i = 0, l = structFragment.getNodeCount(); i < l; ++i) { var node = structFragment.getNodeAt(i); var coord = node.getCoord2D(true); coord = Kekule.CoordUtils.add(coord, delta); node.setCoord2D(coord); } } finally { structFragment.endUpdate(); } } }, /** @ignore */ doGetRefLength: function() { var mol = this.getStructureFragment(); return mol? mol.getConnectorLengthMedian(Kekule.CoordMode.COORD2D, true): 0; }, /** @ignore */ doCreateObjects: function(targetObj, options) { var structFragment = this.getStructureFragment(); return structFragment? structFragment.clone(): null; }, /** @ignore */ doGetMergableNode: function(mol, targetNode) { return mol.getAnchorNodeAt(0) || mol.getNodeAt(0); }, /** @ignore */ doGetMergableConnector: function(mol, targetNode) { return null; } }); /** * A class to store predefined subgroup templates. * @class * @augments Kekule.Editor.StoredStructFragmentRepositoryItem2D * @param {Variant} structData Source data of structfragment. * @param {String} dataFormat Format id of structData. * * @property {Variant} structData Source data of structfragment. * @property {String} dataFormat Format id of structData. * @property {Array} inputTexts Strings that can direcly used to be insert subgroup, * e.g, COOH and CO2H can both input a carboxyl. */ Kekule.Editor.StoredSubgroupRepositoryItem2D = Class.create(Kekule.Editor.StoredStructFragmentRepositoryItem2D, /** @lends Kekule.Editor.StoredSubgroupRepositoryItem2D# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.StoredSubgroupRepositoryItem2D', /** @construct */ initialize: function(/*$super, */structData, dataFormat, defScale) { this.tryApplySuper('initialize', [structData, dataFormat, defScale]) /* $super(structData, dataFormat, defScale) */; }, /** @private */ initProperties: function() { //this.defineProp('abbr', {'dataType': DataType.STRING}); this.defineProp('inputTexts', {'dataType': DataType.STRING}); }, /** @ignore */ doCreateObjects: function(targetObj, options) { var structFragment = this.getStructureFragment(); if (structFragment) { var result = new Kekule.SubGroup(); // always returns sub group even if structureFragment is in molecule class result.assign(structFragment, false); // do not copy id return result; } else return null; }, /** @ignore */ createObjects: function(/*$super, */targetObj) { var result = this.tryApplySuper('createObjects', [targetObj]) /* $super(targetObj) */; return result; /* var mol = result.objects[0]; if (mol && mol.getAnchorNodeCount() >= 0) { var coord = (mol.getAnchorNodeAt(0) || mol.getNodeAt(0)).getAbsCoord2D(true); result.baseObjCoord = coord; } return result; */ }, /** @ignore */ _adjustTemplateStructureFragmentCoords: function(structFragment) { structFragment.setCoord2D({'x': 0, 'y': 0}); var anchorNode = structFragment.getCurrConnectableObj(); if (anchorNode) { var delta = Kekule.CoordUtils.substract({x: 0, y: 0}, anchorNode.getCoord2D(true)); // transform coords of children structFragment.beginUpdate(); try { for (var i = 0, l = structFragment.getNodeCount(); i < l; ++i) { var node = structFragment.getNodeAt(i); var coord = node.getCoord2D(true); coord = Kekule.CoordUtils.add(coord, delta); node.setCoord2D(coord); } } finally { structFragment.endUpdate(); } } } }); // utils functions Kekule.Editor.StoredSubgroupRepositoryItem2D.getRepItemOfInputText = function(inputText) { var repItems = Kekule.Editor.RepositoryItemManager.getAllItems(Kekule.Editor.StoredSubgroupRepositoryItem2D) || []; for (var i = 0, l = repItems.length; i < l; ++i) { var repItem = repItems[i]; if ((repItem.getInputTexts() || []).indexOf(inputText) >= 0) return repItem; } return null; }; Kekule.Editor.StoredSubgroupRepositoryItem2D.getAllRepItems = function() { return Kekule.Editor.RepositoryItemManager.getAllItems(Kekule.Editor.StoredSubgroupRepositoryItem2D) || []; }; /** * A base class to generate ring structure based repository item. * @class * @augments Kekule.Editor.MolRepositoryItem2D * @param {Int} ringAtomCount Atom count on ring. * @param {Float} bondLength Default bond length to generate ring. * * @property {Int} ringAtomCount Atom count on ring. * @property {Float} bondLength Default bond length to generate ring. * @property {Bool} isAromatic Whether this ring is a aromatic one (single/double bond intersect), * @property {Bool} enableCoordCache */ Kekule.Editor.MolRingRepositoryItem2D = Class.create(Kekule.Editor.MolRepositoryItem2D, /** @lends Kekule.Editor.MolRingRepositoryItem2D# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.MolRingRepositoryItem2D', /** @construct */ initialize: function(/*$super, */ringAtomCount, bondLength) { this.tryApplySuper('initialize') /* $super() */; this.setRingAtomCount(ringAtomCount); this.setBondLength(bondLength || 1); }, /** @private */ initProperties: function() { this.defineProp('ringAtomCount', {'dataType': DataType.INT}); this.defineProp('bondLength', {'dataType': DataType.FLOAT, 'setter': function(value) { if (value !== this.getBondLength()) { this.setPropStoreFieldValue('bondLength', value); //this._clearCoordCache(); } } }); this.defineProp('isAromatic', {'dataType': DataType.BOOL}); this.defineProp('enableCoordCache', {'dataType': DataType.BOOL, 'setter': function(value) { if (value !== this.getEnableCoordCache()) { this.setPropStoreFieldValue('enableCoordCache', value); //console.log('set cache', value, this._coordCache); if (value && !this._coordCache) this._initCoordCache(); } } }); /* this.defineProp('ring', {'dataType': 'Kekule.StructureFragment', 'serializable': false, 'setter': null, 'getter': function() { var result = this.getPropStoreFieldValue('ring'); if (!result) { result = this.generateRing(); this.setPropStoreFieldValue('ring', result); } return result; } }); */ }, /* @private */ /* objectChange: function($super, modifiedProps) { $super(modifiedProps); if ((modifiedProps.indexOf('ringAtomCount') >= 0) || (modifiedProps.indexOf('bondLength') >= 0)) this.setPropStoreFieldValue('ring', null); }, */ /** @ignore */ doGetRefLength: function() { return this.getBondLength(); }, /** @ignore */ doCreateObjects: function(targetObj, options) { var orginalMol = options && options.originalStructFragment; return this.generateRing(orginalMol); }, /** @ignore */ doGetMergableNode: function(mol, targetNode) { return mol.getNodeAt(0); }, /** @ignore */ doGetMergableConnector: function(mol, targetNode) { return mol.getConnectorAt(0); }, /** * Generate a ring structure fragment either in targetMol * or a new molecule based on newStructFragmentClass if targetMol is null. * @private */ generateRing: function(targetMol, newStructFragmentClass) { if (!newStructFragmentClass) newStructFragmentClass = Kekule.Molecule; var mol; //var children = []; if (targetMol) mol = targetMol; else mol = new newStructFragmentClass(); var atomCount = this.getRingAtomCount(); mol.beginUpdate(); try { mol.setCoord2D({'x': 0, 'y': 0}); // important, ensure the old molecule coord does not affect new insertion of object // remove unneed mol node and connectors var oldNodeCount = mol.getNodeCount(); if (oldNodeCount > atomCount) // remove unwanted nodes { for (var i = oldNodeCount - 1; i >= atomCount; --i) mol.removeNodeAt(i); } var oldConnectorCount = mol.getConnectorCount(); for (var i = oldConnectorCount - 1; i >= 0; --i) // remove all old bonds mol.removeConnectorAt(i); var coordCacheEnabled = this.getEnableCoordCache(); var hasCoordCache = coordCacheEnabled && this._hasCoordCache(atomCount); //console.log(atomCount, this._hasCoordCache(atomCount), coordCacheEnabled, this._coordCache[atomCount]); var atomCoords; if (!hasCoordCache) // has no cache, need to calculate atomCoords = this._calcAtomCoords(atomCount); else atomCoords = this._getCachedCoords(atomCount); var bondLength = this.getBondLength(); var lastAtom; var lastAtomIndex; var firstAtom; // generate atoms and bonds for (var i = 0; i < atomCount; ++i) { var atom = mol.getNodeAt(i); if (!atom) { atom = this._generateAtom(i); mol.appendNode(atom); } var atomCoord; var coord = atomCoords[i]; if (bondLength !== 1) coord = Kekule.CoordUtils.multiply(coord); atom.setCoord2D(coord); //children.push(atom); if (i === 0) firstAtom = atom; // connect with bond if (lastAtom) { var bond = this._generateBond(lastAtomIndex, i); mol.appendConnector(bond); bond.appendConnectedObj(lastAtom); bond.appendConnectedObj(atom); //children.push(bond); } lastAtom = atom; lastAtomIndex = i; } // seal the last bond var bond = this._generateBond(atomCount - 1, 0); mol.appendConnector(bond); bond.appendConnectedObj(lastAtom); bond.appendConnectedObj(firstAtom); //children.push(bond); } finally { mol.endUpdate(); } /* if (!targetMol) return mol; else return children; */ return mol; }, /** @private */ _generateAtom: function(atomIndex) { return new Kekule.Atom(null, 'C'); // default use C element }, /** @private */ _generateBond: function(atomIndex1, atomIndex2) { var bondOrder = Kekule.BondOrder.SINGLE; if (this.getIsAromatic()) { if (atomIndex1 % 2) // even index bondOrder = Kekule.BondOrder.DOUBLE; } return new Kekule.Bond(null, null, bondOrder); }, /** @private */ _calcAtomCoords: function(atomCount) { var bondLength = 1; var coordCacheEnabled = this.getEnableCoordCache(); var halfCenterAngle = Math.PI / atomCount; var sinHalfCenterAngle = Math.sin(halfCenterAngle); //var cosHalfCenterAngle = Math.sin(halfCenterAngle); var centerAngle = 2 * halfCenterAngle; //var bondAngle = Math.PI - centerAngle; var centerAtomLength = bondLength / 2 / sinHalfCenterAngle; var startingAngle = ((atomCount === 4) || (atomCount === 8)) ? -Math.PI / 2 - centerAngle / 2 : -Math.PI / 2; var currAngle = startingAngle; var result = []; for (var i = 0; i < atomCount; ++i) { // set atom coord var x = centerAtomLength * Math.cos(currAngle); var y = centerAtomLength * Math.sin(currAngle); var coord = {'x': x, 'y': y}; result.push(coord); if (coordCacheEnabled) { this._setCoordToCache(coord, atomCount, i); } currAngle += centerAngle; } return result; }, /** @private */ _initCoordCache: function() { //console.log('init cache'); this._coordCache = []; // fill some common ring caches for (var i = 3; i < 13; ++i) { //console.log('pre calc', i); this._calcAtomCoords(i); } }, /** @private */ _clearCoordCache: function() { this._coordCache = []; }, /** @private */ _setCoordToCache: function(coord, atomCount, atomIndex) { //console.log('') var cache = this._coordCache[atomCount]; if (!cache) { cache = []; this._coordCache[atomCount] = cache; } cache[atomIndex] = coord; }, /** @private */ _getCachedCoord: function(atomCount, atomIndex) { var cache = this._coordCache[atomCount]; return cache && cache[atomIndex] ; }, /** @private */ _getCachedCoords: function(atomCount) { return this._coordCache[atomCount]; }, /** @private */ _hasCoordCache: function(atomCount) { return !!(this._coordCache && this._coordCache[atomCount]); } }); /** * A repository class to generate lone carbon chains. * @class * @augments Kekule.Editor.MolRepositoryItem2D * @param {Int} atomCount Atom count on chain. * @param {Float} bondLength Default bond length to generate chain. * * @property {Int} atomCount Atom count on chain. * @property {Float} bondLength Default bond length to generate chain. * @property {Bool} negativeDirection If true, the chain will be created as "VVVVV", else "^^^^^". */ Kekule.Editor.MolChainRepositoryItem2D = Class.create(Kekule.Editor.MolRepositoryItem2D, /** @lends Kekule.Editor.MolChainRepositoryItem2D# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.MolChainRepositoryItem2D', /** @private */ STEP_X_INC: Math.cos(30 / 180 * Math.PI), /** @private */ STEP_Y_INC: Math.sin(30 / 180 * Math.PI), /** @construct */ initialize: function(/*$super, */atomCount, bondLength) { this.tryApplySuper('initialize') /* $super() */; this.setAtomCount(atomCount); this.setBondLength(bondLength || 1); }, /** @private */ initProperties: function() { this.defineProp('atomCount', {'dataType': DataType.INT}); this.defineProp('bondLength', {'dataType': DataType.FLOAT}); this.defineProp('negativeDirection', {'dataType': DataType.BOOL}); this.defineProp('enableCoordCache', {'dataType': DataType.BOOL, 'setter': function(value) { if (value !== this.getEnableCoordCache()) { this.setPropStoreFieldValue('enableCoordCache', value); //console.log('set cache', value, this._coordCache); if (value && !this._coordCache) this._initCoordCache(); } } }); }, /** @ignore */ doGetRefLength: function() { return this.getBondLength(); }, /** @ignore */ doCreateObjects: function(targetObj, options) { var orginalMol = options && options.originalStructFragment; return this.generateChain(orginalMol); }, /** @ignore */ doGetMergableNode: function(mol, targetNode) { return mol.getNodeAt(0); }, /** @ignore */ doGetMergableConnector: function(mol, targetNode) { //return mol.getConnectorAt(0); return null; // TODO: now has bugs when flex chain is created on an existing bond }, /** * Generate a chain structure fragment either in targetMol * or a new molecule based on newStructFragmentClass if targetMol is null. * @private */ generateChain: function(targetMol, newStructFragmentClass) { if (!newStructFragmentClass) newStructFragmentClass = Kekule.Molecule; var mol = targetMol || new newStructFragmentClass(); var atomCount = this.getAtomCount(); var bondLength = this.getBondLength() || 1; /* var atomCoords = this._getCachedCoord(atomCount); if (!atomCoords) // has no cache, need to calculate { atomCoords = this._calcAtomCoords(atomCount); } */ //console.log('fetch atom coords', atomCoords); /* var deltaCoord = {'x': this.STEP_X_INC * bondLength, 'y': this.STEP_Y_INC * bondLength}; if (this.getNegativeDirection()) deltaCoord.y = -deltaCoord.y; */ var negativeDirection = this.getNegativeDirection(); mol.beginUpdate(); try { mol.setCoord2D({'x': 0, 'y': 0}); // important, ensure the old molecule coord does not affect new insertion of object /* var currCoord = {'x': 0, 'y': 0}; var ySign = 1; */ var oldNodeCount = mol.getNodeCount(); if (oldNodeCount > atomCount) // remove unwanted nodes { for (var i = oldNodeCount - 1; i >= atomCount; --i) mol.removeNodeAt(i); } var oldConnectorCount = mol.getConnectorCount(); if (oldConnectorCount > atomCount - 1) { for (var i = oldConnectorCount - 1; i >= atomCount - 1; --i) mol.removeConnectorAt(i); } var lastAtom, currAtom, bond; for (var i = 0, l = atomCount; i < l; ++i) { lastAtom = currAtom; var atomCreated = false; var currAtom = mol.getNodeAt(i); if (!currAtom) { currAtom = this._generateAtom(i); atomCreated = true; } //currAtom.setCoord2D(currCoord); var coord = this._getCachedCoord(i); if (!coord) { coord = this._calcAtomCoord(i); } if (bondLength !== 1) coord = Kekule.CoordUtils.multiply(coord, bondLength); if (negativeDirection) coord = {'x': coord.x, 'y': -coord.y}; // avoid change cached coord directly currAtom.setCoord2D(coord); if (atomCreated) mol.appendNode(currAtom); if (lastAtom && atomCreated) { bond = this._generateBond(i - 1, i); mol.appendConnector(bond); bond.appendConnectedObj(lastAtom); bond.appendConnectedObj(currAtom); } /* currCoord = {'x': currCoord.x + deltaCoord.x, 'y': currCoord.y + deltaCoord.y * ySign}; ySign = -ySign; */ } } finally { mol.endUpdate(); } // set manipulate center object //this.setMolManipulationCenterObj(mol.getNodeAt(0)); this.setMolManipulationCenterCoord({'x': 0, 'y': 0}); this.setMolManipulationDefDirectionCoord({'x': 1, 'y': 0}); return mol; }, /** @private */ _generateAtom: function(atomIndex) { return new Kekule.Atom(null, 'C'); // default use C element }, /** @private */ _generateBond: function(atomIndex1, atomIndex2) { var bondOrder = Kekule.BondOrder.SINGLE; /* if (this.getIsAromatic()) { if (atomIndex1 % 2) // even index bondOrder = Kekule.BondOrder.DOUBLE; } */ return new Kekule.Bond(null, null, bondOrder); }, /** @private */ _calcAtomSetCoords: function(atomCount) { var coordCacheEnabled = this.getEnableCoordCache(); var bondLength = 1; var deltaCoord = {'x': this.STEP_X_INC * bondLength, 'y': this.STEP_Y_INC * bondLength}; var result = []; var currCoord = {'x': 0, 'y': 0}; result.push(currCoord); this._coordCache[0] = currCoord; var lastAtom, currAtom, bond; for (var i = 1, l = atomCount; i < l; ++i) { currCoord = {'x': currCoord.x + deltaCoord.x, 'y': (i % 2)? deltaCoord.y: 0}; result.push(currCoord); if (coordCacheEnabled) this._coordCache[i] = currCoord; } //console.log(atomCount, result); return result; }, /** @private */ _calcAtomCoord: function(atomIndex) { var bondLength = 1; var deltaCoord = {'x': this.STEP_X_INC * bondLength, 'y': this.STEP_Y_INC * bondLength}; var result = { 'x': deltaCoord.x * atomIndex, 'y': (atomIndex % 2)? deltaCoord.y: 0 }; if (this._coordCache) this._coordCache[atomIndex] = result; return result; }, /** @private */ _initCoordCache: function() { //console.log('init cache'); this._coordCache = []; // fill some common chain caches var coords = this._calcAtomSetCoords(30); }, /** @private */ _clearCoordCache: function() { this._coordCache = []; }, /** @private */ _getCachedCoord: function(atomIndex) { return this._coordCache && this._coordCache[atomIndex]; } }); /** * A base class to generate path glyphs. * @class * @augments Kekule.Editor.AbstractRepositoryItem * @param {Class} glyphClass Class to create glyph. * @param {Float} glyphRefLength Default length to generate glyph. * @property {Hash} glyphInitialParams Initial params to create glyph. * * @property {Class} glyphClass Class to create glyph. * @property {Float} glyphRefLength Default length to generate glyph. * @property {Hash} glyphInitialParams Initial params to create glyph. */ Kekule.Editor.PathGlyphRepositoryItem2D = Class.create(Kekule.Editor.AbstractRepositoryItem, /** @lends Kekule.Editor.PathGlyphRepositoryItem2D# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.PathGlyphRepositoryItem2D', /** @construct */ initialize: function(/*$super, */glyphClass, glyphRefLength, glyphInitialParams) { this.tryApplySuper('initialize') /* $super() */; this.setGlyphClass(glyphClass); this.setGlyphRefLength(glyphRefLength || 1); this.setGlyphInitialParams(glyphInitialParams); }, /** @private */ initProperties: function() { this.defineProp('glyphRefLength', {'dataType': DataType.FLOAT}); this.defineProp('glyphClass', {'dataType': DataType.CLASS, 'serializable': false}); this.defineProp('glyphInitialParams', {'dataType': DataType.HASH}); }, /** @ignore */ getAvailableCoordModes: function() { return Kekule.CoordMode.COORD2D; // only support 2D }, /** @ignore */ doGetRefLength: function() { return this.getGlyphRefLength(); }, /** @ignore */ createObjects: function(targetObj) { var glyph = this.generateGlyph(); var baseCoord = {'x': 0, 'y': 0}; return { 'objects': [glyph], 'mergeObj': null, 'mergeDest': null, 'baseObjCoord': baseCoord, 'centerCoord': baseCoord }; }, /** @private */ generateGlyph: function() { var gClass = this.getGlyphClass(); var obj = new gClass(null, this.getGlyphRefLength(), this.getGlyphInitialParams()); return obj; } }); /** * A manager to store all predefined subgroup repositories * @class */ Kekule.Editor.RepositoryItemManager = { /** @private */ _itemClassMap: new Kekule.MapEx(true), /** @private */ _itemNameMap: new Kekule.MapEx(true), /** * Returns all repository items of a particular repository class. * @param {Class} repClass * @returns {Array} */ getAllItems: function(repClass) { return RM._itemClassMap.get(repClass); }, getItem: function(name) { return RM._itemNameMap.get(name); }, /** * Register a repository item. * @param {Kekule.Editor.AbstractRepositoryItem} repItem */ register: function(repItem) { var name = repItem.getName(); var replacedItem; if (name) // add to name map { replacedItem = RM.getItem(); RM._itemNameMap.set(name, repItem); } // add to class map var repClass = repItem.getClass(); var items = RM.getAllItems(repClass); if (!items) { items = [repItem]; RM._itemClassMap.set(repClass, items); } else { if (replacedItem) // replace old Kekule.ArrayUtils.replace(items, replacedItem, repItem); else // add new Kekule.ArrayUtils.pushUnique(items, repItem); } }, /** * Unregister a repository item. * @param {Kekule.Editor.AbstractRepositoryItem} repItem */ unregister: function(repItem) { var repClass = repItem.getClass(); // remove from class map var items = RM.getAllItems(repClass); if (items) { Kekule.ArrayUtils.remove(items, repItem); } // remove from name map var name = repItem.getName(); if (name) { RM._itemNameMap.remove(name); } } }; var RM = Kekule.Editor.RepositoryItemManager; // register all predefined subgroup rep items Kekule._registerAfterLoadSysProc(function (){ if (Kekule.Editor.RepositoryData) { var data = Kekule.Editor.RepositoryData.subGroups || []; for (var i = 0, l = data.length; i < l; ++i) { var detail = data[i]; var repItem = new Kekule.Editor.StoredSubgroupRepositoryItem2D(detail.structData, detail.dataFormat, detail.scale); repItem.setInputTexts(detail.inputTexts).setName(detail.name || detail.inputTexts[0]); RM.register(repItem); //console.log('reg', repItem); } var data = Kekule.Editor.RepositoryData.fragments || []; for (var i = 0, l = data.length; i < l; ++i) { var detail = data[i]; var repItem = new Kekule.Editor.StoredStructFragmentRepositoryItem2D(detail.structData, detail.dataFormat, detail.scale); repItem.setName(detail.name); RM.register(repItem); //console.log('reg', repItem); } } }); })();