Source: widgets/operation/kekule.actions.js

/**
 * @fileoverview
 * The implementation of Action class.
 * Action is a type of class that can be associated with widget to do a specified job (like the corresponding
 * part in Delphi).
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /utils/kekule.utils.js
 * requires /xbrowsers/kekule.x.js
 * requires /html/kekule.nativeServices.js
 * requires /localization/
 */


(function(){

/**
 * Base class for actions.
 * @class
 * @augments ObjectEx
 *
 * @property {Bool} visible Change the property to set widgets' visibility style linked to this action.
 * @property {Bool} displayed Change the property to set widgets' display style linked to this action.
 * @property {Bool} enabled Whether widget linked to this action can reflect to user input. Default is true.
 * @property {Bool} checked Change the checked property of widgets linked to this action.
 * @property {String} checkGroup If this value is set, checked will be automatically set to true when action is executed.
 *   What's more, when a action's checked is set to true, all other actions with the same checkGroup in action list will
 *   be automatically set to false.
 * @property {String} hint Hint of action. If this value is set, all widgets' hint properties will be updated.
 * @property {Text} text Caption/text of action. If this value is set, all widgets' hint properties will be updated.
 * @property {String} htmlClassName This value will be added to widget when action is linked and will be removed when action is unlinked.
 * @property {owner} Owner of action, usually a {@link Kekule.ActionList}.
 * @property {Kekule.Widget.BaseWidget} invoker Who invokes this action.
 */
/**
 * Invoked when an action is executed. Has one field: {htmlEvent: html event to raise the action}.
 * @name Kekule.Action#execute
 * @event
 */
Kekule.Action = Class.create(ObjectEx,
/** @lends Kekule.Action# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Action',
	/** @private */
	HTML_CLASSNAME: null,
	/** @constructs */
	initialize: function($super)
	{
		$super();
		this.setPropStoreFieldValue('linkedWidgets', []);

		this.setPropStoreFieldValue('enabled', true);
		this.setPropStoreFieldValue('visible', true);
		this.setPropStoreFieldValue('displayed', true);

		this.setPropStoreFieldValue('htmlClassName', this.getInitialHtmlClassName());

		this.setBubbleEvent(true);

		this.reactWidgetExecuteBind = this.reactWidgetExecute.bind(this);
	},
	/** @private */
	finalize: function($super)
	{
		var owner = this.getOwner();
		if (owner && owner.actionRemoved)
		{
			owner.actionRemoved(this);
		}
		this.unlinkAllWidgets();
		this.setPropStoreFieldValue('linkedWidgets', []);
		$super();
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('enabled', {'dataType': DataType.BOOL,
			'setter': function(value)
			{
				if (value !== this.getEnabled())
				{
					this.setPropStoreFieldValue('enabled', value);
					this.updateAllWidgetsProp('enabled', value);
				}
			}
		});
		this.defineProp('visible', {'dataType': DataType.BOOL,
			'setter': function(value)
			{
				if (value !== this.getVisible())
				{
					this.setPropStoreFieldValue('visible', value);
					this.updateAllWidgetsProp('visible', value);
				}
			}
		});
		this.defineProp('displayed', {'dataType': DataType.BOOL,
			'setter': function(value)
			{
				if (value !== this.getDisplayed())
				{
					this.setPropStoreFieldValue('displayed', value);
					this.updateAllWidgetsProp('displayed', value);
				}
			}
		});
		this.defineProp('checked', {'dataType': DataType.BOOL,
			'setter': function(value)
			{
				if (value !== this.getChecked())
				{
					this.setPropStoreFieldValue('checked', value);
					this.updateAllWidgetsProp('checked', value);
					this.checkedChanged();
				}
			}
		});
		this.defineProp('checkGroup', {'dataType': DataType.STRING});
		this.defineProp('hint', {'dataType': DataType.STRING,
			'setter': function(value)
			{
				if (value && (value !== this.getHint()))
				{
					this.setPropStoreFieldValue('hint', value);
					this.updateAllWidgetsProp('hint', value);
				}
			}
		});
		this.defineProp('text', {'dataType': DataType.STRING,
			'setter': function(value)
			{
				if (value && (value !== this.getText()))
				{
					this.setPropStoreFieldValue('text', value);
					this.updateAllWidgetsProp('text', value);
				}
			}
		});

		this.defineProp('htmlClassName', {'dataType': DataType.STRING,
			'setter': function(value)
			{
				var old = this.getHtmlClassName();
				this.setPropStoreFieldValue('htmlClassName', value);
				this.updateAllWidgetClassName(value, old);
			}
		});

		this.defineProp('owner', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null});

		// widgets that associated with this action. Private property.
		this.defineProp('linkedWidgets', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null});
		this.defineProp('invoker', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false, 'setter': null});
	},

	/** @private */
	getHigherLevelObj: function()
	{
		return this.getOwner();
	},

	/** @ignore */
	invokeEvent: function($super, eventName, event)
	{
		if (!event)
			event = {};
		// save invoker into event param
		if (!event.invoker)
			event.invoker = this.getInvoker();
		$super(eventName, event);
	},

	/** @private */
	getInitialHtmlClassName: function()
	{
		return this.HTML_CLASSNAME || null;
	},
	/**
	 * Returns belonged action list.
	 * @returns {Kekule.ActionList}
	 */
	getActionList: function()
	{
		var result = this.getOwner();
		return (result instanceof Kekule.ActionList)? result: null;
	},

	/** @private */
	checkedChanged: function()
	{
		//var group = this.getCheckGroup();
		var list = this.getActionList();
		if (list)
			list.actionCheckedChanged(this);
	},

	/**
	 * Link a widget to this action.
	 * This method should not be called directly. Instead, user should use the action property of widget.
	 * @param {Kekule.Widget.BaseWidget} widget
	 * @ignore
	 */
	linkWidget: function(widget)
	{
		var widgets = this.getLinkedWidgets();
		if (widgets.indexOf(widget) < 0)
		{
			// update widget properties
			var text = this.getText();
			if (text)
				this.updateWidgetProp(widget, 'text', text);
			var hint = this.getHint();
			if (hint)
				this.updateWidgetProp(widget, 'hint', hint);
			this.updateWidgetProp(widget, 'enabled', this.getEnabled());
			this.updateWidgetProp(widget, 'displayed', this.getDisplayed());
			this.updateWidgetProp(widget, 'visible', this.getVisible());
			this.updateWidgetProp(widget, 'checked', this.getChecked());
			this.updateWidgetClassName(widget, this.getHtmlClassName(), null);

			// install event handler
			widget.addEventListener('execute', this.reactWidgetExecuteBind);
			// add to array
			widgets.push(widget);

			return this;
		}
	},
	/**
	 * Unlink a widget from this action,
	 * This method should not be called directly. Instead, user should use widget.setAction(null).
	 * @param {Kekule.Widget.BaseWidget} widget
	 * @ignore
	 */
	unlinkWidget: function(widget)
	{
		var widgets = this.getLinkedWidgets();
		var index = widgets.indexOf(widget);
		if (index >= 0)
		{
			// remove class names
			this.updateWidgetClassName(widget, null, this.getHtmlClassName());
			// uninstall event handler
			widget.removeEventListener('execute', this.reactWidgetExecuteBind);
			// remove from array
			widgets.splice(index, 1);
		}
	},
	/** @private */
	unlinkAllWidgets: function()
	{
		var widgets = this.getLinkedWidgets();
		for (var i = widgets.length - 1; i >= 0; --i)
		{
			this.unlinkWidget(widgets[i]);
		}
	},

	/** @private */
	reactWidgetExecute: function(e)
	{
		return this.execute(e.target, e.htmlEvent);
	},

	/**
	 * Execute the action.
	 * @param {Object} target Object that invokes this action.
	 * @param {Object} htmlEvent HTML event that causes the executing process, can be null.
	 */
	execute: function(target, htmlEvent)
	{
		var oldChecked = this.getChecked();
		if (!this.getCheckGroup() || !oldChecked)
		{
			this.doExecute(target, htmlEvent);
			if (this.getCheckGroup())
			{
				this.setChecked(true);
			}
		}
		this.setPropStoreFieldValue('invoker', target);
		this.invokeEvent('execute', {'htmlEvent': htmlEvent});
		return this;
	},
	/**
	 * Do the actual action job. Descendants should override this method.
	 * @private
	 */
	doExecute: function(target, htmlEvent)
	{
		// do nothing here
	},

	/**
	 * Update the state (enabled, visible and so on) of action.
	 */
	update: function()
	{
		this.doUpdate();
		return this;
	},
	/**
	 * Do the actual state updating job. Descendants should override this method.
	 * @private
	 */
	doUpdate: function()
	{
		// do nothing here
	},

	/** @private */
	updateAllWidgetsProp: function(propName, propValue)
	{
		var widgets = this.getLinkedWidgets();
		for (var i = 0, l = widgets.length; i < l; ++i)
		{
			var w = widgets[i];
			this.updateWidgetProp(w, propName, propValue);
		}
	},
	/** @private */
	updateWidgetProp: function(widget, propName, propValue)
	{
		if (widget.hasProperty(propName))
		{
			widget.setPropValue(propName, propValue);
		}
	},
	/** @private */
	updateWidgetClassName: function(widget, addClasses, removeClasses)
	{
		if (removeClasses)
			widget.removeClassName(removeClasses, true);
		if (addClasses)
			widget.addClassName(addClasses, true);
	},
	/** @private */
	updateAllWidgetClassName: function(addClasses, removeClasses)
	{
		var widgets = this.getLinkedWidgets();
		for (var i = 0, l = widgets.length; i < l; ++i)
		{
			var w = widgets[i];
			this.updateWidgetClassName(w, addClasses, removeClasses);
		}
	}
});

