/**
* @fileoverview
* Extend some core classes for editing purpose.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /core/kekule.structures.js
* requires /utils/kekule.utils.js
*/
(function()
{
var K = Kekule;
var C = Kekule.CoordMode;
var AU = Kekule.ArrayUtils;
ClassEx.extend(Kekule.ChemObject,
/** @lends Kekule.ChemObject# */
{
/**
* Check if this chem object is independent and can be cloned in editor.
* @returns {Bool}
*/
isStandalone: function()
{
//return true;
return !this.getIsAttachedToParent();
},
/**
* If this object is standalone, this method will return this directly.
* Otherwise will find the nearest standalone parent object.
*/
getStandaloneAncestor: function()
{
var result = this;
while (result.isStandalone && (!result.isStandalone()) && result.getParent && result.getParent())
{
result = result.getParent();
}
return result;
},
/**
* Returns objects that should be removed cascadely when deleting this one in editor.
* @returns {Array}
*/
getCascadeDeleteObjs: function()
{
return [];
},
/**
* Remove this object (from parent object), then notify parent to remove dependant objects.
* This method will be used to delete objects in editor.
* @param {Bool} freeObj If this param is set to true, current object will be immediately finalized.
*/
cascadeRemove: function(freeObj)
{
var parent = this.getParent();
if (parent && parent.notifyBeforeCascadeRemove)
parent.notifyBeforeCascadeRemove(this);
if (freeObj)
this.finalize();
else
this.removeSelf();
if (parent && parent.notifyAfterCascadeRemove)
parent.notifyAfterCascadeRemove(this);
},
/**
* The method will be called from child that will be cascadely removed.
* Parent can do some related removing job here (e.g., remove dependant objects).
* @param {Kekule.ChemObject} childToBeDeleted
* @param {Bool} freeObj If this param is set to true, the removed object will be immediately finalized.
* @private
*/
notifyBeforeCascadeRemove: function(childToBeDeleted, freeObj)
{
// do nothing here
},
/**
* The method will be called from child that has been already cascadely removed.
* Parent can do some related removing job here (e.g., remove dependant objects).
* @param {Kekule.ChemObject} childBeDeleted
* @param {Bool} freeObj If this param is set to true, the removed object will be immediately finalized.
* @private
*/
notifyAfterCascadeRemove: function(childBeDeleted, freeObj)
{
// do nothing here
if ((!this.getKeepEmptyEvenOnCascadeRemove()) && this.isEmpty())
{
this.cascadeRemove(freeObj);
}
},
/**
* Whether the coord of chem object is calculated from other object (like connector).
* @returns {Bool}
*/
isCoordDependent: function()
{
return false;
},
/**
* If coord is calculated from other objects, this function will return them.
* If this object is coord independent, this function will return object itself.
* @return {Array}
*/
getCoordDependentObjects: function()
{
var result = [];
if (!this.isCoordDependent())
result.push(this);
return result;
},
/**
* If this object determinate other object's coord, this method should returns them.
* @return {Array}
*/
getCoordDeterminateObjects: function()
{
var result = [];
if (this.getAttachedMarkers)
{
var markers = this.getAttachedMarkers() || [];
for (var i = 0, l = markers.length; i < l; ++i)
{
result = result.concat(markers[i].getCoordDependentObjects());
}
}
return result;
},
/**
* Return all bonds in this chemObject as well as in child objects.
* @returns {Array} Array of {Kekule.ChemStructureConnector}.
*/
getAllContainingConnectors: function()
{
return [];
},
/**
* Returns the stub object (e.g. the rotation center object in constraint bond rotation) in constraint manipulation.
* Null means constraint manipulation is not available.
* Descendants may override this method.
* @returns {Object}
* @private
*/
getConstraintManipulationBaseObj: function()
{
if (this.isAttachedMarker && this.isAttachedMarker())
{
var p = this.getParent();
if (p instanceof Kekule.StructureFragment) // molecule total charge will be be constrainted
return null;
else
return p;
//return p;
}
else
return null;
},
/**
* Transform abs coord of object by transformMatrix.
* @param {Array} transformMatrix
* @param {Array} childTransformMatrix Usually this matrix exclude translate.
* @param {Int} coordMode
* @param {Bool} cascade Whether transform child objects. Default is true.
* @param {Bool} allowCoordBorrow
*/
transformAbsCoordByMatrix: function(transformMatrix, childTransformMatrix, coordMode, cascade, allowCoordBorrow, _useChildCoord)
{
// transform children
if (this.getChildCount && (cascade || Kekule.ObjUtils.isUnset(cascade)))
{
for (var i = 0, l = this.getChildCount(); i < l; ++i)
{
var child = this.getChildAt(i);
if (child && child.transformAbsCoordByMatrix)
child.transformAbsCoordByMatrix(childTransformMatrix, childTransformMatrix, coordMode, cascade, allowCoordBorrow, true);
}
}
var transformFunc = (coordMode === Kekule.CoordMode.COORD3D)? Kekule.CoordUtils.transform3DByMatrix
:Kekule.CoordUtils.transform2DByMatrix;
// transform self
if (_useChildCoord)
{
if (this.getCoordOfMode && this.setCoordOfMode)
{
var coord = this.getCoordOfMode(coordMode, allowCoordBorrow);
var newCoord = transformFunc(coord, transformMatrix);
this.setCoordOfMode(newCoord, coordMode);
}
}
else if (this.getAbsCoordOfMode && this.setAbsCoordOfMode)
{
var coord = this.getAbsCoordOfMode(coordMode, allowCoordBorrow);
var newCoord = transformFunc(coord, transformMatrix);
this.setAbsCoordOfMode(newCoord, coordMode);
}
},
/**
* Scale size of object.
* @param {Float} scale
* @param {Int} coordMode
* @param {Bool} cascade Whether scale child objects, default is true.
* @param {Bool} allowCoordBorrow
*/
scaleSize: function(scale, coordMode, cascade, allowCoordBorrow)
{
if (this.getSizeOfMode && this.setSizeOfMode)
{
var size = this.getSizeOfMode(coordMode, allowCoordBorrow);
var newSize = Kekule.CoordUtils.multiply(size, scale);
this.setSizeOfMode(newSize, coordMode);
}
if (this.getChildCount && (cascade || Kekule.ObjUtils.isUnset(cascade)))
{
for (var i = 0, l = this.getChildCount(); i < l; ++i)
{
var child = this.getChildAt(i);
if (child && child.scaleSize)
child.scaleSize(scale, coordMode, cascade, allowCoordBorrow);
}
}
}
});
ClassEx.extendMethod(Kekule.ChemObject, '_attachedMarkerAdded', function($origin, marker){
var result = $origin(marker);
if (marker)
marker.setIsAttachedToParent(true);
return result;
});
ClassEx.extendMethod(Kekule.ChemObject, '_attachedMarkerRemoved', function($origin, marker){
var result = $origin(marker);
if (marker)
marker.setIsAttachedToParent(false);
return result;
});
ClassEx.defineProps(Kekule.ChemObject, [
// If this value is true, on cascade deleting, this object will not be deleted even if it is empty (without any children and data).
{'name': 'keepEmptyEvenOnCascadeRemove', 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC},
// Explicit whether this object is attached to another one as an attached marker
{'name': 'isAttachedToParent', 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLIC}
]);
ClassEx.extend(Kekule.ChemStructureObject,
/** @lends Kekule.ChemStructureObject# */
{
/** @ignore */
getCascadeDeleteObjs: function($super)
{
// TODO: here nested substructures is not considered
var result = $super();
var linkedConnectors = this.getLinkedConnectors? this.getLinkedConnectors(): [];
for (var i = 0, l = linkedConnectors.length; i < l; ++i)
{
var connector = linkedConnectors[i];
if (connector.getConnectedObjs().length <= 2)
{
Kekule.ArrayUtils.pushUnique(result, connector);
var newCascadeObjs = connector.getCascadeDeleteObjs();
Kekule.ArrayUtils.pushUnique(result, newCascadeObjs);
}
}
return result;
},
/** @ignore */
isStandalone: function($super)
{
return false; // structure object usually is child of struct fragment
},
/**
* If this object determinate other object's coord, this method should returns them.
* @return {Array}
*/
getCoordDeterminateObjects: function($super)
{
var result = $super();
var connectors = this.getLinkedConnectors();
for (var i = 0, l = connectors.length; i < l; ++i)
{
var connector = connectors[i];
AU.pushUnique(result, connector);
AU.pushUnique(result, connector.getCoordDeterminateObjects());
}
return result;
}
});
ClassEx.extend(/*Kekule.ChemStructureNode*/Kekule.BaseStructureNode,
/** @lends Kekule.BaseStructureNode# */
{
/** @ignore */
getCascadeDeleteObjs: function($super)
{
return $super();
},
/**
* Whether the coord of chem object is calculated from other object (like connector).
* @returns {Bool}
*/
isCoordDependent: function()
{
return false;
},
/**
* Move node by delta.
* @param {Hash} delta
* @param {Int} coordMode
* @returns {Hash} New coord after moving.
*/
move: function(delta, coordMode)
{
var oldCoord = this.getCoordOfMode(coordMode);
var newCoord = Kekule.CoordUtils.add(oldCoord, delta);
this.setCoordOfMode(newCoord, coordMode);
return newCoord;
},
/**
* Move node 2D coord by delta.
* @param {Hash} delta
* @returns {Hash} New coord after moving.
*/
move2D: function(delta)
{
return this.move(delta, C.COORD2D);
},
/**
* Move node 3D coord by delta.
* @param {Hash} delta
* @returns {Hash} New coord after moving.
*/
move3D: function(delta)
{
return this.move(delta, C.COORD3D);
}
});
ClassEx.extend(/*Kekule.ChemStructureConnector*/Kekule.BaseStructureConnector,
/** @lends Kekule.BaseStructureConnector# */
{
/** @ignore */
getCascadeDeleteObjs: function($super)
{
var result = $super();
var objs = this.getConnectedObjs();
for (var i = 0, l = objs.length; i < l; ++i)
{
var obj = objs[i];
if (obj instanceof Kekule.BaseStructureNode)
{
if (obj.getLinkedConnectors().length <= 1)
Kekule.ArrayUtils.pushUnique(result, obj);
}
}
return result;
},
/**
* Whether the coord of chem object is not calculated from other object (like connector).
* @returns {Bool}
*/
isCoordDependent: function()
{
return true;
},
/**
* If coord is calculated from other objects, this function will return them.
* @return {Array}
*/
getCoordDependentObjects: function($super)
{
var result = $super(); // []
var objs = this.getConnectedObjs();
// if objs is a nested node in fragment and the fragment is not expanded, return the fragment instead
for (var i = 0, l = objs.length; i < l; ++i)
{
var obj = objs[i];
var connObj;
if (obj.isExposed)
{
var connObj = obj.isExposed()? obj: obj.getExposedAncestor();
/*
if (obj.isExposed())
result.push(obj);
else
result.push(obj.getExposedAncestor());
*/
}
else
connObj = obj;
//result.push(obj);
//result = result.concat(obj);
if (connObj)
AU.pushUnique(result, connObj.getCoordDependentObjects());
//result.push(connObj);
}
return result;
},
/**
* Move connector by delta. Actually this action will move all nodes connected with this connector.
* @param {Hash} delta
* @param {Int} coordMode
*/
move: function(delta, coordMode)
{
for (var i = 0, l = this.getConnectedObjCount(); i < l; ++i)
{
var obj = this.getConnectedObjAt(i);
if (obj.move)
obj.move(delta, coordMode);
}
},
/**
* Move connector 2D coord by delta.
* @param {Hash} delta
* @returns {Hash} New coord after moving.
*/
move2D: function(delta)
{
return this.move(delta, C.COORD2D);
},
/**
* Move connector 3D coord by delta.
* @param {Hash} delta
* @returns {Hash} New coord after moving.
*/
move3D: function(delta)
{
return this.move(delta, C.COORD3D);
},
/**
* Set absolute center coord of connector.
* Actually mode all conntected nodes.
* @param {Hash} value
* @param {Int} coordMode
*/
setAbsBaseCoord: function(value, coordMode)
{
var old = this.getAbsBaseCoord();
var delta = Kekule.CoordUtils.substract(value, old);
this.move(delta, coordMode);
},
setAbsBaseCoord2D: function(value)
{
this.setAbsCoordOfMode(value, Kekule.CoordMode.COORD2D);
},
setAbsBaseCoord3D: function(value)
{
this.setAbsCoordOfMode(value, Kekule.CoordMode.COORD3D);
}
});
ClassEx.extend(Kekule.ChemStructureNode,
/** @lends Kekule.ChemStructureNode# */
{
/** @ignore */
getConstraintManipulationBaseObj: function($super)
{
var linkedObjs = this.getLinkedObjs();
return (linkedObjs.length === 1)? linkedObjs[0]: $super();
}
});
ClassEx.extend(Kekule.Glyph.PathGlyphNode,
/** @lends Kekule.Glyph.PathGlyphNode# */
{
/** @ignore */
getCascadeDeleteObjs: function($super) // to glyph element, delte one means delete the whole glyph
{
/*
var result = $super();
result.push(this.getParent());
*/
var result = [this.getParent()];
return result;
}
});
ClassEx.extend(Kekule.Glyph.PathGlyphConnectorControlNode,
/** @lends Kekule.Glyph.PathGlyphConnectorControlNode# */
{
/**
* If coord is calculated from other objects, this function will return them.
* @return {Array}
*/
/*
getCoordDependentObjects: function($super)
{
var result = $super(); // []
var connector = this.getParentConnector();
if (connector)
result = (result || []).concat([connector]);
return result;
}
*/
/**
* If this object determinate other object's coord, this method should returns them.
* @return {Array}
* @ignore
*/
getCoordDeterminateObjects: function($super)
{
var result = $super(); // []
var connector = this.getParentConnector();
if (connector)
result = (result || []).concat([connector]);
return result;
},
});
ClassEx.extend(Kekule.Glyph.PathGlyphConnector,
/** @lends Kekule.Glyph.PathGlyphConnector# */
{
/** @ignore */
getCascadeDeleteObjs: function($super) // to glyph element, delte one means delete the whole glyph
{
/*
var result = $super();
result.push(this.getParent());
*/
var result = [this.getParent()];
return result;
},
/**
* If coord is calculated from other objects, this function will return them.
* @return {Array}
* @ignore
*/
getCoordDependentObjects: function($super)
{
var result = $super(); // []
var controlPoints = this.getControlPoints && this.getControlPoints();
if (controlPoints && controlPoints.length)
{
result = (result || []).concat(controlPoints);
}
return result;
}
});
ClassEx.extend(Kekule.StructureFragment,
/** @lends Kekule.StructureFragment# */
{
/** @ignore */
isStandalone: function($super)
{
return !this.getCrossConnectors().length; // cross connector means this fragment is child of another fragment
},
/** @private */
notifyBeforeCascadeRemove: function($super, childToBeDeleted, freeObj)
{
// get dependant objects
var dependantObjs = this._getObjsNeedToBeCascadeRemoved(childToBeDeleted);
if (dependantObjs && dependantObjs.length)
{
for (var i = dependantObjs.length - 1; i >= 0; --i)
{
var obj = dependantObjs[i];
//obj.cascadeDelete(); // DONE: is this safe? not safe, may cause recursion.
if (freeObj)
obj.finalize();
else
obj.removeSelf();
}
}
$super(childToBeDeleted, freeObj);
},
/** @private */
getCoordDependentObjects: function($super) // when manipulate mol in editor, actually the nodes are moved or rotated
{
if (this.isExpanded && !this.isExpanded())
{
return $super();
}
else if (!this.hasCtab())
return $super();
else
{
var result = []; // $super();
var childCount = this.getChildCount();
for (var i = 0; i < childCount; ++i)
{
var child = this.getChildAt(i);
AU.pushUnique(result, child.getCoordDependentObjects());
}
return result;
}
},
/** @ignore */
transformAbsCoordByMatrix: function($super, transformMatrix, childTransformMatrix, coordMode, cascade, allowCoordBorrow, _useChildCoord)
{
// transform node only
if (cascade || Kekule.ObjUtils.isUnset(cascade))
{
for (var i = 0, l = this.getNodeCount(); i < l; ++i)
{
var node = this.getNodeAt(i);
if (node && node.transformAbsCoordByMatrix)
node.transformAbsCoordByMatrix(childTransformMatrix, childTransformMatrix, coordMode, cascade, allowCoordBorrow, true);
}
}
// then transform self
$super(transformMatrix, childTransformMatrix, coordMode, false, allowCoordBorrow, _useChildCoord);
}
});
/*
ClassEx.extend(Kekule.CompositeMolecule, {
notifyAfterCascadeDelete: function($super, childBeDeleted)
{
$super(childToBeDeleted);
// if self is empty, release self
if (this.getSubMoleculeCount() <= 0)
this.cascadeDelete();
}
});
*/
ClassEx.extend(Kekule.Glyph.Base,
/** @lends Kekule.Glyph.Base# */
{
/** @ignore */
isStandalone: function($super)
{
return /*true && */ $super();
}
});
ClassEx.extend(Kekule.Glyph.PathGlyph,
/** @lends Kekule.Glyph.PathGlyph# */
{
/** @private */
getCoordDependentObjects: function()
{
var result = [];
var nodeCount = this.getNodeCount();
for (var i = 0; i < nodeCount; ++i)
{
var node = this.getNodeAt(i);
result.push(node);
}
return result;
},
/** @ignore */
transformAbsCoordByMatrix: function($super, transformMatrix, childTransformMatrix, coordMode, cascade, allowCoordBorrow, _useChildCoord)
{
// transform node only
if (cascade || Kekule.ObjUtils.isUnset(cascade))
{
for (var i = 0, l = this.getNodeCount(); i < l; ++i)
{
var node = this.getNodeAt(i);
if (node && node.transformAbsCoordByMatrix)
node.transformAbsCoordByMatrix(childTransformMatrix, childTransformMatrix, coordMode, cascade, allowCoordBorrow, true);
}
}
// then transform self
$super(transformMatrix, childTransformMatrix, coordMode, false, allowCoordBorrow, _useChildCoord);
}
});
})();