Source: widgets/chem/editor/kekule.chemEditor.objModifiers.js

/**
 * @fileoverview
 * Widgets to modify an chem object in a user friendly 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";

var AU = Kekule.ArrayUtils;
var CE = Kekule.Editor;
var CNS = Kekule.Widget.HtmlClassNames;
var CCNS = Kekule.ChemWidget.HtmlClassNames;
var BNS = Kekule.ChemWidget.ComponentWidgetNames;

/**
 * Base namespace of all object modifiers.
 * @namespace
 */
Kekule.Editor.ObjModifier = {};

/**
 * Enumeration of obj modifier type.
 * Each obj modifier class should have a class method getCategories returns an array of these types.
 * @enum
 */
Kekule.Editor.ObjModifier.Category = {
	/** Gneral popurse modifiers. **/
	GENERAL: 'general',
	/** Modifiers related to chem structure (e.g., bond, atom). **/
	CHEM_STRUCTURE: 'chemStruct',
	/** Modifiers related to chem document glyphs. **/
	GLYPH: 'glyph',
	/** Modifiers related to render styles. **/
	STYLE: 'style',
	/** Misc modifiers. **/
	MISC: 'misc'
};

/**
 * A special object to create widget to modify an chem object in a user friendly way in chem editor.
 * @class
 * @augments ObjectEx
 *
 * @param {Kekule.Editor.BaseEditor} editor
 *
 * @property {Kekule.Editor.BaseEditor} editor
 * @property {Array} targetObjs Objects that selected in editor and can be modified by this modifier.
 * @property {Kekule.Widget.BaseWidget} widget The concrete modifier widget.
 */
Kekule.Editor.ObjModifier.Base = Class.create(ObjectEx,
/** @lends Kekule.Editor.ObjModifier.Base# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Editor.ObjModifier.Base',
	/** @construct */
	initialize: function($super, editor)
	{
		$super();
		this.setEditor(editor);
		this.modifierWidgetValueChangedBind = this.modifierWidgetValueChanged.bind(this);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('editor', {
			'dataType': 'Kekule.Editor.BaseEditor', 'serializable': false
		});
		this.defineProp('targetObjs', {
			'dataType': DataType.ARRAY, 'serializable': false,
			'getter': function()
			{
				return this.getEditor().getSelection();
			}
		});
		this.defineProp('widget', {
			'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false,
			'setter': null,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('widget');
				if (!result)
				{
					result = this.createWidget();
					this.setPropStoreFieldValue('widget', result);
				}
				return result;
			}
		});
	},

	/** @private */
	getEditorConfigs: function()
	{
		return this.getEditor().getEditorConfigs();
	},

	/**
	 * Returns a name that represent this modifier.
	 * Descendants may override this method.
	 */
	getModifierName: function()
	{
		return this.getClassLocalName().toLowerCase();
	},
	/**
	 * Create the modifier widget.
	 * @returns {Kekule.Widget.BaseWidget}
	 * @private
	 */
	createWidget: function()
	{
		var result = this.doCreateWidget();
		if (result)
			this.installValueChangeEventHandler(result);  // listen to value change event, changing properties of objects in editor
		return result;
	},
	/**
	 * Do the actual work of createWidget method.
	 * Descendants should override this method.
	 * @returns {Kekule.Widget.BaseWidget}
	 * @private
	 */
	doCreateWidget: function()
	{
		return null;  // do nothing here
	},

	/**
	 * Listen to value change event, changing properties of objects in editor.
	 * The default approach is listen to 'valueChange' event, which is suitable for form controls.
	 * descendants may override this method to listen to other events.
	 * @private
	 */
	installValueChangeEventHandler: function(widget)
	{
		widget.on('valueChange', this.modifierWidgetValueChangedBind);
	},
	/**
	 * Called whether modifier widget is changed and the changes should be applied to target objects in editor.
	 */
	modifierWidgetValueChanged: function()
	{
		this.applyToTargets();
	},

	/**
	 * Load corresponding value from targets, update the outlook of modifier widget.
	 */
	loadFromTargets:function()
	{
		var editor = this.getEditor();
		if (editor)
		{
			var targets = this.getTargetObjs();
			if (targets)
				this.doLoadFromTargets(editor, targets);
		}
		return this;
	},
	/**
	 * Do actual work of loadFromTargets method.
	 * Descendants must override this method.
	 * @param {Kekule.Editor.BaseEditor} editor
	 * @param {Array} targets Target objects.
	 * @private
	 */
	doLoadFromTargets: function(editor, targets)
	{
		// do nothing here
	},
	/**
	 * Change properties of target objects based on this widget.
	 */
	applyToTargets: function()
	{
		//console.log('applyToTargets');
		var editor = this.getEditor();
		if (editor)
		{
			var targets = this.getTargetObjs();
			if (targets)
				this.doApplyToTargets(editor, targets);
		}
		return this;
	},
	/**
	 * Do actual work of applyToTargets method.
	 * Descendants may override this method.
	 * @param {Kekule.Editor.BaseEditor} editor
	 * @param {Array} targets Target objects.
	 * @private
	 */
	doApplyToTargets: function(editor, targets)
	{
		var oper = this.createModificationOper();
		if (oper)
			editor.execOperation(oper);
	},
	/**
	 * Create operation to change all target objects based on this widget.
	 * @param {Array} targets Target objects.
	 * @returns {Kekule.Operation}
	 * @private
	 */
	createModificationOper: function(targets)
	{
		var operations = [];
		if (target && targets.length)
		{
			for (var i = 0, l = targets.length; i < l; ++i)
			{
				var oper = this.createModificationOperOnTarget(targets[i]);
				if (oper)
					operations.push(oper);
			}
		}
		var operCount = operations.length;
		if (operCount <= 0)
			return null;
		else if (operCount === 1)
			return operations[0];
		else  // create macro operation
			return new Kekule.MacroOperation(operations);
	},
	/**
	 * Create a instance of {@link Kekule.ChemObjOperation.Modify} to modify targetObj.
	 * Descendants may override this method.
	 * @param {Kekule.ChemObject} targetObj
	 * @param {Hash} newPropValues
	 * @returns {Kekule.Operation}
	 * @private
	 */
	createModificationOperOnTarget: function(targetObj)
	{
		var modifiedPropValues = this.getModifiedPropValues();
		if (modifiedPropValues)
			return this.createModificationOperation(targetObj, modifiedPropValues);
		else
			return null;
	},
	/**
	 * Returns changed property values for target objects.
	 * Descendants may override this method.
	 * @returns {Hash}
	 * @private
	 */
	getModifiedPropValues: function()
	{
		return null;
	},
	/**
	 * Create a instance of {@link Kekule.ChemObjOperation.Modify} to modify targetObj.
	 * Descendants may override this method.
	 * @param {Kekule.ChemObject} targetObj
	 * @param {Hash} newPropValues
	 * @returns {Kekule.Operation}
	 * @private
	 */
	createModificationOperation: function(targetObj, newPropValues)
	{
		return new Kekule.ChemObjOperation.Modify(targetObj, newPropValues, this.getEditor());
	}
});