/**
 * Container of a series of related actions.
 * @class
 * @augments ObjectEx
 *
 * @property {Array} actions Actions in this list.
 * @property {Bool} ownActions If this property is true, action will be finalized if removed from this list.
 *   Default is true.
 * @property {Bool} autoUpdate If set to true, all actions in list will update their state after one action is executed.
 */
/**
 * Invoked when a child action is executed. Event param of it has field: {action}
 * @name Kekule.ActionList#execute
 * @event
 */
Kekule.ActionList = Class.create(ObjectEx,
/** @lends Kekule.ActionList# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ActionList',
	/** @constructs */
	initialize: function($super)
	{
		$super();
		this.setPropStoreFieldValue('actions', []);
		this.setPropStoreFieldValue('ownActions', true);
		this.setPropStoreFieldValue('autoUpdate', true);
		this.reactActionExecutedBind = this.reactActionExecuted.bind(this);
		this.addEventListener('execute', this.reactActionExecutedBind);
	},
	/** @private */
	finalize: function($super)
	{
		this.removeEventListener('execute', this.reactActionExecutedBind);
		this.clear();
		this.setPropStoreFieldValue('actions', null);
		$super();
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('actions', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null});
		this.defineProp('ownActions', {'dataType': DataType.BOOL});
		this.defineProp('autoUpdate', {'dataType': DataType.BOOL,
			'setter': function(value)
			{
				this.setPropStoreFieldValue('autoUpdate', value);
				if (value)
					this.updateAll();
			}
		});
	},

	/**
	 * Returns if one action of group is already checked.
	 * @param {String} group
	 * @returns {Bool}
	 */
	hasActionChecked: function(group)
	{
		return !!this.getCheckedAction(group);
	},

	/**
	 * Returns checked action of group.
	 * @param {String} group
	 * @returns {Kekule.Action}
	 */
	getCheckedAction: function(group)
	{
		var actions = this.getActions();
		for (var i = 0, l = actions.length; i < l; ++i)
		{
			var a = actions[i];
			if ((a.getCheckGroup() === group) && (a.getChecked()))
				return a;
		}
		return null;
	},

	/**
	 * Called after an action is added to list.
	 * @private
	 */
	actionAdded: function(action)
	{
		if (this.getOwnActions())
		{
			var oldOwner = action.getOwner();
			if (oldOwner && oldOwner.actionRemoved)
				oldOwner.actionRemoved(action);
			action.setPropStoreFieldValue('owner', this);
		}

		Kekule.ArrayUtils.pushUnique(this.getActions(), action);
		action.update();
		// install event listener
		//action.addEventListener('execute', this.reactActionExecutedBinded);
	},
	/**
	 * Called after an action is removed from list.
	 * @private
	 */
	actionRemoved: function(action)
	{
		Kekule.ArrayUtils.remove(this.getActions(), action);
		action.setPropStoreFieldValue('owner', null);
		// remove event listener
		//action.removeEventListener('execute', this.reactActionExecutedBinded);
	},

	/**
	 * Notify a child action item's checked property checked.
	 * @private
	 */
	actionCheckedChanged: function(action)
	{
		if (action && action.getChecked())
		{
			var group = action.getCheckGroup();
			if (group)
			{
				var actions = this.getActions();
				for (var i = 0, l = actions.length; i < l; ++i)
				{
					var a = actions[i];
					if ((a !== action) && (a.getCheckGroup() === group) && (a.getChecked()))
						a.setChecked(false);
				}
			}
		}
	},

	/**
	 * Event listener to react to execute event of child actions.
	 * @private
	 */
	reactActionExecuted: function(e)
	{
		// this method will receives execute event from child actions
		if (e.target instanceof Kekule.Action)
		{
			this.invokeEvent('execute', {'action': e.target});
			if (this.getAutoUpdate())
				this.updateAll();
		}
	},

	/**
	 * Returns count of actions inside.
	 * Same as {@link Kekule.ActionList.getActionLength}.
	 * @returns {Int}
	 */
	getActionCount: function()
	{
		return this.getActions().length;
	},
	/**
	 * Returns count of actions inside.
	 * Same as {@link Kekule.ActionList.getActionCount}.
	 * @returns {Int}
	 */
	getActionLength: function()
	{
		return this.getActions().length;
	},
	/**
	 * Returns action object at index.
	 * @param {Int} index
	 * @returns {Kekule.Action}
	 */
	getActionAt: function(index)
	{
		return this.getActions()[index];
	},
	/**
	 * Returns index of an action in list.
	 * @param {Kekule.Action} action
	 * @returns {Int}
	 */
	indexOfAction: function(action)
	{
		return this.getActions().indexOf(action);
	},
	/**
	 * Change the position of action in list.
	 * @param {Kekule.Action} action
	 * @param {Int} index
	 */
	setActionIndex: function(action, index)
	{
		var actions = this.getActions();
		if (actions)
		{
			var oldIndex = actions.indexOf(action);
			if (oldIndex >= 0 && oldIndex !== index)
			{
				// remove from old position
				actions.splice(oldIndex, 1);
				// insert to new
				actions.splice(index, 0, action);
			}
		}
		return this;
	},
	/**
	 * Check whether an action is in this list.
	 * @param {Kekule.Action} action
	 * @returns {Bool}
	 */
	hasAction: function(action)
	{
		return this.indexOfAction(action) >= 0;
	},

	/**
	 * Add a new action to list.
	 * @param {Kekule.Action} action
	 */
	add: function(action)
	{
		this.actionAdded(action);
		return this;
	},
	/**
	 * Remove an action from list.
	 * @param {Kekule.Action} action
	 */
	remove: function(action)
	{
		this.actionRemoved(action);
		if (this.getOwnActions())
			action.finalize();
		return this;
	},
	/**
	 * Remove action at index.
	 * @param {Int} index
	 */
	removeAt: function(index)
	{
		var actions = this.getActions();
		var action = actions[index];
		if (action)
		{
			Kekule.ArrayUtils.removeAt(actions, index);
			this.actionRemoved(action);
		}
		return this;
	},
	/**
	 * Clear all actions in list.
	 */
	clear: function()
	{
		var actions = this.getActions();
		for (var i = actions.length - 1; i >=0; --i)
		{
			this.actionRemoved(actions[i]);
		}
		this.setPropStoreFieldValue('actions', []);
		return this;
	},

	/**
	 * Update state of all actions in list.
	 */
	updateAll: function()
	{
		var actions = this.getActions();
		for (var i = 0, l = actions.length; i < l; ++i)
		{
			actions[i].update();
		}
		return this;
	}
});


