kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,717 lines (1,569 loc) • 49 kB
JavaScript
/**
* @fileoverview
* Widgets to modify a chem object in an in-place way in chem editor.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /utils/kekule.utils.js
* requires /widgets/kekule.widget.base.js
* requires /widgets/kekule.widget.buttons.js
* requires /widgets/kekule.chemEditor.baseEditor.js
* requires /widgets/operation/kekule.operations.js
*/
(function(){
"use strict";
/** @ignore */
Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, {
CHEMEDITOR_ATOM_SETTER: 'K-ChemEditor-Atom-Setter',
CHEMEDITOR_TEXT_SETTER: 'K-ChemEditor-Text-Setter',
CHEMEDITOR_FORMULA_SETTER: 'K-ChemEditor-Formula-Setter',
CHEMEDITOR_CHEM_CONDITION_SETTER: 'K-ChemEditor-ChemCondition-Setter'
});
var AU = Kekule.ArrayUtils;
var CE = Kekule.Editor;
var CNS = Kekule.Widget.HtmlClassNames;
var CCNS = Kekule.ChemWidget.HtmlClassNames;
var BNS = Kekule.ChemWidget.ComponentWidgetNames;
/**
* A Util class to manage all embedded setters in an editor.
* @class
*/
Kekule.Editor.EmbeddedSetterHolder = Class.create(ObjectEx,
/** @lends Kekule.Editor.EmbeddedSetterHolder# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetterHolder',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize');
this.setPropStoreFieldValue('editor', editor);
this.setPropStoreFieldValue('setterInstanceMap', new Kekule.MapEx(true));
},
/** @private */
initProperties: function()
{
this.defineProp('editor', {
'dataType': 'Kekule.Editor.BaseEditor', 'serializable': false, 'setter': null
});
// private
this.defineProp('setterInstanceMap', {
'dataType': DataType.OBJECT, 'serializable': false, 'setter': null
});
},
/** @ignore */
finalize: function()
{
var map = this.getSetterInstanceMap();
var instances = map.getValues() || [];
for (var i = 0, l = instances.length; i < l; ++i)
{
var inst = instances[i];
if (inst)
inst.finalize();
}
map.finalize();
this.tryApplySuper('finalize');
},
/**
* Returns a setter instance according to setterClass.
* @param {ClassEx} setterClass
* @param {Bool} doNotAutoCreate
* @returns {Kekule.Editor.EmbeddedSetter.Base}
*/
getSetter: function(setterClass, doNotAutoCreate)
{
var map = this.getSetterInstanceMap();
var result = map.get(setterClass);
if (!result && !doNotAutoCreate)
{
result = new setterClass(this.getEditor());
map.set(setterClass, result);
}
return result;
}
});
ClassEx.defineProp(Kekule.Editor.BaseEditor, 'embeddedSetterHolder', {
'dataType': 'Kekule.Editor.EmbeddedSetterHolder', 'serializable': false, 'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('embeddedSetterHolder');
if (!result)
{
result = new Kekule.Editor.EmbeddedSetterHolder(this);
this.setPropStoreFieldValue('embeddedSetterHolder', result);
}
return result;
}
});
ClassEx.extendMethods(Kekule.Editor.BaseEditor, {
getEmbeddedSetter: function ($origin, setterClass) {
return this.getEmbeddedSetterHolder().getSetter(setterClass);
}
});
Kekule.Editor.EmbeddedSetterManager = {
_setterRegistryItems: [],
register: function(setterClass, targetObjClasses)
{
var objClasses = Kekule.ArrayUtils.toArray(targetObjClasses);
for (var i = 0, l = objClasses.length; i < l; ++i)
{
ESM._setterRegistryItems.push({
'setter': setterClass,
'target': objClasses[i]
});
}
},
unregister: function(setterClass, targetObjClasses)
{
var items = ESM._setterRegistryItems;
var objClasses = Kekule.ArrayUtils.toArray(targetObjClasses);
for (var i = 0, l = objClasses.length; i < l; ++i)
{
for (var j = items.length - 1; j >= 0; --j)
{
if (items[j].setter === setterClass && items[j].target === objClasses[i])
{
items.splice(j, 1);
break;
}
}
}
},
getDefaultSetterClassForObjectClass: function(objClass)
{
var items = ESM._setterRegistryItems;
for (var i = items.length - 1; i >= 0; --i)
{
if (items[i].target === objClass || ClassEx.isDescendantOf(objClass, items[i].target))
return items[i].setter;
}
},
getDefaultSetterClassForObject: function(obj)
{
var result = ESM.getDefaultSetterClassForObjectClass(obj.getClass());
if (!result)
return null;
else if (!result.isValidTarget || result.isValidTarget(obj))
return result;
else
return null;
}
};
var ESM = Kekule.Editor.EmbeddedSetterManager;
/**
* Base namespace of all embedded setters.
* @namespace
*/
Kekule.Editor.EmbeddedSetter = {};
/**
* Base class of embedded setters for chem editor.
* @class
* @augments ObjectEx
*
* @param {Kekule.Editor.BaseEditor} editor
*
* @property {Kekule.Editor.BaseEditor} editor
* @property {Kekule.ChemObject} targetObj The object to be modified.
* @property {Kekule.Widget.BaseWidget} widget The concrete setter widget.
*/
Kekule.Editor.EmbeddedSetter.Base = Class.create(ObjectEx,
/** @lends Kekule.Editor.EmbeddedSetter.Base# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.ObjModifier.Base',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize');
this.setPropStoreFieldValue('editor', editor);
},
/** @private */
initProperties: function()
{
this.defineProp('editor', {
'dataType': 'Kekule.Editor.BaseEditor', 'serializable': false, 'setter': null,
});
},
/** @private */
getEditorConfigs: function()
{
return this.getEditor().getEditorConfigs();
},
/**
* Indicating whether this setter can be used to modify several objects simultaneously in editor.
* Descendants may override this method.
* @returns {Bool}
*/
getAllowSettingMultiObjs: function()
{
return false;
},
/**
* Indicating whether this setter can be used to create new object in editor.
* Descendants may override this method.
* @returns {Bool}
*/
getAllowCreateNewObj: function()
{
return false;
},
/** @private */
getBaseCoordOfObjs: function(objs)
{
var objCount = objs.length;
if (objCount) {
var sumCoord = {};
for (var i = 0; i < objCount; ++i) {
var coord = this.getEditor().getObjectScreenCoord(objs[i]);
sumCoord = Kekule.CoordUtils.add(sumCoord, coord);
}
var baseCoord = Kekule.CoordUtils.divide(sumCoord, objCount);
return baseCoord;
}
return null;
},
/**
* Open the setter widget to modify the targetObjs
* @param {Array} targetObjs
* @param {Hash} invokeCoord
* @param {Func} callback callback(applied, modifiedObjs, extraData)
*/
execute: function(targetObjs, invokeCoord, callback)
{
var objs;
if (!targetObjs) // no object provided, may be need to create new object?
{
objs = null;
if (!this.getAllowCreateNewObj())
return false;
}
else
{
objs = Kekule.ArrayUtils.toArray(targetObjs);
objs = this.getAllowSettingMultiObjs()? objs : [objs[0]];
}
var baseCoord = objs? this.getBaseCoordOfObjs(objs): invokeCoord;
this._currTargetObjs = objs;
this._currExecuteCallback = callback;
return this.doExecute(objs, baseCoord, callback);
},
/**
* Do the actual work of execute method.
* Descendants should override this method.
* @param {Array} targetObjs
* @param {Hash} baseCoord
* @private
*/
doExecute: function(targetObjs, baseCoord, callback)
{
// do nothing here
},
/**
* Notify the modification or target objects is done or cancelled.
* @param {Bool} applied
* @param {Array} modifiedObjs
* @param {Variant} extraData extra data, set by each type of embedded setters.
*/
notifyModificationResult: function(applied, modifiedObjs, extraData)
{
if (this._currExecuteCallback)
{
if (applied && !modifiedObjs)
modifiedObjs = this._currTargetObjs;
this._currExecuteCallback(applied, modifiedObjs, extraData);
}
},
});
/**
* Embedded setter to change atom in molecule.
* @class
* @augments Kekule.Editor.EmbeddedSetter.Base
*/
Kekule.Editor.EmbeddedSetter.MolAtom = Class.create(Kekule.Editor.EmbeddedSetter.Base,
/** @lends Kekule.Editor.EmbeddedSetter.MolAtom# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetter.MolAtom',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize', [editor]);
this._createNonAtomLabelInfos();
this._setterShown = false; // use internally
},
finalize: function()
{
if (this.getAtomSetter())
this.getAtomSetter().finalize();
this.tryApplySuper('finalize');
},
/** @private */
initProperties: function()
{
this.defineProp('currAtom', {'dataType': DataType.OBJECT, 'serializable': false}); // private
this.defineProp('nonAtomLabelInfos', {'dataType': DataType.ARRAY, 'serializable': false}); // private
this.defineProp('atomSetter', {
'dataType': DataType.OBJECT, 'serializable': false,
'setter': function(value)
{
var old = this.getAtomSetter();
if (old)
this.unbindAtomSetter(old);
this.setPropStoreFieldValue('atomSetter', value);
if (value)
this.bindAtomSetter(value);
}
}); // private
},
/** @private */
canInteractWithObj: function(obj)
{
if (this.isValidNode(obj))
return true;
else
return false;
},
/**
* Check if obj is a valid chem node and can be edited.
* @param {Kekule.ChemObject} obj
* @returns {Bool}
* @private
*/
isValidNode: function(obj)
{
// return (obj instanceof Kekule.ChemStructureNode) && !(obj instanceof Kekule.StructureFragment && obj.isStandalone());
return Kekule.Editor.EmbeddedSetter.MolAtom.isValidTarget(obj);
},
/** @private */
/*
_getVarAtomListLabel: function()
{
var labelConfigs = this.getEditor().getRenderConfigs().getDisplayLabelConfigs();
return labelConfigs? labelConfigs.getVariableAtom(): Kekule.ChemStructureNodeLabels.VARIABLE_ATOM;
},
_getVarAtomNotListLabel: function()
{
var labelConfigs = this.getEditor().getRenderConfigs().getDisplayLabelConfigs();
return '~' + (labelConfigs? labelConfigs.getVariableAtom(): Kekule.ChemStructureNodeLabels.VARIABLE_ATOM);
},
*/
/** @private */
_createNonAtomLabelInfos: function()
{
/*
var result = [];
var labelConfigs = this.getEditor().getRenderConfigs().getDisplayLabelConfigs();
// R group
result.push({
'text': labelConfigs.getRgroup(), 'nodeClass': Kekule.RGroup,
'description': Kekule.$L('ChemWidgetTexts.CAPTION_RGROUP') //Kekule.ChemWidgetTexts.CAPTION_RGROUP
});
// Kekule.Pseudoatom
result.push({
'text': labelConfigs.getDummyAtom(), 'nodeClass': Kekule.Pseudoatom,
'props': {'atomType': Kekule.PseudoatomType.DUMMY},
'description': Kekule.$L('ChemWidgetTexts.CAPTION_DUMMY_ATOM') //Kekule.ChemWidgetTexts.CAPTION_DUMMY_ATOM
});
result.push({
'text': labelConfigs.getHeteroAtom(), 'nodeClass': Kekule.Pseudoatom,
'props': {'atomType': Kekule.PseudoatomType.HETERO},
'description': Kekule.$L('ChemWidgetTexts.CAPTION_HETERO_ATOM') //Kekule.ChemWidgetTexts.CAPTION_HETERO_ATOM
});
result.push({
'text': labelConfigs.getAnyAtom(), 'nodeClass': Kekule.Pseudoatom,
'props': {'atomType': Kekule.PseudoatomType.ANY},
'description': Kekule.$L('ChemWidgetTexts.CAPTION_ANY_ATOM') //Kekule.ChemWidgetTexts.CAPTION_ANY_ATOM
});
// Kekule.VariableAtom List and Not List
result.push({
'text': this._getVarAtomListLabel(), 'nodeClass': Kekule.VariableAtom, 'isVarList': true,
'description': Kekule.$L('ChemWidgetTexts.CAPTION_VARIABLE_ATOM') //Kekule.ChemWidgetTexts.CAPTION_VARIABLE_ATOM
});
result.push({
'text': this._getVarAtomNotListLabel(), 'nodeClass': Kekule.VariableAtom, 'isVarNotList': true,
'description': Kekule.$L('ChemWidgetTexts.CAPTION_VARIABLE_NOT_ATOM') //Kekule.ChemWidgetTexts.CAPTION_VARIABLE_NOT_ATOM
});
*/
var editor = this.getEditor();
var result = editor && editor.getEnabledNonAtomInputData && editor.getEnabledNonAtomInputData();
this.setNonAtomLabelInfos(result);
return result;
},
/** @private */
_indexOfNonAtomLabel: function(nodeLabel)
{
var infos = this.getNonAtomLabelInfos();
for (var i = 0, l = infos.length; i < l; ++i)
{
var info = infos[i];
if (info.nodeLabel === nodeLabel)
return i;
}
return -1;
},
/** @private */
_getNonAtomInfo: function(nodeLabel)
{
var index = this._indexOfNonAtomLabel(nodeLabel);
return (index < 0)? null: this.getNonAtomLabelInfos()[index];
},
/** @private */
getAtomSetterWidget: function(canCreate)
{
var result = this.getAtomSetter();
if (!result && canCreate) // create new one
{
var parentElem = this.getEditor().getCoreElement();
var doc = parentElem.ownerDocument;
result = this._createAtomSetterWidget(doc, parentElem);
this.setAtomSetter(result);
}
return result;
},
/** @private */
bindAtomSetter: function(atomSetterWidget)
{
this._initAtomSetterWidgetSettings(atomSetterWidget);
this._setAtomSetterEventListener(atomSetterWidget, true);
},
/** @private */
unbindAtomSetter: function(atomSetterWidget)
{
this._setAtomSetterEventListener(atomSetterWidget, false);
},
/** @private */
_createAtomSetterWidget: function(doc, parentElem)
{
var result = new Kekule.ChemWidget.StructureNodeSetter(this.getEditor());
result.setUseDropDownSelectPanel(true);
//this._initAtomSetterWidgetSettings(result);
result.appendToElem(parentElem);
return result;
},
/** @private */
_initAtomSetterWidgetSettings: function(widget)
{
widget.addClassName(CCNS.CHEMEDITOR_ATOM_SETTER);
if (widget.setSelectableInfos)
{
var listAtoms = AU.clone(this.getEditor().getEditorConfigs().getStructureConfigs().getPrimaryOrgChemAtoms());
listAtoms.push('...'); // add periodic table item
//result.setSelectableElementSymbols(listAtoms);
// non-atom nodes
var nonAtomLabelInfos = this.getNonAtomLabelInfos();
//result.setSelectableNonElementInfos(nonAtomLabelInfos);
// subgroups
//result.setSelectableSubGroupRepItems(Kekule.Editor.StoredSubgroupRepositoryItem2D.getAllRepItems());
widget.setSelectableInfos({
'elementSymbols': listAtoms,
'nonElementInfos': nonAtomLabelInfos,
'subGroupRepItems': Kekule.Editor.StoredSubgroupRepositoryItem2D.getAllRepItems()
});
}
},
/** @private */
_setAtomSetterEventListener: function(widget, isBind)
{
// react to value change of setter
//var self = this;
/*
widget.addEventListener('keyup', function(e)
{
var ev = e.htmlEvent;
if (ev.getKeyCode() === Kekule.X.Event.KeyCode.ENTER)
{
self.applySetter(widget);
widget.dismiss(); // avoid call apply setter twice
}
}
);
widget.addEventListener('valueChange', function(e){
//var data = e.value;
//console.log(e.target, e.currentTarget);
if (self.getAtomSetter() && self.getAtomSetter().isShown())
{
self.applySetter(widget);
widget.dismiss(); // avoid call apply setter twice
}
});
widget.addEventListener('showStateChange', function(e)
{
if (e.target === widget && !e.byDomChange)
{
//console.log('show state change', e);
if (!e.isShown && !e.isDismissed) // widget hidden, feedback the edited value
{
if (self.getAtomSetter() && self.getAtomSetter().isShown())
self.applySetter(widget);
}
}
}
);
*/
if (!isBind)
{
widget.removeEventListener('valueChange', this._reactAtomSetterValueChange, this);
widget.removeEventListener('showStateChange', this._reactAtomSetterShowStateChange, this);
}
else
{
widget.addEventListener('valueChange', this._reactAtomSetterValueChange, this);
widget.addEventListener('showStateChange', this._reactAtomSetterShowStateChange, this);
}
},
/** @private */
_reactAtomSetterValueChange: function(e)
{
var widget = this.getAtomSetter();
if (widget && widget.isShown())
{
if (e.value) // ensure there is actual changes
{
this.applySetter(widget);
this.notifyModificationResult(true, null);
}
widget.dismiss(); // avoid call apply setter twice
}
},
/** @private */
_reactAtomSetterShowStateChange: function(e)
{
var widget = this.getAtomSetter();
if (e.target === widget && !e.byDomChange)
{
if (!e.isShown && !e.isDismissed) // widget hidden, feedback the edited value
{
/*
if (widget && widget.isShown())
this.applySetter(widget);
*/
this.notifyModificationResult(false);
}
}
},
/** @private */
getPeriodicTableDialogWidget: function(canCreate)
{
var result = this.getPeriodicTableDialog();
if (!result && canCreate) // create new one
{
var parentElem = this.getEditor().getCoreElement();
var doc = parentElem.ownerDocument;
result = this._createPeriodicTableDialogWidget(doc, parentElem);
this.setPeriodicTableDialog(result);
}
//console.log(result);
return result;
},
/** @private */
_createPeriodicTableDialogWidget: function(doc, parentElem)
{
var dialog = new Kekule.Widget.Dialog(doc, Kekule.$L('ChemWidgetTexts.CAPTION_PERIODIC_TABLE_DIALOG') /*CWT.CAPTION_PERIODIC_TABLE_DIALOG*/,
[Kekule.Widget.DialogButtons.OK, Kekule.Widget.DialogButtons.CANCEL]
);
var table = new Kekule.ChemWidget.PeriodicTable(doc);
table.setUseMiniMode(true);
table.setEnableSelect(true);
table.appendToElem(dialog.getClientElem());
this.setPeriodicTable(table);
return dialog;
},
/**
* Open atom edit box for obj in coord.
* @param {Hash} coord
* @param {Object} obj
*/
openSetterUi: function(coord, obj)
{
var oldSetter = this.getAtomSetter(); // check if there is old already created setter
if (oldSetter && oldSetter.isShown()) // has a old setter
{
//this.applySetter(oldSetter, this.getCurrAtom());
oldSetter.hide();
// IMPORTANT: ensure the hide process done quickly
// and the unprepare process of popup atom setter do not imfluence the prepare process of it
oldSetter._haltPrevShowHideProcess();
}
if (!this.isValidNode(obj))
return;
this.setCurrAtom(obj);
var fontSize = this.getEditor().getEditorConfigs().getInteractionConfigs().getAtomSetterFontSize() || 0;
fontSize *= this.getEditor().getZoom() || 1;
var posAdjust = fontSize / 1.5; // adjust position to align to atom center
var setter = this.getAtomSetterWidget(true);
//setter.setEditor(this.getEditor());
setter.setLabelConfigs(this.getEditor().getRenderConfigs().getDisplayLabelConfigs());
setter.setNodes([obj]);
var parentElem = this.getEditor().getCoreElement();
setter.appendToElem(parentElem); // ensure setter widget is a child of parentElem, since popup show may change the parent each time
var inputBox = setter.getNodeInputBox();
//setter.setIsDirty(false);
//setter.setIsPopup(true);
var style = setter.getElement().style;
style.position = 'absolute';
style.left = (coord.x - posAdjust) + 'px';
style.top = (coord.y - posAdjust) + 'px';
style.opacity = 1;
//inputBox.getElement().style.fontSize = fontSize + 'px';
if (setter.setNodeInputBoxFontSize)
setter.setNodeInputBoxFontSize(fontSize + 'px');
/*
style.marginTop = -posAdjust + 'px';
style.marginLeft = -posAdjust + 'px';
*/
//setter.show();
setter._applied = false;
setter.setStandaloneOnShowHide(true);
setter.show(this.getEditor(), null, Kekule.Widget.ShowHideType.POPUP);
(function(){
inputBox.focus();
inputBox.selectAll();
}).defer();
//result.selectAll.bind(result).defer();
/*
setter.selectAll();
setter.focus();
*/
},
/** @private */
applySetter: function(setter, atom)
{
if (setter._applied) // avoid called twice
return;
if (!atom)
atom = this.getCurrAtom();
var newData = setter.getValue();
if (!newData)
return;
var operation = Kekule.Editor.OperationUtils.createNodeModificationOperationFromData(atom, newData, this.getEditor());
if (operation) // only execute when there is real modification
{
var editor = this.getEditor();
editor.beginManipulateAndUpdateObject();
try
{
operation.execute();
}
catch (e)
{
//Kekule.error(/*Kekule.ErrorMsg.NOT_A_VALID_ATOM*/Kekule.$L('ErrorMsg.NOT_A_VALID_ATOM'));
throw(e);
}
finally
{
editor.endManipulateAndUpdateObject();
}
if (editor && editor.getEnableOperHistory() && operation)
{
editor.pushOperation(operation);
}
}
},
/** @ignore */
doExecute: function(targetObjs, baseCoord, callback)
{
// only consider one object
this.openSetterUi(baseCoord, targetObjs[0]);
}
});
/** @ignore */
Kekule.Editor.EmbeddedSetter.MolAtom.isValidTarget = function(obj)
{
return (obj instanceof Kekule.ChemStructureNode) && !(obj instanceof Kekule.StructureFragment && obj.isStandalone());
};
/**
* Base embedded to setter to add or edit a block-based object in document.
* This class is the ancestor of several concrete setter classes, and should not be used directly.
* @class
* @augments Kekule.Editor.EmbeddedSetter.Base
*/
Kekule.Editor.EmbeddedSetter.CreatableBlock = Class.create(Kekule.Editor.EmbeddedSetter.Base,
/** @lends Kekule.Editor.EmbeddedSetter.CreatableBlock# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetter.CreatableBlock',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize', [editor]);
this._operAddObj = null; // private
},
/** @private */
initProperties: function()
{
this.defineProp('currObj', {'dataType': DataType.OBJECT, 'serializable': false}); // private
this.defineProp('setterWidget', {'dataType': DataType.OBJECT, 'serializable': false}); // private
},
/** @ignore */
getAllowCreateNewObj: function()
{
return true;
},
/**
* Check if obj is a valid object and can be edited.
* Descendants should override this method.
* @param {Kekule.ChemObject} obj
* @returns {Bool}
* @private
*/
isValidObj: function(obj)
{
return true;
},
/**
* Check if new object can be inserted into editor.
* Descendants should override this method.
* @param chemspace
* @param coord
* @private
*/
canCreateNewObj: function(chemSpace, coord)
{
var editor = this.getEditor();
return editor.canCreateNewChild();
},
/**
* Create new object in editor.
* Descendants should override this method
* @param chemSpace
* @param coord
* @private
*/
doCreateNewObj: function(chemSpace, coord)
{
// do nothing here
},
/** @private */
createNewObj: function(chemSpace, coord)
{
var result;
var editor = this.getEditor();
if (!this.canCreateNewObj())
return null;
editor.beginUpdateObject();
try
{
if (!result)
result = this.doCreateNewObj(chemSpace, coord);
chemSpace.appendChild(result);
// editor.setObjectScreenCoord(result, coord);
var addOperation = new Kekule.ChemObjOperation.Add(result, chemSpace, null, editor);
this._operAddObj = addOperation;
}
finally
{
editor.endUpdateObject();
}
return result;
},
/** @private */
retrieveSetterWidget: function(canCreate)
{
var result = this.getSetterWidget();
if (!result && canCreate) // create new one
{
var parentElem = this.getEditor().getCoreElement();
var doc = parentElem.ownerDocument;
result = this.createSetterWidget(doc, parentElem);
this.setSetterWidget(result);
}
return result;
},
/** @private */
createSetterWidget: function(doc, parentElem)
{
var result = this.doCreateSetterWidget(doc, parentElem, this.getEditor()); // new Kekule.Widget.TextBox(this.getEditor());
result.appendToElem(parentElem);
// default event handler
var self = this;
/*
result.addEventListener('keyup', function(e)
{
var ev = e.htmlEvent;
var keyCode = ev.getKeyCode();
if (keyCode === Kekule.X.Event.KeyCode.ENTER) // ctrl+enter
{
self.applySetter(result);
result.dismiss(); // avoid call apply setter twice
}
else if (keyCode === Kekule.X.Event.KeyCode.ESC) // ESC, cancel editor
{
result.dismiss();
self.cancelSetter();
}
}
);
*/
result.addEventListener('showStateChange', function(e)
{
//console.log('show state change', e.isShown, e.isDismissed, e.byDomChange);
if (!e.byDomChange)
{
if (!e.isShown && e.isDismissed)
{
self.notifyModificationResult(false);
}
else if (!e.isShown && !e.isDismissed) // widget hidden, feedback the edited value
{
self.applySetter(result);
}
if (e.isShown) // set applied to false on newly shown widget
result._applied = false;
}
}
);
return result;
},
/**
* Create a new setter widget.
* Descendants should override this method.
* @param doc
* @param parentElem
*/
doCreateSetterWidget: function(doc, parentElem, parentWidget)
{
// do nothing here
},
/**
* Returns the modified values of current setter widget, those values need to be applied to target object.
* If this function returns null, the target object need to be removed from editor.
* Descendants should override this method.
* @param widget
* @returns {Object}
*/
getSetterWidgetModifiedValues: function(widget)
{
return null;
},
/** @private */
needToModifyObj: function(obj, modifiedValues)
{
// ensure the property is really modified
var needDoModification = false;
var propNames = Object.getOwnPropertyNames(modifiedValues);
for (var i = 0, l = propNames.length; i < l; ++i)
{
var propName = propNames[i];
if (obj.hasProperty(propName) && obj.getPropValue(propName) !== modifiedValues[propName]) {
needDoModification = true;
break;
}
}
return needDoModification;
},
/** @private */
createTargetObjRemoveOper: function(obj)
{
return new Kekule.ChemObjOperation.Remove(obj, obj.getParent(), null, this.getEditor());
},
/** @private */
createTargetObjModifyOper: function(obj, modifiedValues)
{
return new Kekule.ChemObjOperation.Modify(obj, modifiedValues, this.getEditor());
},
/** @private */
cancelSetter: function()
{
if (this._operAddObj) // already created new formula, remove it
{
this._operAddObj.reverse();
this._operAddObj = null;
}
},
/** @private */
applySetter: function(setter, obj)
{
if (setter._applied) // avoid call twice
return;
if (!obj)
obj = this.getCurrObj();
var modifiedValues = this.getSetterWidgetModifiedValues(setter);
var oper;
// var text = setter.getText();
// if (!text) // no input, delete
if (!modifiedValues)
{
if (this._operAddObj) // new object just added to space
this.cancelSetter();
else // old one, delete it
{
oper = this.createTargetObjRemoveOper(obj);
}
}
else
{
// ensure the property is really modified
var needDoModification = this.needToModifyObj(obj, modifiedValues);
if (needDoModification)
oper = this.createTargetObjModifyOper(obj, modifiedValues); // new Kekule.ChemObjOperation.Modify(obj.getFormula(), {'text': text}, this.getEditor());
}
var editor = this.getEditor();
if (editor && oper)
{
editor.beginManipulateAndUpdateObject();
try
{
oper.execute();
if (editor.getEnableOperHistory())
{
if (this._operAddObj)
{
var group = new Kekule.MacroOperation();
group.add(this._operAddObj);
group.add(oper);
editor.pushOperation(group);
this._operAddObj = null;
}
else
editor.pushOperation(oper);
this.notifyModificationResult(true,[obj]);
}
}
finally
{
editor.endManipulateAndUpdateObject();
}
}
setter._applied = true;
},
/**
* Open setter widget in coord.
* @param {Hash} coord
* @param {Object} obj
* @private
*/
openSetterUi: function(coord, obj)
{
var oldSetter = this.getSetterWidget();
if (oldSetter && oldSetter.isShown()) // has an old setter
{
this.applySetter(oldSetter, this.getCurrObj());
// oldSetter.hide();
oldSetter._haltPrevShowHideProcess();
}
if (!obj) // need create new
obj = this.createNewObj(this.getEditor().getChemObj(), coord);
if (!this.isValidObj(obj))
return;
this.setCurrObj(obj);
this.getEditor().setSelection([obj]);
var setter = this.retrieveSetterWidget(true);
var parentElem = this.getEditor().getCoreElement();
setter.appendToElem(parentElem); // ensure setter widget is a child of parentElem, since popup show may change the parent each time
var style = setter.getElement().style;
style.position = 'absolute';
style.left = coord.x + 'px';
style.top = coord.y + 'px';
// style.fontSize = fontSize + 'px';
// style.fontFamily = fontName;
/*
style.marginTop = -posAdjust + 'px';
style.marginLeft = -posAdjust + 'px';
*/
setter._applied = false;
setter.setStandaloneOnShowHide(true);
this.prepareOpeningSetterUi(setter, obj, coord);
setter.show(this.getEditor(), null, Kekule.Widget.ShowHideType.POPUP);
var self = this;
(function() {
self.afterOpeningSetterUi(setter, obj, coord);
}).defer();
},
/**
* Descendants should override this.
* @param setterWidget
* @param targetObj
* @param baseCoord
* @private
*/
prepareOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
// do nothing here
},
/**
* Descendants should override this.
* @param setterWidget
* @param targetObj
* @param baseCoord
* @private
*/
afterOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
// do nothing here
},
/** @ignore */
doExecute: function(targetObjs, baseCoord, callback)
{
// defer the open process, ensure it be called after the hide event of old setter widget
var self = this;
(function() {
// only consider one object
self.openSetterUi(baseCoord, targetObjs && targetObjs[0]);
}).defer();
}
});
/**
* Embedded to setter to add or edit formula based molecule in document.
* @class
* @augments Kekule.Editor.EmbeddedSetter.CreatableBlock
*/
Kekule.Editor.EmbeddedSetter.Formula = Class.create(Kekule.Editor.EmbeddedSetter.CreatableBlock,
/** @lends Kekule.Editor.EmbeddedSetter.Formula# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetter.Formula',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize', [editor]);
},
/** @ignore */
isValidObj: function(obj)
{
return Kekule.Editor.EmbeddedSetter.Formula.isValidTarget(obj);
},
/**
* Returns plain text of formula that shows in text setter.
* @param {Kekule.StructureFragment} mol
* @returns {String}
* @private
*/
getFormulaText: function(mol)
{
return mol.hasFormula()? mol.getFormula().getText(): '';
},
/** @ignore */
canCreateNewObj: function(chemSpace, coord)
{
var result = this.tryApplySuper('canCreateNewObj', [chemSpace, coord]);
if (!result)
{
var mol = this.getEditor().getOnlyOneBlankStructFragment();
result = !!mol;
}
return result;
},
/** @ignore */
doCreateNewObj: function(chemSpace, coord)
{
var result;
var editor = this.getEditor();
if (!editor.canCreateNewChild())
{
result = this.getEditor().getOnlyOneBlankStructFragment();
}
else
{
result = new Kekule.Molecule();
}
editor.setObjectScreenCoord(result, coord);
result.getFormula(true); // create a forumla
return result;
},
/** @ignore */
doCreateSetterWidget: function(doc, parentElem, parentWidget)
{
var result = new Kekule.Widget.Container(this.getEditor());
result.addClassName(CCNS.CHEMEDITOR_FORMULA_SETTER);
result.setDisplayed(false);
result.appendToElem(parentElem)
var inputter = new Kekule.Widget.TextBox(result);
// event handler
var self = this;
inputter.addEventListener('keyup', function(e)
{
var ev = e.htmlEvent;
var keyCode = ev.getKeyCode();
if (keyCode === Kekule.X.Event.KeyCode.ENTER) // enter
{
self.applySetter(result);
result.dismiss(); // avoid call apply setter twice
}
else if (keyCode === Kekule.X.Event.KeyCode.ESC) // ESC, cancel editor
{
result.dismiss();
self.cancelSetter();
}
}
);
return result;
},
/** @ignore */
getSetterWidgetModifiedValues: function(widget)
{
var formulaText = widget.getChildWidgets()[0].getValue();
return formulaText? {'formulaText': widget.getChildWidgets()[0].getValue()}: null;
},
/** @private */
needToModifyObj: function(obj, modifiedValues) {
return this.applySuper('needToModifyObj', [obj.getFormula(), {'text': modifiedValues.formulaText}]);
},
/** @ignore */
createTargetObjModifyOper: function(obj, modifiedValues)
{
return new Kekule.ChemObjOperation.Modify(obj.getFormula(), {'text': modifiedValues.formulaText}, this.getEditor());
},
/** @ignore */
prepareOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
var mol = targetObj;
var fontSize = mol.getCascadedRenderOption('fontSize') || this.getEditor().getEditorConfigs().getInteractionConfigs().getAtomSetterFontSize();
fontSize *= this.getEditor().getZoom() || 1;
var fontName = mol.getCascadedRenderOption('fontFamily') || '';
//var posAdjust = fontSize / 1.5; // adjust position to align to atom center
var text = this.getFormulaText(mol);
var slabel = text || '';
setterWidget.getChildWidgets()[0].setValue(slabel);
var style = setterWidget.getElement().style;
style.fontSize = fontSize + 'px';
style.fontFamily = fontName;
},
/** @ignore */
afterOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
var inputter = setterWidget.getChildWidgets()[0];
inputter.selectAll();
inputter.focus();
}
});
/** @ignore */
Kekule.Editor.EmbeddedSetter.Formula.isValidTarget = function(obj)
{
return (obj instanceof Kekule.StructureFragment) && obj.hasFormula() && !obj.hasCtab();
};
/**
* Embedded to setter to add or edit chem condition symbol in document.
* @class
* @augments Kekule.Editor.EmbeddedSetter.CreatableBlock
*/
Kekule.Editor.EmbeddedSetter.ChemConditionSymbol = Class.create(Kekule.Editor.EmbeddedSetter.CreatableBlock,
/** @lends Kekule.Editor.EmbeddedSetter.ChemConditionSymbol# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetter.ChemConditionSymbol',
/** @ignore */
isValidObj: function(obj)
{
return Kekule.Editor.EmbeddedSetter.ChemConditionSymbol.isValidTarget(obj);
},
/** @ignore */
doCreateNewObj: function(chemSpace, coord)
{
var editor = this.getEditor();
var result = new Kekule.Glyph.ChemConditionSymbol();
editor.setObjectScreenCoord(result, coord);
return result;
},
/**
* Returns condition text of symbol.
* @param {Kekule.ChemConditionSymbol} symbol
* @returns {String}
* @private
*/
getConditionText: function(symbol)
{
return symbol.getText();
},
/** @private */
doCreateSetterWidget: function(doc, parentElem)
{
var configs = this.getEditorConfigs().getChemConditionSetterConfigs();
var result = new Kekule.Widget.Container(this.getEditor());
result.addClassName(CCNS.CHEMEDITOR_CHEM_CONDITION_SETTER);
result.setDisplayed(false);
result.appendToElem(parentElem)
var conditionInputter = new Kekule.ChemWidget.ChemConditionComboBox(result); // new Kekule.Widget.ComboBox(result);
// conditionInputter.addClassName(CCNS.CHEMEDITOR_CHEM_CONDITION_SETTER);
// conditionInputter.setDisplayed(false);
// conditionInputter.appendToElem(parentElem);
conditionInputter.setItems(configs.getListedChemConditions());
// event handler
var self = this;
conditionInputter.addEventListener('keyup', function(e)
{
var ev = e.htmlEvent;
var keyCode = ev.getKeyCode();
if ((keyCode === Kekule.X.Event.KeyCode.ENTER))
{
self.applySetter(result);
result.dismiss(); // avoid call apply setter twice
}
else if (keyCode === Kekule.X.Event.KeyCode.ESC) // ESC, cancel editor
{
conditionInputter.dismiss();
self.cancelSetter();
}
}
);
conditionInputter.addEventListener('valueSelect', function(e)
{
self.applySetter(result);
result.dismiss(); // avoid call apply setter twice
});
return result;
},
/** @ignore */
getSetterWidgetModifiedValues: function(widget)
{
var condText = widget.getChildWidgets()[0].getConditionText();
// var isCustomCondition = widget.getChildWidgets()[0].getIsCustom();
if (!condText) // empty string, actually need to remove this symbol
return null;
/*
if (isCustomCondition)
return {'text': condValue};
else
return {'condition': condValue};
*/
return {'text': condText};
},
/** @ignore */
createTargetObjModifyOper: function(obj, modifiedValues)
{
return new Kekule.ChemObjOperation.Modify(obj, modifiedValues, this.getEditor());
},
/** @ignore */
prepareOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
var fontSize = targetObj.getCascadedRenderOption('fontSize') || this.getEditor().getEditorConfigs().getInteractionConfigs().getAtomSetterFontSize();
fontSize *= (this.getEditor().getZoom() || 1);
var fontName = targetObj.getCascadedRenderOption('fontFamily') || '';
//var posAdjust = fontSize / 1.5; // adjust position to align to atom center
var text = this.getConditionText(targetObj);
setterWidget.getChildWidgets()[0].setConditionText(text || '');
// var sPlaceholder = text || Kekule.$L('ChemWidgetTexts.CAPTION_TEXTBLOCK_INIT');
// setterWidget.setPlaceholder(sPlaceholder);
var style = setterWidget.getElement().style;
style.position = 'absolute';
style.fontSize = fontSize + 'px';
style.fontFamily = fontName;
style.opacity = 1;
},
/** @ignore */
afterOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
var inputter = setterWidget.getChildWidgets()[0];
inputter.selectAll();
inputter.focus();
}
});
/** @ignore */
Kekule.Editor.EmbeddedSetter.ChemConditionSymbol.isValidTarget = function(obj)
{
return obj instanceof Kekule.Glyph.ChemConditionSymbol;
};
/**
* Embedded to setter to add or edit text block in document.
* @class
* @augments Kekule.Editor.EmbeddedSetter.CreatableBlock
*/
Kekule.Editor.EmbeddedSetter.TextBlock = Class.create(Kekule.Editor.EmbeddedSetter.CreatableBlock,
/** @lends Kekule.Editor.EmbeddedSetter.TextBlock# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetter.TextBlock',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize', [editor]);
},
/** @ignore */
isValidObj: function(obj)
{
return Kekule.Editor.EmbeddedSetter.TextBlock.isValidTarget(obj);
// return obj instanceof Kekule.TextBlock;
},
/** @ignore */
doCreateNewObj: function(chemSpace, coord)
{
var editor = this.getEditor();
var result = new Kekule.TextBlock();
editor.setObjectScreenCoord(result, coord, Kekule.Render.CoordPos.CORNER_TL);
return result;
},
/**
* Returns text that shows in text block.
* @param {Kekule.TextBlock} block
* @returns {String}
* @private
*/
getBlockText: function(block)
{
return block.getText();
},
/** @private */
doCreateSetterWidget: function(doc, parentElem)
{
var result = new Kekule.Widget.TextArea(this.getEditor());
result.setAutoSizeX(true);
result.setAutoSizeY(true);
result.setDisplayed(false);
result.addClassName(CCNS.CHEMEDITOR_TEXT_SETTER);
result.appendToElem(parentElem);
// event handler
var self = this;
result.addEventListener('keyup', function(e)
{
var ev = e.htmlEvent;
var keyCode = ev.getKeyCode();
if ((keyCode === Kekule.X.Event.KeyCode.ENTER) && (ev.getCtrlKey())) // ctrl+enter
{
self.applySetter(result);
result.dismiss(); // avoid call apply setter twice
}
else if (keyCode === Kekule.X.Event.KeyCode.ESC) // ESC, cancel editor
{
result.dismiss();
self.cancelSetter();
}
}
);
return result;
},
/** @ignore */
getSetterWidgetModifiedValues: function(widget)
{
var value = widget.getValue();
return value? {'text': widget.getValue()}: null;
},
/** @ignore */
createTargetObjModifyOper: function(obj, modifiedValues)
{
return new Kekule.ChemObjOperation.Modify(obj, modifiedValues, this.getEditor());
},
/** @ignore */
prepareOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
// calculate the top-left position of block
var setterCoord = this.getEditor().getObjectScreenCoord(targetObj, Kekule.Render.CoordPos.CORNER_TL);
var fontSize = targetObj.getCascadedRenderOption('fontSize') || this.getEditor().getEditorConfigs().getInteractionConfigs().getAtomSetterFontSize();
fontSize *= (this.getEditor().getZoom() || 1);
var fontName = targetObj.getCascadedRenderOption('fontFamily') || '';
//var posAdjust = fontSize / 1.5; // adjust position to align to atom center
var text = this.getBlockText(targetObj);
setterWidget.setValue(text || '');
var sPlaceholder = text || Kekule.$L('ChemWidgetTexts.CAPTION_TEXTBLOCK_INIT');
setterWidget.setPlaceholder(sPlaceholder);
var style = setterWidget.getElement().style;
style.position = 'absolute';
style.fontSize = fontSize + 'px';
style.left = setterCoord.x + 'px';
style.top = setterCoord.y + 'px';
style.fontFamily = fontName;
style.opacity = 1;
},
/** @ignore */
afterOpeningSetterUi: function(setterWidget, targetObj, baseCoord)
{
setterWidget.selectAll();
setterWidget.focus();
}
});
/** @ignore */
Kekule.Editor.EmbeddedSetter.TextBlock.isValidTarget = function(obj)
{
return obj instanceof Kekule.TextBlock;
};
/**
* Embedded to setter to add or edit image block in document.
* @class
* @augments Kekule.Editor.EmbeddedSetter.Base
*/
Kekule.Editor.EmbeddedSetter.ImageBlock = Class.create(Kekule.Editor.EmbeddedSetter.Base,
/** @lends Kekule.Editor.EmbeddedSetter.ImageBlock# */
{
/** @private */
CLASS_NAME: 'Kekule.Editor.EmbeddedSetter.ImageBlock',
/** @construct */
initialize: function(editor)
{
this.tryApplySuper('initialize', [editor]);
this._operAddBlock = null; // private
this._imgProbeElem = null;
this._actionOpenFile = this.createOpenAction();
},
doFinalize: function()
{
if (this._actionOpenFile)
this.actionOpenFile.finalize();
},
/** @private */
initProperties: function()
{
this.defineProp('currBlock', {'dataType': DataType.OBJECT, 'serializable': false}); // private
},
/** @ignore */
getAllowCreateNewObj: function()
{
return true;
},
/**
* Check if obj is a valid text block and can be edited.
* @param {Kekule.ChemObject} obj
* @returns {Bool}
* @private
*/
isValidBlock: function(obj)
{
return Kekule.Editor.EmbeddedSetter.ImageBlock.isValidTarget(obj);
// return obj instanceof Kekule.ImageBlock;
},
/** @private */
createNewBlock: function(chemSpace, coord, size, src)
{
var editor = this.getEditor();
if (!editor.canCreateNewChild())
return null;
editor.beginUpdateObject();
try
{
var block = new Kekule.ImageBlock();
if (src)
block.setSrc(src);
if (size)
block.setSize2D(size);
//chemSpace.appendChild(block);
editor.setObjectScreenCoord(block, coord, Kekule.Render.CoordPos.CORNER_TL);
var addOperation = new Kekule.ChemObjOperation.Add(block, chemSpace, null, editor);
this._operAddBlock = addOperation;
}
finally
{
editor.endUpdateObject();
}
return block;
},
/** @private */
_getImgFilters: function()
{
// add png, jpg, gif and svg
var result = [
{'title': Kekule.$L('WidgetTexts.TITLE_IMG_FORMAT_PNG'), 'filter': '.png'},
{'title': Kekule.$L('WidgetTexts.TITLE_IMG_FORMAT_JPG'), 'filter': '.jpg,.jpeg'},
{'title': Kekule.$L('WidgetTexts.TITLE_IMG_FORMAT_GIF'), 'filter': '.gif'},
{'title': Kekule.$L('WidgetTexts.TITLE_IMG_FORMAT_SVG'), 'filter': '.svg'},
Kekule.NativeServices.FILTER_ALL_SUPPORT,
Kekule.NativeServices.FILTER_ANY
];
return result;
},
/** @private */
createOpenAction: function()
{
var result = new Kekule.ActionFileOpen();
result.setFilters(this._getImgFilters());
result.on('open', this.reactImageFileOpen, this);
return result;
},
/** @private */
reactImageFileOpen: function(e)
{
var file = e.file;
if (file)
{
var self = this;
var reader = new FileReader();
reader.addEventListener('load', function(){
var imgElem = self._getImageProbeElem();
var doc = imgElem.ownerDocument;
// hide imgElem and append it to body to calculate size
//imgElem.style.display = 'none';
//doc.body.appendChild(imgElem);
try
{
// clear img prev width/height
delete imgElem.width;
delete imgElem.height;
}
catch(e)
{
}
imgElem.src = reader.result;
var editor = self.getEditor();
//(function(){ console.log(imgElem.width, imgElem.height); }).defer();
(function(){
var size = {'x': imgElem.width, 'y': imgElem.height};
if (size.x <= 0 || size.y <= 0) // empty image
{
Kekule.error(Kekule.$L('ErrorMsg.INVALID_OR_EMPTY_IMAGE'));
return;
}
//console.log('imgSize', size);
//imgElem.parentNode.removeChild(imgElem);
//size = editor.translateCoord(size, Kekule.Editor.CoordSys.SCREEN, Kekule.Editor.CoordSys.CHEM);
//console.log('transSize', size);
var coord1 = self._currCoord;
var contextCoord1 = editor.objCoordToContext(coord1);
var contextCoord2 = Kekule.CoordUtils.add(contextCoord1, size);
var coord2 = editor.contextCoordToObj(contextCoord2);
var size = Kekule.CoordUtils.substract(coord2, coord1);
size = Kekule.CoordUtils.absValue(size);
var currBlock = self.getCurrBlock();
var oper;
var chemSpace = editor.getChemObj();
if (currBlock) // modify existed
{
oper = new Kekule.ChemObjOperation.Modify(currBlock, {'src': reader.result, 'size2D': size});
}
else // create new
{
var block = self.createNewBlock(self.getEditor().getChemObj(), self._currCoord, size, reader.result);
self.setCurrBlock(block);
oper = new Kekule.ChemObjOperation.Add(block, chemSpace, null, editor);
}
if (oper)
{
editor.beginManipulateAndUpdateObject();
try
{
oper.execute();
if (editor && editor.getEnableOperHistory())
editor.pushOperation(oper);
self.notifyModificationResult(true, [self.getCurrBlock()]);
}
finally
{
editor.endManipulateAndUpdateObject();
}
}
}).defer(); // execute later, get accurate image size
});
reader.addEventListener('error', function(e){
self.notifyModificationResult(false);
});
reader.readAsDataURL(file);
}
},
/** @private */
_getImageProbeElem: function()
{
if (!this._imgProbeElem)
{
var doc = this.getEditor().getElement().ownerDocument;
this._imgProbeElem = doc.createElement('img');
}
return this._imgProbeElem;
},
/** @private */
doExecute: function(targetObjs, baseCoord, callback)
{
var block = targetObjs && targetObjs[0];
this.setCurrBlock(block);
this._currCoord = baseCoord;
this._actionOpenFile.execute(this.getEditor());
}
});
/** @ignore */
Kekule.Editor.EmbeddedSetter.ImageBlock.isValidTarget = function(obj)
{
return obj instanceof Kekule.ImageBlock;
};
var EmbeddedSetter = Kekule.Editor.EmbeddedSetter;
ESM.register(EmbeddedSetter.MolAtom, [Kekule.ChemStructureNode]);
ESM.register(EmbeddedSetter.Formula, [Kekule.StructureFragment]);
ESM.register(EmbeddedSetter.TextBlock, [Kekule.TextBlock]);
ESM.register(EmbeddedSetter.ImageBlock, [Kekule.ImageBlock]);
ESM.register(EmbeddedSetter.ChemConditionSymbol, [Kekule.Glyph.ChemConditionSymbol]);
})();