/**
 * Manager Object of All obj modifiers.
 * @type {Object}
 */
Kekule.Editor.ObjModifierManager = {
	/** @private */
	_modifierMap: new Kekule.MapEx(true),
	/**
	 * Register new modifier class(es).
	 * @param {Array} objClasses Classes of object these modifiers can be applied.
	 * @param {Variant} modifierClasses Registered modifier class or classes.
	 */
	register: function(objClasses, modifierClasses)
	{
		if (!modifierClasses || !objClasses)
			return;
		var mClasses = AU.toArray(modifierClasses);
		var oClasses = AU.toArray(objClasses);
		for (var i = 0, l = oClasses.length; i < l; ++i)
		{
			var objClass = oClasses[i];
			var mapItem = OMM._modifierMap.get(objClass);
			if (!mapItem)
			{
				mapItem = mClasses;
				OMM._modifierMap.set(objClass, mapItem);
			}
			else
				AU.pushUnique(mapItem, mClasses);
		}
	},
	/**
	 * Unregister modifier class(es).
	 * @param {Variant} modifierClasses Unregistered modifier class or classes.
	 */
	unregister: function(modifierClasses)
	{
		if (!modifierClasses)
			return;
		var mClasses = AU.toArray(modifierClasses);
		{
			var objClasses = OMM._modifierMap.getKeys();
			for (var i = 0, l = objClasses.length; i < l; ++i)
			{
				var mapItem = OMM._modifierMap.get(objClasses[i]);
				if (mapItem && mapItem.length)
					OMM._modifierMap.set(objClasses[i], AU.exclude(mapItem, mClasses));
			}
		}
	},
	/**
	 * Returns the categores of a modifier class (and its ancestors).
	 * @param {Class} modifierClass
	 */
	getModifierCategories: function(modifierClass)
	{
		var result = (modifierClass.getCategories && modifierClass.getCategories()) || [];
		var superClass = ClassEx.getSuperClass(modifierClass);
		if (superClass)
			AU.pushUnique(result, OMM.getModifierCategories(superClass));
		return result;
	},
	/**
	 * Returns available modifier classes for objClass.
	 * @param {Class} objClass
	 * @param {Array} allowedCategories Optional, if set, only modifiers of thoses categories will be returned.
	 * @returns {Array}
	 */
	getModifierClasses: function(objClass, allowedCategories)
	{
		var result = OMM._modifierMap.get(objClass) || [];
		var superClass = ClassEx.getSuperClass(objClass);
		if (superClass)
			AU.pushUnique(result, OMM.getModifierClasses(superClass, allowedCategories));
		if (allowedCategories && allowedCategories.length)  // check categories
		{
			var modifierClasses = result;
			result = [];
			for (var i = 0, l = modifierClasses.length; i < l; ++i)
			{
				var modifierCategories = OMM.getModifierCategories(modifierClasses[i]);
				if (AU.intersect(modifierCategories, allowedCategories).length)
					result.push(modifierClasses[i]);
			}
		}
		return result;
	}
};

var OMM = Kekule.Editor.ObjModifierManager;

})();