// Some useful and common actions
/**
 * Action to open a file dialog and load file(s).
 * This action relies on JavaScript FileReader API.
 * @class
 * @augments Kekule.Action
 *
 * @property filters {Array} Filters of open file dialog.
 */
/**
 * Invoked when file(s) is loaded from dialog. Has one additional fields: {files, file}
 * @name Kekule.ActionFileOpen#open
 * @event
 */
Kekule.ActionFileOpen = Class.create(Kekule.Action,
/** @lends Kekule.ChemWidget.ActionFileOpen# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionFileOpen',
	/** @constructs */
	initialize: function($super)
	{
		$super();
		//this.reactFileOpenBind = this.reactFileOpen.bind(this);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('filters', {'dataType': DataType.ARRAY});
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		this.setEnabled(this.getEnabled() && Kekule.NativeServices.showFilePickerDialog/*Kekule.BrowserFeature.fileapi*/);
	},
	/** @private */
	doExecute: function(target)
	{
		var elem = target.getElement();
		var doc = elem.ownerDocument;
		/*
		var input = this.createInputElem(doc);
		input.click();  // open file dialog
		//this._inputElem = input;
		*/
		var self = this;
		var invoker = this.getInvoker();  // save invoker in closure
		//this.openFilePicker(doc, this.reactFileOpenBind);
		this.openFilePicker(doc, function(result, firstFile, files){
			if (result)
				self.fileOpened(files, invoker);
		});
	},
	/**
	 * Open a file open dialog, when it is closed, callback will be evoked.
	 * @param {HTMLDocument} doc
	 * @param {Function} callback Callback function, with params (result(true on OK), files, firstFile)
	 */
	openFilePicker: function(doc, callback)
	{
		/*
		if (Kekule.ActionFileOpen.openFilePicker)
			return Kekule.ActionFileOpen.openFilePicker(doc, callback);
		else
			return null;
		*/
		return Kekule.NativeServices.showFilePickerDialog(doc, callback, {
			'mode': 'open',
			'filters': this.getFilters()
		});
	},

	/* @private */
	/*
	reactInputChange: function(e)
	{
		var target = e.getTarget();
		console.log('file input change', target.files);
		this.fileOpened(target.files);
		// dismiss input element
		//this._inputElem = null;
		Kekule.X.Event.removeListener(target, 'change', this.reactInputChangeBind);
		target.ownerDocument.body.focus();
		if (target.parentNode)
		{
			target.parentNode.removeChild(target);
		}
	},
	*/
	/* @private */
	/*
	reactFileOpen: function(result, firstFile, files)
	{
		if (result)
			this.fileOpened(files);
	},
	*/
	/**
	 * Called when file is opened from input element.
	 * @param {Object} files
	 * @private
	 */
	fileOpened: function(files, invoker)
	{
		this.doFileOpened(files, invoker);
		this.invokeEvent('open', {'files': files, 'file': files[0]});
	},
	/**
	 * Do actual work of fileOpened. Descendants can override this method.
	 * @param {Object} files
	 * @private
	 */
	doFileOpened: function(files, invoker)
	{
		// do nothing here
	}
});

