kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,543 lines (1,467 loc) • 280 kB
JavaScript
/**
* @fileoverview
* Editor for Kekule.ChemSpace.
* @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/operation/kekule.operations.js
* requires /widgets/commonCtrls/kekule.widget.formControls.js
* requires /widgets/commonCtrls/kekule.widget.dialogs.js
* requires /widgets/chem/periodicTable/kekule.chemWidget.periodicTables.js
* requires /widgets/chem/uiMarker/kekule.chemWidget.uiMarkers.js
* requires /widgets/chem/editor/kekule.chemEditor.extensions.js
* requires /widgets/chem/editor/kekule.chemEditor.editorUtils.js
* requires /widgets/chem/editor/kekule.chemEditor.configs.js
* requires /widgets/chem/editor/kekule.chemEditor.operations.js
* requires /widgets/chem/editor/kekule.chemEditor.baseEditors.js
* requires /widgets/chem/editor/kekule.chemEditor.repositories.js
* requires /widgets/chem/editor/kekule.chemEditor.utilWidgets.js
*/
(function(){
"use strict";
var AU = Kekule.ArrayUtils;
var CU = Kekule.CoordUtils;
var CCNS = Kekule.ChemWidget.HtmlClassNames;
//var CWT = Kekule.ChemWidgetTexts;
/** @ignore */
Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, {
CHEMSPACE_EDITOR: 'K-Chem-Space-Editor',
CHEMSPACE_EDITOR2D: 'K-Chem-Space-Editor2D',
CHEMSPACE_EDITOR3D: 'K-Chem-Space-Editor3D',
CHEMEDITOR_ATOM_SETTER: 'K-ChemEditor-Atom-Setter',
CHEMEDITOR_TEXT_SETTER: 'K-ChemEditor-Text-Setter',
CHEMEDITOR_FORMULA_SETTER: 'K-ChemEditor-Formula-Setter'
});
Kekule.globalOptions.add('chemWidget.editor', {
'enableSelect': true,
'enableFileDrop': true,
'allowCreateNewChild': true,
'autoCreateNewStructFragment': true,
'allowAppendDataToCurr': true,
'enableGesture': true,
'initOnNewDoc': true
});
Kekule.globalOptions.add('chemWidget.editor.molManipulation', {
/*
'enableMagneticMerge': true,
'enableNodeMerge': true,
'enableNeighborNodeMerge': true,
'enableConnectorMerge': true,
'enableStructFragmentMerge': true,
*/
'enableNodeStick': true,
'enableStructFragmentStick': true,
'enableConstrainedMove': true,
'enableConstrainedRotate': true,
'enableConstrainedResize': true,
'enableDirectedMove': true
});
Kekule.globalOptions.add('chemWidget.editor.bondManipulation', {
'enableBondModification': true,
'allowBondingToBond': false,
'autoSwitchBondOrder': false
});
/**
* A chem editor to edit chemspace object and other chem objects.
* When load a chem object other than instance of Kekule.ChemSpace, an empty ChemSpace instance will
* be created and loaded object should be insert into it.
* @class
* @augments Kekule.Editor.BaseEditor
* @param {Variant} parentOrElementOrDocument
* @param {Kekule.ChemObject} chemObj initially loaded chemObj.
* @param {Int} renderType Display in 2D or 3D. Value from {@link Kekule.Render.RendererType}.
* @param {Kekule.Editor.BaseEditorConfigs} editorConfigs Configuration of this editor.
*
* @property {Kekule.ChemSpace} chemSpace ChemSpace loaded in this editor.
* @property {Float} defBondLength
* @property {Bool} allowCreateNewChild Whether new direct child of space can be created.
* Note: if the space is empty, one new child will always be allowed to create.
* @property {Bool} autoCreateNewStructFragment Whether new molecule object can be created in space.
* Note: if property {@link Kekule.Editor.ChemSpaceEditor.allowCreateNewChild} is false, this property
* will always be false.
* @property {Bool} allowAppendDataToCurr Whether display "append data" check box in the dialog of data load action.
*/
Kekule.Editor.ChemSpaceEditor = Class.create(Kekule.Editor.BaseEditor,
/** @lends Kekule.Editor.ChemSpaceEditor# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.ChemSpaceEditor',
/** @constructs */
initialize: function(/*$super, */parentOrElementOrDocument, chemObj, renderType, editorConfigs)
{
/*
this.setPropStoreFieldValue('allowCreateNewChild', true);
this.setPropStoreFieldValue('autoCreateNewStructFragment', true);
this.setPropStoreFieldValue('allowAppendDataToCurr', true);
*/
var getOptionValue = Kekule.globalOptions.get;
this.setPropStoreFieldValue('allowCreateNewChild', getOptionValue('chemWidget.editor.allowCreateNewChild', true));
this.setPropStoreFieldValue('autoCreateNewStructFragment', getOptionValue('chemWidget.editor.autoCreateNewStructFragment', true));
this.setPropStoreFieldValue('allowAppendDataToCurr', getOptionValue('chemWidget.editor.autoCreateNewStructFragment', true));
this.tryApplySuper('initialize', [parentOrElementOrDocument, chemObj, renderType, editorConfigs]) /* $super(parentOrElementOrDocument, chemObj, renderType, editorConfigs) */;
this._containerChemSpace = null; // private field, used to mark that a extra chem space container is used
},
/** @private */
initProperties: function()
{
this.defineProp('chemSpace', {'dataType': 'Kekule.ChemSpace', 'serializable': false,
'getter': function() { return this.getChemObj(); },
'setter': function(value) { this.setChemObj(value); }
});
this.defineProp('autoCreateNewStructFragment', {'dataType': DataType.BOOL,
'getter': function()
{
return this.getPropStoreFieldValue('autoCreateNewStructFragment') && this.canCreateNewChild();
}
});
this.defineProp('allowCreateNewChild', {'dataType': DataType.BOOL});
this.defineProp('allowAppendDataToCurr', {'dataType': DataType.BOOL});
},
/** @ignore */
initPropValues: function(/*$super*/)
{
this.tryApplySuper('initPropValues') /* $super() */;
//this.setFileDroppable(true); // defaultly turn on file drop function
this.setFileDroppable(Kekule.globalOptions.get('chemWidget.editor.enableFileDrop', true));
},
/**
* Returns whether new direct child can be created in current space.
* This method will returns true of property {@link Kekule.Editor.ChemSpaceEditor.allowCreateNewChild} is true
* or the space is empty.
* @returns {Bool}
*/
canCreateNewChild: function()
{
return this.getChemSpace() && (this.getAllowCreateNewChild() || (this.getChemSpace().getChildCount() <= 0));
},
/** @ignore */
getActualDrawOptions: function(/*$super*/)
{
var result = this.tryApplySuper('getActualDrawOptions') /* $super() */;
// a special field to ensure use explict size of space
// rather than calc size based on child objects
result.useExplicitSpaceSize = true;
return result;
},
/** @private */
doSetChemObj: function(/*$super, */value)
{
var old = this.getChemObj();
if (old !== value)
{
if (old && this._containerChemSpace)
{
old.finalize();
this._containerChemSpace = null;
}
if (value)
{
if (value instanceof Kekule.ChemSpace)
{
this._initChemSpaceDefProps(value);
this.tryApplySuper('doSetChemObj', [value]) /* $super(value) */;
}
else
{
var space = this.createContainerChemSpace(null, value);
//this._initChemSpaceDefProps(space, value);
//space.appendChild(value);
this._containerChemSpace = space;
this.tryApplySuper('doSetChemObj', [space]) /* $super(space) */;
}
if (this.getEditorConfigs().getInteractionConfigs().getAutoExpandClientSizeAfterLoading())
{
this.autoExpandChemSpaceSize();
}
if (value instanceof Kekule.ChemSpace)
{
// auto scroll
if (this.getEditorConfigs().getInteractionConfigs().getScrollToObjAfterLoading())
{
var objs = value.getChildren();
if (objs && objs.length)
this.scrollClientToObject(objs);
}
}
}
else
this.tryApplySuper('doSetChemObj', [value]) /* $super(value) */;
}
else
this.tryApplySuper('doSetChemObj', [value]) /* $super(value) */;
},
/** @ignore */
getExportableClasses: function(/*$super*/)
{
var result = this.tryApplySuper('getExportableClasses') /* $super() */; // now result includes chemSpace
// add child objects of chemspace to result
var space = this.getChemSpace();
if (space)
{
/*
for (var i = 0, l = space.getChildCount(); i < l; ++i)
{
var obj = space.getChildAt(i);
if (obj && obj.getClass)
Kekule.ArrayUtils.pushUnique(result, obj.getClass());
}
*/
var iteratorFunc = function(child){
if (child.isStandalone && child.isStandalone() && child.getClass)
{
var oClass = child.getClass();
Kekule.ArrayUtils.pushUnique(result, oClass);
}
};
space.iterateChildren(iteratorFunc, true);
}
return result;
},
/** @ignore */
exportObjs: function(/*$super, */objClass)
{
var result = this.tryApplySuper('exportObjs', [objClass]) /* $super(objClass) */;
if ((!result || !result.length) && objClass) // check child objects of chemSpace
{
result = [];
var space = this.getChemSpace();
/*
if (space)
{
for (var i = 0, l = space.getChildCount(); i < l; ++i)
{
var obj = space.getChildAt(i);
if (obj && (obj instanceof objClass))
result.push(obj);
}
}
*/
var filter = function(child){ return child && (child instanceof objClass); };
result = space.filterChildren(filter, true);
}
return result;
},
/** @ignore */
getSavingTargetObj: function(/*$super*/)
{
// if only one child in chemspace, save this obj alone (rather than the space).
var space = this.getChemSpace();
var childCount = space.getChildCount();
if (childCount === 1)
{
return space.getChildAt(0);
}
else
return this.tryApplySuper('getSavingTargetObj') /* $super() */;
},
/** @ignore */
_cloneSavingTargetObj: function(obj)
{
var space = this.getChemSpace();
var childCount = space.getChildCount();
if (childCount === 1 && obj === space.getChildAt(0))
{
// The objRef properties are related with chemspace, if clone obj only, the relation may be lost
var clonedSpace = space.clone(true);
return clonedSpace.getChildAt(0);
}
else
return this.tryApplySuper('_cloneSavingTargetObj', [obj]);
},
/** @ignore */
createDefaultConfigs: function()
{
//return new Kekule.Editor.ChemSpaceEditorConfigs();
return Kekule.Editor.ChemSpaceEditorConfigs.getInstance();
},
/** @private */
doCreateNewDocObj: function()
{
return this.createContainerChemSpace();
},
/** @private */
doLoad: function(/*$super, */chemObj)
{
// supply essential charge and radical markers
this._supplyChemMarkersOnObj(chemObj);
this.tryApplySuper('doLoad', [chemObj]) /* $super(chemObj) */;
},
/** @private */
doLoadEnd: function(/*$super, */chemObj)
{
var result = this.tryApplySuper('doLoadEnd', [chemObj]) /* $super(chemObj) */;
// calc def bond length
var defBondLength = null;
if (chemObj)
{
var connectors = chemObj.getAllContainingConnectors();
if (connectors && connectors.length)
{
var coordMode = (this.getRenderType() === Kekule.Render.RendererType.R3D)?
Kekule.CoordMode.COORD3D: Kekule.CoordMode.COORD2D;
defBondLength = Kekule.ChemStructureUtils.getConnectorLengthMedian(connectors, coordMode, this.getAllowCoordBorrow());
}
}
this.setDefBondLength(defBondLength);
return result;
},
/** @ignore */
resetDisplay: function(/*$super*/)
{
// called after loading a new chemObj, or creating a new doc
this.tryApplySuper('resetDisplay') /* $super() */;
this.resetClientDisplay();
},
/**
* Reset the transform params of context and repaint the client.
* @private
*/
resetClientDisplay: function(restoreScrollPosition)
{
// run rest later after updating object in editor, to ensure the context is really updated (even in begin/endUpdateObject block, e.g., in undo change space size operation)
// IMPORTANT: if not called in a defer way, the resetClientDisplay may be run before endUpdateObject. In reset process, the
// object is actually not updated in draw context (e.g., the box of text block is still the old one and returns a old container box),
// then the _initialTransformParams are recalculated in resetClientDisplay but got a wrong result.
// So, here we must ensure the resetClientDisplay run after endUpdateObject.
if (this.isUpdatingObject()) // suspend the reset process after updating is done
{
var self = this;
/*
this.once('endUpdateObject', function(e){
if (e.target === self)
self._resetClientDisplayCore(restoreScrollPosition);
});
*/
this._registerAfterUpdateObjectProc(function(){
self._resetClientDisplayCore(restoreScrollPosition);
});
}
else
return this._resetClientDisplayCore(restoreScrollPosition);
},
/** @private */
_resetClientDisplayCore: function(restoreScrollPosition)
{
// adjust editor size
var space = this.getChemObj();
if (space)
{
var scrollPos;
if (restoreScrollPosition)
scrollPos = this.getClientScrollPosition();
//console.log('decide size', space.getScreenSize());
var screenSize = space.getScreenSize();
this.changeClientSize(screenSize.x, screenSize.y, this.getCurrZoom());
// scroll to top center
var elem = this.getEditClientElem().parentNode;
var visibleClientSize = Kekule.HtmlElementUtils.getElemClientDimension(elem);
if (!restoreScrollPosition)
this.scrollClientTo(0, (screenSize.x * this.getCurrZoom() - visibleClientSize.width) / 2);
else
this.scrollClientTo(scrollPos.y, scrollPos.x);
}
},
/** @ignore */
zoomChanged: function(/*$super, */zoomLevel)
{
this.tryApplySuper('zoomChanged') /* $super() */;
var space = this.getChemObj();
if (space)
{
var screenSize = space.getScreenSize();
this.changeClientSize(screenSize.x, screenSize.y, zoomLevel);
}
},
/** @ignore */
createNewBoundInfoRecorder: function(/*$super, */renderer)
{
this.tryApplySuper('createNewBoundInfoRecorder', [renderer]) /* $super(renderer) */;
var recorder = this.getBoundInfoRecorder();
if (recorder) // add event listener to update text box size
{
recorder.addEventListener('updateBasicDrawObject', this.reactUpdateBasicDrawObject, this);
}
},
/** @private */
reactUpdateBasicDrawObject: function(e)
{
var obj = e.obj;
// TODO: use such a method to set text block size may not be a good approach
if (obj && (obj instanceof Kekule.TextBlock))
{
var boundInfo = e.boundInfo;
this.updateTextBlockSize(obj, boundInfo);
}
},
/* @ignore */
objectChanged: function(/*$super, */obj, changedPropNames)
{
/*
if (this.getCoordMode() === Kekule.CoordMode.COORD2D) // only works in 2D mode
{
if (obj instanceof Kekule.TextBlock) // size need to be recalculated
{
//console.log('text box changed', obj.getId());
// must not use setSize2D, otherwise a new object change event will be triggered
//obj.setPropStoreFieldValue('size2D', {'x': null, 'y': null});
obj.__$needRecalcSize__ = true; // special flag, indicating to recalculate size
}
}
*/
var result = this.tryApplySuper('objectChanged', [obj, changedPropNames]) /* $super(obj, changedPropNames) */;
//this.autoExpandChemSpaceSize();
return result;
},
/** @ignore */
doManipulationEnd: function(/*$super*/)
{
if (this.getEditorConfigs().getInteractionConfigs().getAutoExpandClientSizeAfterManipulation())
{
//this.autoExpandChemSpaceSize();
var autoExpandInfo = this._calcChemSpaceAutoExpandInfo();
if (autoExpandInfo) // need to expand
{
var opers = this._createChemSpaceAutoExpandOperations(autoExpandInfo);
if (opers)
{
if (this.getEnableOperHistory())
{
//console.log('append resize oper', opers);
this._appendOpersToLastManipulationOperation(opers);
}
for (var i = 0, l = opers.length; i < l; ++i)
{
opers[i].execute(); // execute but not push to history
}
}
}
}
return this.tryApplySuper('doManipulationEnd') /* $super() */;
},
/** @private */
_appendOpersToLastManipulationOperation: function(opers)
{
var currManipulationOpers = this.getOperationsInCurrManipulation();
if (currManipulationOpers && currManipulationOpers.length)
{
var lastOper = currManipulationOpers[currManipulationOpers.length - 1];
var operHistory = this.getOperHistory();
if (operHistory)
{
var historyOperations = operHistory.getOperations(); // TODO: here we use the internal array structure of OperationHistory, may change in the future
var historyIndex = historyOperations.indexOf(lastOper);
if (historyIndex >= 0) // replace oper in history
{
var macroOperation;
if (lastOper instanceof Kekule.MacroOperation)
macroOperation = lastOper;
else
{
macroOperation = new Kekule.MacroOperation([lastOper]);
historyOperations.splice(historyIndex, 1, macroOperation);
currManipulationOpers.splice(currManipulationOpers.length - 1, 1, macroOperation);
}
for (var i = 0, l = opers.length; i < l; ++i)
{
macroOperation.add(opers[i]);
}
}
}
}
},
/** @private */
updateTextBlockSize: function(textBlock, boundInfo)
{
if (this.getCoordMode() !== Kekule.CoordMode.COORD2D) // only works in 2D mode
return;
/*
var oldSize = textBlock.getSize2D();
if (Kekule.ObjUtils.notUnset(oldSize.x) || Kekule.ObjUtils.notUnset(oldSize.y)) // size already set, by pass
return;
*/
//if (!textBlock.__$needRecalcSize__)
if (!textBlock.getNeedRecalcSize())
return;
var stype = boundInfo.shapeType;
if (stype === Kekule.Render.BoundShapeType.RECT)
{
/*
console.log('boundddddd', boundInfo);
var coords = boundInfo.coords; // context coords
var objCoord1 = this.contextCoordToObj(coords[0]);
var objCoord2 = this.contextCoordToObj(coords[1]);
var delta = Kekule.CoordUtils.substract(objCoord2, objCoord1);
// must not use setSize2D, otherwise a new object change event will be triggered and a new update process will be launched
textBlock.setPropStoreFieldValue('size2D', {'x': Math.abs(delta.x), 'y': Math.abs(delta.y)});
//textBlock.setSize2D({'x': Math.abs(delta.x), 'y': Math.abs(delta.y)});
//delete textBlock.__$needRecalcSize__;
textBlock.setNeedRecalcSize(false);
*/
}
},
/** @private */
createContainerChemSpace: function(id, containingChemObj)
{
//var result = new Kekule.ChemSpace(id);
var result = new Kekule.ChemDocument(id);
this._initChemSpaceDefProps(result, containingChemObj);
if (containingChemObj)
{
result.appendChild(containingChemObj);
// adjust child position
var spaceSize = result.getSizeOfMode(this.getCoordMode(), this.getAllowCoordBorrow());
var coord, ratio;
var objBox = Kekule.Render.ObjUtils.getContainerBox(containingChemObj, this.getCoordMode(), this.getAllowCoordBorrow());
if (this.getCoordMode() === Kekule.CoordMode.COORD2D)
{
/*
var clientElem = this.getEditClientElem();
var clientDim = Kekule.HtmlElementUtils.getElemClientDimension(clientElem);
var clientScrollX = clientDim.scrollTop;
var clientScrollY = clientDim.scrollLeft;
var padding = clientDim.height / 2;
*/
var padding = this.getEditorConfigs().getChemSpaceConfigs().getDefPadding();
var ratio = result.getObjScreenLengthRatio();
//console.log('adjust inside obj size', objBox, this.isShown());
}
/*
var oldObjCoord = containingChemObj.getCoordOfMode?
containingChemObj.getCoordOfMode(this.getCoordMode(), this.getAllowCoordBorrow()) || {}:
{};
*/
var oldObjCoord = containingChemObj.getAbsBaseCoord?
containingChemObj.getAbsBaseCoord(this.getCoordMode(), this.getAllowCoordBorrow()) || {}:
{};
if (ratio && objBox) // 2D and calc padding
{
/*
var oldObjCoord = containingChemObj.getCoordOfMode?
containingChemObj.getCoordOfMode(this.getCoordMode()) || {}:
{};
*/
coord = Kekule.CoordUtils.divide(spaceSize, 2);
//coord.y = spaceSize.y - /*(objBox.y2 - objBox.y1) / 2*/objBox.y2 - padding * ratio;
//coord.y = spaceSize.y - Math.abs(objBox.y2 - objBox.y1) / 2 - padding * ratio;
//coord.x -= (objBox.x2 + objBox.x1) / 2;
/*
coord.y = (oldObjCoord.y || 0) + spaceSize.y - padding * ratio - objBox.y2;
coord.x += (oldObjCoord.x || 0) - (objBox.x2 + objBox.x1) / 2;
*/
var objBoxCenter = {x: (objBox.x1 + objBox.x2) / 2, y: (objBox.y1 + objBox.y2) / 2};
var newObjCenter = {x: coord.x, y: spaceSize.y - padding * ratio - (objBox.y2 - objBox.y1) / 2};
var centerDelta = Kekule.CoordUtils.substract(newObjCenter, objBoxCenter);
coord = Kekule.CoordUtils.add(oldObjCoord, centerDelta);
/*
coord.y = spaceSize.y - padding * ratio - (objBox.y2 - objBox.y1) / 2 - objBoxCenter.y;
coord.x = coord.x - (objBox.x2 + objBox.x1) / 2;
*/
//console.log(spaceSize, coord, objBox);
}
else
{
//var oldObjCoord = containingChemObj.getCoordOfMode(this.getCoordMode()) || {};
coord = Kekule.CoordUtils.divide(spaceSize, 2);
/*
coord.x -= (objBox.x2 + objBox.x1) / 2;
coord.y -= (objBox.y2 + objBox.y1) / 2;
if (this.getCoordMode() === Kekule.CoordMode.COORD3D)
coord.z -= (objBox.z2 + objBox.z1) / 2;
coord = Kekule.CoordUtils.add(coord, oldObjCoord);
*/
}
/*
if (containingChemObj.setCoordOfMode)
containingChemObj.setCoordOfMode(coord, this.getCoordMode());
*/
if (containingChemObj.setAbsBaseCoord)
containingChemObj.setAbsBaseCoord(coord, this.getCoordMode());
//this.setObjCoord(containingChemObj, coord, Kekule.Render.CoordPos.CENTER);
}
return result;
},
/** @private */
_initChemSpaceDefProps: function(chemSpace, containingChemObj, forceResetSize2D)
{
var configs = this.getEditorConfigs();
var chemSpaceConfigs = configs.getChemSpaceConfigs();
if (this.getCoordMode() === Kekule.CoordMode.COORD2D) // now only handles 2D size
{
var screenSize = chemSpace.getScreenSize();
if (!screenSize.x && !screenSize.y)
{
screenSize = chemSpaceConfigs.getDefScreenSize2D();
chemSpace.setScreenSize(screenSize);
}
if (!chemSpace.getDefAutoScaleRefLength())
{
var refLength;
if (containingChemObj && containingChemObj.getAllAutoScaleRefLengths)
{
var refLengths = containingChemObj.getAllAutoScaleRefLengths(this.getCoordMode(), this.getAllowCoordBorrow());
refLength = refLengths && refLengths.length? Kekule.ArrayUtils.getMedian(refLengths): null;
}
else
{
var refLengths = chemSpace.getAllAutoScaleRefLengths(this.getCoordMode(), this.getAllowCoordBorrow());
refLength = refLengths.length? Kekule.ArrayUtils.getMedian(refLengths): null;
}
if (!refLength)
refLength = configs.getStructureConfigs().getDefBondLength();
chemSpace.setDefAutoScaleRefLength(refLength);
}
if (!chemSpace.getSize2D() || forceResetSize2D)
{
var refScreenLength = this.getRenderConfigs().getLengthConfigs().getDefBondLength();
var ratio = chemSpace.getDefAutoScaleRefLength() / refScreenLength;
chemSpace.setObjScreenLengthRatio(ratio);
chemSpace.setSize2D({'x': screenSize.x * ratio, 'y': screenSize.y * ratio});
}
}
if (chemSpace.setIsEditing)
chemSpace.setIsEditing(true);
},
/** @private */
_getChemSpaceObjScreenLengthRatio: function()
{
var chemSpace = this.getChemSpace();
var ratio = chemSpace.getObjScreenLengthRatio();
if (!ratio)
{
var refScreenLength = this.getRenderConfigs().getLengthConfigs().getDefBondLength();
ratio = chemSpace.getDefAutoScaleRefLength() / refScreenLength;
}
return ratio;
},
/**
* Change the size of current chemspace.
* The screen size of the space will also be modified in 2D coord mode.
* @param {Hash} size
* @param {Int} coordMode
*/
changeChemSpaceSize: function(size, coordMode)
{
var chemSpace = this.getChemSpace();
chemSpace.beginUpdate();
try
{
chemSpace.setSizeOfMode(size, coordMode);
// now only change screen size in 2D mode
if (coordMode === Kekule.CoordMode.COORD2D)
{
/*
var ratio = chemSpace.getObjScreenLengthRatio();
if (!ratio)
{
var refScreenLength = this.getRenderConfigs().getLengthConfigs().getDefBondLength();
ratio = chemSpace.getDefAutoScaleRefLength() / refScreenLength;
}
*/
var ratio = this._getChemSpaceObjScreenLengthRatio();
if (ratio)
{
chemSpace.setScreenSize({'x': size.x / ratio, 'y': size.y / ratio});
}
}
}
finally
{
chemSpace.endUpdate();
}
this.resetClientDisplay(true);
},
/**
* Change the screen size of current chem space in editor.
* The size2D of chemSpace will also be modified.
* @param {Hash} screenSize
*/
changeChemSpaceScreenSize: function(screenSize)
{
var chemSpace = this.getChemSpace();
chemSpace.beginUpdate();
try
{
/*
var ratio = chemSpace.getObjScreenLengthRatio();
if (!ratio)
{
var refScreenLength = this.getRenderConfigs().getLengthConfigs().getDefBondLength();
ratio = chemSpace.getDefAutoScaleRefLength() / refScreenLength;
}
*/
var ratio = this._getChemSpaceObjScreenLengthRatio();
if (ratio)
{
// change size 2D
chemSpace.setSize2D({'x': screenSize.x * ratio, 'y': screenSize.y * ratio});
}
chemSpace.setScreenSize(screenSize);
}
finally
{
chemSpace.endUpdate();
}
this.resetClientDisplay(true);
},
/**
* Shift coords of all direct children of chem space.
* @private
*/
_shiftChemSpaceChildCoord: function(coordDelta, coordMode, scrollToNewPosition)
{
var chemSpace = this.getChemSpace();
var children = chemSpace.getChildren();
if (children)
{
var scrollCoord;
if (scrollToNewPosition)
{
scrollCoord = this.getClientScrollCoord(Kekule.Editor.CoordSys.CHEM);
//scrollCoord = CU.multiply(scrollCoord, -1);
}
this.beginUpdateObject();
try
{
chemSpace.beginUpdate();
try
{
var allowCoordBorrow = this.getAllowCoordBorrow();
for (var i = 0, l = children.length; i < l; ++i)
{
var child = children[i];
if (child && child.setCoordOfMode)
{
var oldCoord = child.getCoordOfMode(coordMode, allowCoordBorrow) || {};
child.setCoordOfMode(CU.add(oldCoord, coordDelta));
}
/*
if (coordDelta2D && child && child.setCoord2D)
child.setCoord2D(CU.add(child.getCoord2D(), coordDelta2D));
if (coordDelta3D && child && child.setCoord3D)
child.setCoord3D(CU.add(child.getCoord3D(), coordDelta3D));
*/
}
if (scrollToNewPosition)
{
//console.log('scrollToNewPosition', scrollCoord);
this.scrollClientToCoord(scrollCoord, Kekule.Editor.CoordSys.CHEM);
}
}
finally
{
chemSpace.endUpdate();
}
}
finally
{
this.endUpdateObject();
}
}
},
/** @private */
_calcExpandChemSpaceSizeToTargetObjsInfo: function(targetObjs, coordMode)
{
if (targetObjs && targetObjs.length)
{
var containerBoxInfo = this._getTargetObjsExposedContainerBoxInfo(targetObjs);
var totalContainerBox = containerBoxInfo.totalBox;
if (totalContainerBox)
return this._calcExpandChemSpaceSizeToContainerBoxInfo(totalContainerBox, coordMode);
}
return null;
},
/** @private */
_calcExpandChemSpaceSizeToContainerBoxInfo: function(containerBox, coordMode)
{
var CM = Kekule.CoordMode;
var space = this.getChemSpace();
var is3D = (coordMode === CM.COORD3D);
var coordFields = ['x', 'y'];
if (is3D)
coordFields.push('z');
var chemSpaceConfigs = this.getEditorConfigs().getChemSpaceConfigs();
var ObjScreenLengthRatio = this._getChemSpaceObjScreenLengthRatio();
var screenPadding = chemSpaceConfigs.getDefPadding();
var objSysPadding = screenPadding * ObjScreenLengthRatio;
var currSize = is3D? space.getSize3D(): space.getSize2D();
var minMaxCoords = Kekule.BoxUtils.getMinMaxCoords(containerBox);
// check minCoord, if coord less than zero + padding, need to expand
var minCoord = minMaxCoords.min;
var minShift = {};
var needMinShift = false;
for (var i = 0, l = coordFields.length; i < l; ++i)
{
var coordValue = minCoord[coordFields[i]];
if (coordValue < objSysPadding)
{
minShift[coordFields[i]] = objSysPadding - coordValue;
needMinShift = true;
}
else
minShift[coordFields[i]] = 0;
}
// check maxCoord, if coord greater than size - padding, need to expand
var maxCoord = minMaxCoords.max;
var maxDelta = {};
var needMaxExpand = false;
for (var i = 0, l = coordFields.length; i < l; ++i)
{
var coordValue = maxCoord[coordFields[i]];
if (coordValue > currSize[coordFields[i]] - objSysPadding)
{
maxDelta[coordFields[i]] = coordValue - (currSize[coordFields[i]] - objSysPadding);
needMaxExpand = true;
}
else
maxDelta[coordFields[i]] = 0;
}
// prepare to expand
var expands = {};
var actualExpands = {};
if (needMinShift || needMaxExpand)
{
//console.log('do expand', needMinShift, minShift, needMaxExpand, maxDelta);
var autoExpandScreenSize = is3D? chemSpaceConfigs.getAutoExpandScreenSize2D(): chemSpaceConfigs.getAutoExpandScreenSize3D();
var autoExpandSize = CU.multiply(autoExpandScreenSize, ObjScreenLengthRatio);
for (var i = 0, l = coordFields.length; i < l; ++i)
{
var field = coordFields[i];
expands[field] = minShift[field] + maxDelta[field];
var scale = Math.ceil(expands[field] / autoExpandSize[field]);
actualExpands[field] = scale * autoExpandSize[field];
}
// do expand and shift
var newSize = CU.add(currSize, actualExpands);
var result = {'newSize': newSize};
if (needMinShift)
result.coordShift = minShift;
return result;
}
else
return null;
},
/** @private */
_expandChemSpaceSizeAndShiftChildrenCoords: function(newSize, coordShift, coordMode)
{
this.beginUpdateObject();
try
{
if (coordShift)
this._shiftChemSpaceChildCoord(coordShift, coordMode, true);
this.changeChemSpaceSize(newSize, coordMode);
}
finally
{
this.endUpdateObject();
}
},
/** @private */
_expandChemSpaceSizeToContainerBox: function(containerBox, coordMode)
{
var detail = this._calcExpandChemSpaceSizeToContainerBoxInfo(containerBox, coordMode);
if (detail)
{
this._expandChemSpaceSizeAndShiftChildrenCoords(detail.newSize, detail.coordShift, coordMode);
}
return detail;
},
/**
* Change the size of current chemspace, automatically expand it to display all targetObjs.
* @param {Array} targetObjs
* @param {Int} coordMode
*/
expandChemSpaceSizeToTargetObjs: function(targetObjs, coordMode)
{
if (Kekule.ObjUtils.isUnset(coordMode))
coordMode = this.getCoordMode();
if (targetObjs && targetObjs.length)
{
var containerBoxInfo = this._getTargetObjsExposedContainerBoxInfo(targetObjs);
var totalContainerBox = containerBoxInfo.totalBox;
if (totalContainerBox)
this._expandChemSpaceSizeToContainerBox(totalContainerBox, coordMode);
}
return this;
},
/**
* Automatically expand the size of current chem space to display all child objects.
*/
autoExpandChemSpaceSize: function()
{
if (this._isAutoExpandingChemSpace) // a flag, avoid recursion calls
return this;
this._isAutoExpandingChemSpace = true;
try
{
var space = this.getChemSpace();
var targetObjs = space.getChildren();
if (targetObjs)
this.expandChemSpaceSizeToTargetObjs(targetObjs, this.getCoordMode());
}
finally
{
this._isAutoExpandingChemSpace = false;
}
return this;
},
/**
* Returns the new size and coord shift to auto expand chem space.
* @private
*/
_calcChemSpaceAutoExpandInfo: function()
{
var space = this.getChemSpace();
var targetObjs = space.getChildren();
if (targetObjs)
return this._calcExpandChemSpaceSizeToTargetObjsInfo(targetObjs, this.getCoordMode());
else
return null;
},
/** @private */
_createChemSpaceAutoExpandOperations: function(expandInfo)
{
if (expandInfo)
{
var result = [];
var space = this.getChemSpace();
var coordMode = this.getCoordMode();
/*
if (expandInfo.coordShift)
result.push(new Kekule.ChemSpaceEditorOperation.ShiftChildrenCoords(space, expandInfo.coordShift, coordMode, this));
if (expandInfo.newSize)
result.push(new Kekule.ChemSpaceEditorOperation.ChangeSpaceSize(space, expandInfo.newSize, coordMode, this));
*/
result.push(new Kekule.ChemSpaceEditorOperation.ChangeSpaceSizeAndShiftChildrenCoords(space, expandInfo.newSize, expandInfo.coordShift, coordMode, this));
return result;
}
else
return null;
},
/**
* Create new molecule or structure fragment in chem space.
* @param {String} id
* @returns {Kekule.StructureFragment}
*/
createNewStructFragmentAnchor: function(id)
{
/*
if (!id) // debug, auto add
{
id = 'M' + this.getChemObj().getChildCount();
}
*/
if (this.getAutoCreateNewStructFragment())
{
var result = new Kekule.Molecule(id);
this.getChemSpace().appendChild(result);
return result;
}
else
return null;
},
/**
* Create a new atom in a blank parentMol to growing bonds.
* @param {Kekule.StructFragment} parentMol
* @param {String} id
* @param {Hash} absCoord
* @param {String} isotopeId Set null to create a default one
* @returns {Kekule.ChemStructureNode}
*/
createStructStartingAtom: function(parentMol, id, absCoord, isotopeId)
{
this.beginUpdateObject();
try
{
if (parentMol)
{
if (!isotopeId)
{
// create default one
isotopeId = this.getEditorConfigs().getStructureConfigs().getDefIsotopeId();
}
var initialNode = new Kekule.Atom(null, isotopeId);
parentMol.appendNode(initialNode);
initialNode.setAbsCoordOfMode(absCoord, this.getCoordMode());
return initialNode;
}
else
return null;
}
finally
{
this.endUpdateObject();
}
},
/**
* Create new molecule or structure fragment in chem space, the fragment containing a
* starting node (atom) to growing bond.
* @param {String} id
* @param {Hash} absCoord
* @param {String} isotopeId Set null to create a default one
* @returns {Kekule.ChemStructureNode}
*/
createNewStructFragmentAndStartingAtom: function(id, absCoord, isotopeId)
{
this.beginUpdateObject();
try
{
var struct;
/*
if (this.hasOnlyOneBlankStructFragment()) // only one blank molecule, use it as the new structure fragment's parent.
struct = this.getChemSpace().getChildAt(0);
else
*/
struct = this.createNewStructFragmentAnchor(id);
if (struct)
{
return this.createStructStartingAtom(struct, id, absCoord, isotopeId);
}
else
return null;
}
finally
{
this.endUpdateObject();
}
},
/**
* Check if there is only one blank structure fragment (with no node and connector or formula) in editor.
* @returns {Bool}
*/
hasOnlyOneBlankStructFragment: function()
{
var result = false;
if (this.getChemSpace().getChildCount() === 1)
{
var obj = this.getChemSpace().getChildAt(0);
if (obj instanceof Kekule.StructureFragment)
{
if (obj.getNodeCount() <= 0 && obj.getConnectorCount() <= 0 && !obj.hasFormula()) // empty molecule
result = true;
}
}
return result;
},
/**
* If there is only one blank structure fragment (with no node and connector or formula) in editor,
* returns it.
* @returns {Kekule.StructureFragment}
*/
getOnlyOneBlankStructFragment: function()
{
if (this.hasOnlyOneBlankStructFragment())
return this.getChemSpace().getChildAt(0);
},
/**
* Returns whether a new standalone object (e.g. molecule) can be added to editor.
* @returns {Bool}
*/
canAddNewStandaloneObject: function()
{
return this.getAllowCreateNewChild() || (this.getChemSpace().getChildCount() <= 0);
},
/**
* Returns whether a new structure fragment that doest not connected to any existing ones can be added to space.
* This function returns true if property autoCreateNewStructFragment is true or there is an empty molecule in space.
* @returns {Bool}
*/
canAddUnconnectedStructFragment: function()
{
return this.canAddNewStandaloneObject() || this.hasOnlyOneBlankStructFragment();
},
/**
* Returns whether current editor allows clone objects.
* @returns {Bool}
*/
canCloneObjects: function()
{
return this.getAllowCreateNewChild();
},
/**
* Clone objects in space.
* Note that this method only works when property allowCreateNewChild is true.
* @param {Array} objects
* @param {Hash} screenCoordOffset If this value is set, new cloned objects will be moved based on this coord.
* @param {Bool} addToSpace If true, the cloned objects will add to current space immediately.
* @param {Bool} keepRelations If true, the object reference relations between objects will be kept in the cloned ones.
* @returns {Array} Actually cloned objects.
*/
cloneObjects: function(objects, screenCoordOffset, addToSpace, keepRelations)
{
var self = this;
if (!this.getChemSpace())
return null;
/*
if (!this.getAllowCreateNewChild())
return null;
*/
var allowAddToSpace = this.getAllowCreateNewChild();
var isParentOfOneObj = function(obj, childObjs)
{
for (var i = 0, l = childObjs.length; i < l; ++i)
{
var childObj = childObjs[i];
if (/*(obj === childObj) ||*/ (childObj.isChildOf(obj)))
return true;
}
return false;
};
var removeUnessentialChildren = function(rootObj, refObj, reservedChildObjs)
{
if (reservedChildObjs.indexOf(refObj) >= 0)
{
AU.remove(reservedChildObjs, refObj);
return;
}
if (rootObj.getChildAt && refObj.getChildAt)
{
var refChildObjCount = refObj.getChildCount();
for (var i = refChildObjCount - 1; i >= 0; --i)
{
var o = rootObj.getChildAt(i);
if (!reservedChildObjs.length)
{
rootObj.removeChild(o);
continue;
}
var refChildObj = refObj.getChildAt(i);
if (reservedChildObjs.indexOf(refChildObj) >= 0)
{
AU.remove(reservedChildObjs, refChildObj);
}
else if (isParentOfOneObj(refChildObj, reservedChildObjs))
{
if (refChildObj.getChildObjs)
removeUnessentialChildren(o, refChildObj, reservedChildObjs);
}
else // can delete
{
rootObj.removeChild(o);
}
}
}
};
var _findParentObjOrSelf = function(targetObj, candidateObjs)
{
for (var i = 0, l = candidateObjs.length; i < l; ++i)
{
var candObj = candidateObjs[i];
if (targetObj === candObj || (targetObj.isChildOf && targetObj.isChildOf(candObj)))
return candObj;
}
return null;
};
var getRelatedObjRefRelations = function(standAloneObjs, owner)
{
var result = [];
if (!owner)
owner = self.getChemObj(); // the root chemspace
var relations = owner.getObjRefRelations && owner.getObjRefRelations();
if (relations)
{
// check each relation
for (var i = 0, l = relations.length; i < l; ++i)
{
var rel = relations[i];
var src = rel.srcObj;
var dests = AU.toArray(rel.dest);
var matchedSrc = _findParentObjOrSelf(src, standAloneObjs);
var matchedDests = [];
var matchAtLeastOneDest = false;
if (matchedSrc)
{
for (var j = 0, k = dests.length; j < k; ++j)
{
var dest = dests[j];
var matchedDest = _findParentObjOrSelf(dest, standAloneObjs);
if (matchedDest)
matchAtLeastOneDest = true;
matchedDests.push(matchedDest);
}
if (matchAtLeastOneDest)
result.push({'relation': rel, 'srcParent': matchedSrc, 'destParents': matchedDests});
}
}
}
return result;
};
var getNewObjRefRelation = function(relationMatch, oldObjs, clonedObjs)
{
var oldRelation = relationMatch.relation;
var oldSrcParent = relationMatch.srcParent;
var oldDestParents = relationMatch.destParents;
var indexStack, newSrcObj, newDestObjs = [];
// src
var srcIndex = oldObjs.indexOf(oldSrcParent);
var newSrcParent = clonedObjs[srcIndex];
if (oldRelation.srcObj === oldSrcParent)
newSrcObj = newSrcParent;
else
{
indexStack = oldSrcParent.indexStackOfChild && oldSrcParent.indexStackOfChild(oldRelation.srcObj);
if (indexStack)
{
newSrcObj = newSrcParent.getChildAtIndexStack && newSrcParent.getChildAtIndexStack(indexStack);
}
}
if (!newSrcObj)
return null;
// dest
var destIsArray = AU.isArray(oldRelation.dest);
var oldDests = destIsArray? oldRelation.dest: [oldRelation.dest];
for (var i = 0, l = oldDestParents.length; i <l; ++i)
{
var oldDestParent = oldDestParents[i];
var destIndex = oldObjs.indexOf(oldDestParent);
var newDestParent = clonedObjs[destIndex];
var newDestObj;
if (oldDestParent) // parent has been cloned
{
if (oldDestParent === oldDests[i])
newDestObjs.push(newDestParent);
else if (oldDests[i])
{
indexStack = oldDestParent.indexStackOfChild && oldDestParent.indexStackOfChild(oldDests[i]);
if (indexStack)
{
newDestObj = newDestParent.getChildAtIndexStack && newDestParent.getChildAtIndexStack(indexStack);
if (newDestObj)
newDestObjs.push(newDestObj);
}
}
}
}
if (!newDestObjs.length)
return null;
var newDestValue = destIsArray? newDestObjs: newDestObjs[0];
var newRelation = {'srcObj': newSrcObj, 'srcProp': oldRelation.srcProp, 'dest': newDestValue};
return newRelation;
};
var targetObjs = Kekule.ArrayUtils.toArray(objects);
var standAloneObjs = [];
var childObjMap = new Kekule.MapEx();
for (var i = 0, l = targetObjs.length; i < l; ++i)
{
var obj = targetObjs[i];
var standAloneObj = obj.getStandaloneAncestor? obj.getStandaloneAncestor(): obj;
if (standAloneObj.clone) // object can be cloned
{
Kekule.ArrayUtils.pushUnique(standAloneObjs, standAloneObj);
var mapItem = childObjMap.get(standAloneObj);
if (!mapItem)
{
mapItem = [];
childObjMap.set(standAloneObj, mapItem);
}
mapItem.push(obj);
}
}
// find related relations
var owner = this.getChemSpace();
var relationMatches = getRelatedObjRefRelations(standAloneObjs, owner);
// start clone
var space = this.getChemSpace();
var clonedObjs = [];
var coordMode = this.getCoordMode();
var allowCoordBorrow = this.getAllowCoordBorrow();
var standAloneObjCount = standAloneObjs.length;
/*
for (var i = 0; i < standAloneObjCount; ++i)
{
var obj = standAloneObjs[i];
var clonedObj = obj.clone();
// clear ids to avoid conflict
if (clonedObj.clearIds)
clonedObj.clearIds();
// remove unessential child objects of cloned object
removeUnessentialChildren(clonedObj, obj, childObjMap.get(obj));
if (addToSpace && allowAddToSpace)
{
space.appendChild(clonedObj);
if (screenCoordOffset)
{
var coord = this.getObjectScreenCoord(clonedObj);
var newCoord = Kekule.CoordUtils.add(coord, screenCoordOffset);
this.setObjectScreenCoord(clonedObj, newCoord);
}
}
clonedObjs.push(clonedObj);
}
*/
//var interSpace = new Kekule.IntermediateChemSpace();
//try
{
// clone objects first, and put them in a inter chemspace to build relations
for (var i = 0; i < standAloneObjCount; ++i)
{
var obj = standAloneObjs[i];
var clonedObj = obj.clone();
// clear ids to avoid conflict
if (clonedObj.clearIds)
clonedObj.clearIds();
clonedObjs.push(clonedObj);
}
//interSpace.appendChildren(clonedObjs);
// copy cloned object to a inter chem space, to build relations
// then map the new relations
var newRelations = [];
for (var i = 0, l = relationMatches.length; i < l; ++i)
{
var relationMatch = relationMatches[i];
var newRelation = getNewObjRefRelation(relationMatch, standAloneObjs, clonedObjs);
if (newRelation)
{
newRelations.push(newRelation);
/*
newRelation.srcObj.setPropValue(newRelation.srcProp.name, newRelation.dest); // link objects
console.log('new relation', newRelation, newRelation.srcObj.getOwner(), newRelation.srcObj, newRelation.srcObj.getPropValue(newRelation.srcProp.name));
*/
}
}
// then remove unessential children
for (var i = 0; i < standAloneObjCount; ++i)
{
var obj = standAloneObjs[i];
var clonedObj = clonedObjs[i];
// remove unessential child objects of cloned object
removeUnessentialChildren(clonedObj, obj, childObjMap.get(obj));
if (addToSpace && allowAddToSpace)
{
space.appendChild(clonedObj);
if (screenCoordOffset)
{
var coord = this.getObjectScreenCoord(clonedObj);
var newCoord = Kekule.CoordUtils.add(coord, screenCoordOffset);
this.setObjectScreenCoord(clonedObj, newCoord);
}
}
}
// at last rebuild the relations
for (var i = 0, l = newRelations.length; i < l; ++i)
{
var newRelation = newRelations[i];
// check if new src/dest are not removed
var finalDest;
var hasNewSrcObj = _findParentObjOrSelf(newRelation.srcObj, clonedObjs);
if (hasNewSrcObj)
{
if (AU.isArray(newRelation.dest))
{
finalDest = [];
for (var j = 0, k = newRelation.dest.length; j < k; ++j)
{
var destObj = newRelation.dest[j];
if (_findParentObjOrSelf(destObj, clonedObjs))
finalDest.push(destObj);
}
if (!finalDest.length)
finalDest = null;
}
else
{
if (_findParentObjOrSelf(newRelation.dest, clonedObjs))
finalDest = newRelation.dest;
}
if (finalDest) // now we can map the new relation
{
newRelation.srcObj.setPropValue(newRelation.srcProp.name, finalDest); // link objects
//console.log('new relation', newRelation, newRelation.srcObj.getOwner(), newRelation.srcObj, newRelation.srcObj.getPropValue(newRelation.srcProp.name));
}
}
}
}
//finally
{
//interSpace.finalize();
}
childObjMap.finalize();
return clonedObjs;
},
/**
* Clone objects in editor's selection.
* @param {Hash} coordOffset New cloned objects will be moved based on this coord.
* If this value is not set, a default one will be used.
* @param {Bool} addToSpace If true, the objects cloned will be added to space immediately.
* @param {Bool} allowCloneSpace If true, the chemspace itself can be cloned when being selected.
* Otherwise, the cloned targets are its children.
* @returns {Array} Actually cloned objects.
*/
cloneSelection: function(coordOffset, addToSpace, allowCloneSpace)
{
var _getActualTargetObjs = function(objs, allowCloneSpace)
{
var result = [];
for (var i = 0, l = objs.length; i < l; ++i)
{
var obj = objs[i];
if ((obj instanceof Kekule.ChemSpace) && !allowCloneSpace) // can not clone chemspace, use its children instead
{
var children = obj.getChildren();
AU.pushUnique(result, children);
}
else
AU.pushUnique(result, obj);
}
return result;
};
if (coordOffset === undefined) // use default one
{
coordOffset = this.getDefaultCloneScreenCoordOffset();
}
var objs = this.getSelection();
objs = _getActualTargetObjs(objs, allowCloneSpace);
var clonedObjs = this.cloneObjects(objs, coordOffset, addToSpace);
if (addToSpace)
this.setSelection(clonedObjs);
return clonedObjs;
},
/**
* Returns default coord offset when doing clone selection job in editor.
* @returns {Hash}
*/
getDefaultCloneScreenCoordOffset: function()
{
var screenOffset = this.getEditorConfigs().getInteractionConfigs().getClonedObjectScreenOffset() || 0;
var coordMode = this.getCoordMode();
var coordOffset = {'x': screenOffset, 'y': screenOffset};
if (coordMode === Kekule.CoordMode.COORD3D)
{
coordOffset.z = screenOffset;
}
//coordOffset = this.translateCoord(coordOffset, Kekule.Editor.CoordSys.SCREEN, Kekule.Editor.CoordSys.OBJ);
return coordOffset;
},
/**
* Supply essential charge and radical markers when loading a new chemObj.
* @private
*/
_supplyChemMarkersOnObj: function(chemObj)
{
if (chemObj)
{
var structFragments = Kekule.ChemStructureUtils.getAllStructFragments(chemObj, true);
if (structFragments || structFragments.length)
{
for (var i = 0, l = structFragments.length; i < l; ++i)
{
this._createLosingChemMarkerOnStructFragment(structFragments[i]);
}
}
}
},
/** @private */
_createLosingChemMarkerOnStructFragment: function(mol)
{
mol.beginUpdate();
try
{
// if (mol.getCharge && mol.getCharge())
if (mol.hasExplicitCharge && mol.hasExplicitCharge())
mol.fetchChargeMarker(true);
// then the children
var