/**
 * Action to open a file dialog and load file data.
 * This action relies on JavaScript FileReader API.
 * @class
 * @augments Kekule.Action
 *
 * @property filters {Array} Filters of open file dialog.
 */
/**
 * Invoked when file(s) is loaded from dialog and data is loaded. Has one additional fields: {data, fileName, success}
 * @name Kekule.ActionLoadFileData#load
 * @event
 */
Kekule.ActionLoadFileData = Class.create(Kekule.Action,
/** @lends Kekule.ChemWidget.ActionLoadFileData# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionLoadFileData',
	/** @constructs */
	initialize: function($super)
	{
		$super();
		//this.reactFileLoadBind = this.reactFileLoad.bind(this);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('filters', {'dataType': DataType.ARRAY});
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		this.setEnabled(this.getEnabled() && Kekule.NativeServices.canLoadFileData());
	},
	/** @private */
	doExecute: function(target)
	{
		var elem = target.getElement();
		var doc = elem.ownerDocument;
		//this.loadFileData(doc, this.reactFileLoadBind);
		var self = this;
		var invoker = this.getInvoker();  // save invoker in closure
		this.loadFileData(doc, function(result, data, fileName){
			self.dataLoaded(data, fileName, !!result, invoker);
		});
	},
	/**
	 * Open a file open dialog, when it is closed, callback will be evoked.
	 * @param {HTMLDocument} doc
	 * @param {Function} callback Callback function, with params (result(true on OK), files, firstFile)
	 */
	loadFileData: function(doc, callback)
	{
		//console.log('load file data', this.getFilters());
		return Kekule.NativeServices.loadFileData(doc, callback, {
			'filters': this.getFilters()
		});
	},

	/** @private */
	reactFileLoad: function(result, data, fileName)
	{
		this.dataLoaded(data, fileName, !!result);
	},
	/**
	 * Called when file is opened and data is loaded.
	 * @private
	 */
	dataLoaded: function(data, fileName, loaded, invoker)
	{
		this.doDataLoaded(data, fileName, loaded, invoker);
		this.invokeEvent('load', {'fileName': fileName, 'data': data, 'success': loaded});
	},
	/**
	 * Do actual work of fileOpened. Descendants can override this method.
	 * @private
	 */
	doDataLoaded: function(data, fileName, loaded, invoker)
	{
		// do nothing here
	}
});


/**
 * Action to open a file save dialog and save file(s).
 * This action relies on Data URL.
 * @class
 * @augments Kekule.Action
 *
 * @param {String} data Data to save.
 * @param {String} fileName Prefered file name to save.
 *
 * @property {String} data Data to save.
 * @property {String} fileName Prefered file name to save.
 *
 * @property filters {Array} Filters of save file dialog.
 */
Kekule.ActionFileSave = Class.create(Kekule.Action,
/** @lends Kekule.ChemWidget.ActionFileSave# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionFileSave',
	/** @constructs */
	initialize: function($super, data, fileName)
	{
		$super();
		if (data)
			this.setData(data);
		if (fileName)
			this.setFileName(fileName);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('data', {'dataType': DataType.STRING, 'serializable': false});
		this.defineProp('fileName', {'dataType': DataType.STRING, 'serializable': false});
		this.defineProp('filters', {'dataType': DataType.ARRAY});
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		this.setEnabled(this.getEnabled() /*&& this.getData()*/ && Kekule.NativeServices.canSaveFileData());
	},
	/** @private */
	doExecute: function(target)
	{
		var doc;
		if (!target)
			doc = document;
		else
		{
			var elem = target.getElement();
			doc = elem.ownerDocument;
		}
		Kekule.NativeServices.saveFileData(doc, this.getData(), null, {'initialFileName': this.getFileName(), 'filters': this.getFilters()});
		/*
		var dataElem = this.createDataElem(doc, this.getData(), this.getFileName());
		dataElem.click();  // save file dialog
		dataElem.parentNode.removeChild(dataElem);
		*/
	}

	/** @private */
	/*
	createDataElem: function(doc, data, fileName)
	{
		var elem = doc.createElement('a');
		elem.setAttribute('href', 'data:application/octet-stream,' + encodeURIComponent(data));
		elem.setAttribute('download', fileName);
		elem.innerHTML = 'download here!';
		doc.body.appendChild(elem);
		return elem;
	}
  */
});

/**
 * A util to manager all named actions for special widgets.
 * @class
 */
Kekule.ActionManager = {
	/** @private */
	_namedActionMap: null,
	/** @private */
	_getNamedActionMap: function(canCreate)
	{
		var result = AM._namedActionMap;
		if (!result && canCreate)
		{
			result = new Kekule.MapEx();
			AM._namedActionMap = result;
		}
		return result;
	},
	/** @private */
	getRegisteredActionsOfClass: function(widgetClass, canCreate)
	{
		var actionMap = AM._getNamedActionMap(canCreate);
		if (actionMap)
		{
			var result = actionMap.get(widgetClass);
			if (!result && canCreate)
			{
				result = {};
				actionMap.set(widgetClass, result);
			}
			return result;
		}
		else
			return null;
	},
	/**
	 * Register a named action to a special widget class.
	 * @param {String} name
	 * @param {Class} actionClass
	 * @param {Class} targetWidgetClass
	 */
	registerNamedActionClass: function(name, actionClass, targetWidgetClass)
	{
		var actions = AM.getRegisteredActionsOfClass(targetWidgetClass, true);
		actions[name] = actionClass;
	},
	/**
	 * Unregister a named action from a special widget class.
	 * @param {String} name
	 * @param {Class} targetWidgetClass
	 */
	unregisterNamedActionClass: function(name, targetWidgetClass)
	{
		var actions = AM.getRegisteredActionsOfClass(targetWidgetClass, false);
		if (actions && actions[name])
			delete actions[name];
	},
	/**
	 * Returns action class associated with name for a specific widget class.
	 * @param {String} name
	 * @param {Variant} widgetOrClass Widget instance of widget class.
	 * @param {Bool} checkSupClasses When true, if action is not found in current widget class, super classes will also be checked.
	 * @returns {Class}
	 */
	getActionClassOfName: function(name, widgetOrClass, checkSupClasses)
	{
		var widgetClass = ClassEx.isClass(widgetOrClass)? widgetOrClass:
				(widgetOrClass.getClass && widgetOrClass.getClass());
		if (!widgetClass)
			return null;
		var actions = AM.getRegisteredActionsOfClass(widgetClass, false);
		var result = actions && actions[name];
		if (!result && checkSupClasses)  // cascade
		{
			var supClass = ClassEx.getSuperClass(widgetClass);
			result = supClass? AM.getActionClassOfName(name, supClass, checkSupClasses): null;
		}
		return result;
	}
};
var AM = Kekule.ActionManager;

})();