/**
 * Editor.Events.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

(function(tinymce) {
	var each = tinymce.each;

	/**
	 * Creates all event dispatcher instances for the editor instance and also adds
	 * passthoughs for legacy callback handlers.
	 */
	tinymce.Editor.prototype.setupEvents = function() {
		var self = this, settings = self.settings;

		// Add events to the editor
		each([
			/**
			 * Fires before the initialization of the editor.
			 *
			 * @event onPreInit
			 * @param {tinymce.Editor} sender Editor instance.
			 * @see #onInit
			 * @example
			 * // Adds an observer to the onPreInit event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onPreInit.add(function(ed) {
			 *           console.debug('PreInit: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onPreInit',

			/**
			 * Fires before the initialization of the editor.
			 *
			 * @event onBeforeRenderUI
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onBeforeRenderUI event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *      ed.onBeforeRenderUI.add(function(ed, cm) {
			 *          console.debug('Before render: ' + ed.id);
			 *      });
			 *    }
			 * });
			 */
			'onBeforeRenderUI',

			/**
			 * Fires after the rendering has completed.
			 *
			 * @event onPostRender
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onPostRender event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onPostRender.add(function(ed, cm) {
			 *           console.debug('After render: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onPostRender',

			/**
			 * Fires when the onload event on the body occurs.
			 *
			 * @event onLoad
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onLoad event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onLoad.add(function(ed, cm) {
			 *           console.debug('Document loaded: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onLoad',

			/**
			 * Fires after the initialization of the editor is done.
			 *
			 * @event onInit
			 * @param {tinymce.Editor} sender Editor instance.
			 * @see #onPreInit
			 * @example
			 * // Adds an observer to the onInit event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onInit.add(function(ed) {
			 *           console.debug('Editor is done: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onInit',

			/**
			 * Fires when the editor instance is removed from page.
			 *
			 * @event onRemove
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onRemove event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onRemove.add(function(ed) {
			 *           console.debug('Editor was removed: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onRemove',

			/**
			 * Fires when the editor is activated.
			 *
			 * @event onActivate
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onActivate event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onActivate.add(function(ed) {
			 *           console.debug('Editor was activated: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onActivate',

			/**
			 * Fires when the editor is deactivated.
			 *
			 * @event onDeactivate
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onDeactivate event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onDeactivate.add(function(ed) {
			 *           console.debug('Editor was deactivated: ' + ed.id);
			 *       });
			 *    }
			 * });
			 */
			'onDeactivate',

			/**
			 * Fires when something in the body of the editor is clicked.
			 *
			 * @event onClick
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onClick event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onClick.add(function(ed, e) {
			 *           console.debug('Editor was clicked: ' + e.target.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onClick',

			/**
			 * Fires when a registered event is intercepted.
			 *
			 * @event onEvent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onEvent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onEvent.add(function(ed, e) {
			 *          console.debug('Editor event occurred: ' + e.target.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onEvent',

			/**
			 * Fires when a mouseup event is intercepted inside the editor.
			 *
			 * @event onMouseUp
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onMouseUp event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onMouseUp.add(function(ed, e) {
			 *           console.debug('Mouse up event: ' + e.target.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onMouseUp',

			/**
			 * Fires when a mousedown event is intercepted inside the editor.
			 *
			 * @event onMouseDown
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onMouseDown event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onMouseDown.add(function(ed, e) {
			 *           console.debug('Mouse down event: ' + e.target.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onMouseDown',

			/**
			 * Fires when a dblclick event is intercepted inside the editor.
			 *
			 * @event onDblClick
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onDblClick event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onDblClick.add(function(ed, e) {
			 *          console.debug('Double click event: ' + e.target.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onDblClick',

			/**
			 * Fires when a keydown event is intercepted inside the editor.
			 *
			 * @event onKeyDown
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onKeyDown event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onKeyDown.add(function(ed, e) {
			 *           console.debug('Key down event: ' + e.keyCode);
			 *       });
			 *    }
			 * });
			 */
			'onKeyDown',

			/**
			 * Fires when a keydown event is intercepted inside the editor.
			 *
			 * @event onKeyUp
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onKeyUp event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onKeyUp.add(function(ed, e) {
			 *           console.debug('Key up event: ' + e.keyCode);
			 *       });
			 *    }
			 * });
			 */
			'onKeyUp',

			/**
			 * Fires when a keypress event is intercepted inside the editor.
			 *
			 * @event onKeyPress
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onKeyPress event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onKeyPress.add(function(ed, e) {
			 *           console.debug('Key press event: ' + e.keyCode);
			 *       });
			 *    }
			 * });
			 */
			'onKeyPress',

			/**
			 * Fires when a contextmenu event is intercepted inside the editor.
			 *
			 * @event onContextMenu
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onContextMenu event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onContextMenu.add(function(ed, e) {
			 *            console.debug('Context menu event:' + e.target);
			 *       });
			 *    }
			 * });
			 */
			'onContextMenu',

			/**
			 * Fires when a form submit event is intercepted.
			 *
			 * @event onSubmit
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onSubmit event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onSubmit.add(function(ed, e) {
			 *            console.debug('Form submit:' + e.target);
			 *       });
			 *    }
			 * });
			 */
			'onSubmit',

			/**
			 * Fires when a form reset event is intercepted.
			 *
			 * @event onReset
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onReset event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onReset.add(function(ed, e) {
			 *            console.debug('Form reset:' + e.target);
			 *       });
			 *    }
			 * });
			 */
			'onReset',

			/**
			 * Fires when a paste event is intercepted inside the editor.
			 *
			 * @event onPaste
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onPaste event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onPaste.add(function(ed, e) {
			 *            console.debug('Pasted plain text');
			 *       });
			 *    }
			 * });
			 */
			'onPaste',

			/**
			 * Fires when the Serializer does a preProcess on the contents.
			 *
			 * @event onPreProcess
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Object} obj PreProcess object.
			 * @option {Node} node DOM node for the item being serialized.
			 * @option {String} format The specified output format normally "html".
			 * @option {Boolean} get Is true if the process is on a getContent operation.
			 * @option {Boolean} set Is true if the process is on a setContent operation.
			 * @option {Boolean} cleanup Is true if the process is on a cleanup operation.
			 * @example
			 * // Adds an observer to the onPreProcess event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onPreProcess.add(function(ed, o) {
			 *            // Add a class to each paragraph in the editor
			 *            ed.dom.addClass(ed.dom.select('p', o.node), 'myclass');
			 *       });
			 *    }
			 * });
			 */
			'onPreProcess',

			/**
			 * Fires when the Serializer does a postProcess on the contents.
			 *
			 * @event onPostProcess
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Object} obj PreProcess object.
			 * @example
			 * // Adds an observer to the onPostProcess event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onPostProcess.add(function(ed, o) {
			 *            // Remove all paragraphs and replace with BR
			 *            o.content = o.content.replace(/<p[^>]+>|<p>/g, '');
			 *            o.content = o.content.replace(/<\/p>/g, '<br />');
			 *       });
			 *    }
			 * });
			 */
			'onPostProcess',

			/**
			 * Fires before new contents is added to the editor. Using for example setContent.
			 *
			 * @event onBeforeSetContent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onBeforeSetContent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onBeforeSetContent.add(function(ed, o) {
			 *            // Replaces all a characters with b characters
			 *            o.content = o.content.replace(/a/g, 'b');
			 *       });
			 *    }
			 * });
			 */
			'onBeforeSetContent',

			/**
			 * Fires before contents is extracted from the editor using for example getContent.
			 *
			 * @event onBeforeGetContent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Event} evt W3C DOM Event instance.
			 * @example
			 * // Adds an observer to the onBeforeGetContent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onBeforeGetContent.add(function(ed, o) {
			 *            console.debug('Before get content.');
			 *       });
			 *    }
			 * });
			 */
			'onBeforeGetContent',

			/**
			 * Fires after the contents has been added to the editor using for example onSetContent.
			 *
			 * @event onSetContent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onSetContent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onSetContent.add(function(ed, o) {
			 *            // Replaces all a characters with b characters
			 *            o.content = o.content.replace(/a/g, 'b');
			 *       });
			 *    }
			 * });
			 */
			'onSetContent',

			/**
			 * Fires after the contents has been extracted from the editor using for example getContent.
			 *
			 * @event onGetContent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onGetContent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onGetContent.add(function(ed, o) {
			 *           // Replace all a characters with b
			 *           o.content = o.content.replace(/a/g, 'b');
			 *       });
			 *    }
			 * });
			 */
			'onGetContent',

			/**
			 * Fires when the editor gets loaded with contents for example when the load method is executed.
			 *
			 * @event onLoadContent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onLoadContent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onLoadContent.add(function(ed, o) {
			 *           // Output the element name
			 *           console.debug(o.element.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onLoadContent',

			/**
			 * Fires when the editor contents gets saved for example when the save method is executed.
			 *
			 * @event onSaveContent
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onSaveContent event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onSaveContent.add(function(ed, o) {
			 *           // Output the element name
			 *           console.debug(o.element.nodeName);
			 *       });
			 *    }
			 * });
			 */
			'onSaveContent',

			/**
			 * Fires when the user changes node location using the mouse or keyboard.
			 *
			 * @event onNodeChange
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onNodeChange event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onNodeChange.add(function(ed, cm, e) {
			 *           // Activates the link button when the caret is placed in a anchor element
			 *           if (e.nodeName == 'A')
			 *              cm.setActive('link', true);
			 *       });
			 *    }
			 * });
			 */
			'onNodeChange',

			/**
			 * Fires when a new undo level is added to the editor.
			 *
			 * @event onChange
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onChange event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onChange.add(function(ed, l) {
			 *          console.debug('Editor contents was modified. Contents: ' + l.content);
			 *       });
			 *    }
			 * });
			 */
			'onChange',

			/**
			 * Fires before a command gets executed for example "Bold".
			 *
			 * @event onBeforeExecCommand
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onBeforeExecCommand event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onBeforeExecCommand.add(function(ed, cmd, ui, val) {
			 *           console.debug('Command is to be executed: ' + cmd);
			 *       });
			 *    }
			 * });
			 */
			'onBeforeExecCommand',

			/**
			 * Fires after a command is executed for example "Bold".
			 *
			 * @event onExecCommand
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onExecCommand event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onExecCommand.add(function(ed, cmd, ui, val) {
			 *           console.debug('Command was executed: ' + cmd);
			 *       });
			 *    }
			 * });
			 */
			'onExecCommand',

			/**
			 * Fires when the contents is undo:ed.
			 *
			 * @event onUndo
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Object} level Undo level object.
			 * @ example
			 * // Adds an observer to the onUndo event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onUndo.add(function(ed, level) {
			 *           console.debug('Undo was performed: ' + level.content);
			 *       });
			 *    }
			 * });
			 */
			'onUndo',

			/**
			 * Fires when the contents is redo:ed.
			 *
			 * @event onRedo
			 * @param {tinymce.Editor} sender Editor instance.
			 * @param {Object} level Undo level object.
			 * @example
			 * // Adds an observer to the onRedo event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onRedo.add(function(ed, level) {
			 *           console.debug('Redo was performed: ' +level.content);
			 *       });
			 *    }
			 * });
			 */
			'onRedo',

			/**
			 * Fires when visual aids is enabled/disabled.
			 *
			 * @event onVisualAid
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onVisualAid event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onVisualAid.add(function(ed, e, s) {
			 *           console.debug('onVisualAid event: ' + ed.id + ", State: " + s);
			 *       });
			 *    }
			 * });
			 */
			'onVisualAid',

			/**
			 * Fires when the progress throbber is shown above the editor.
			 *
			 * @event onSetProgressState
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onSetProgressState event using tinyMCE.init
			 * tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onSetProgressState.add(function(ed, b) {
			 *            if (b)
			 *                 console.debug('SHOW!');
			 *            else
			 *                 console.debug('HIDE!');
			 *       });
			 *    }
			 * });
			 */
			'onSetProgressState',

			/**
			 * Fires after an attribute is set using setAttrib.
			 *
			 * @event onSetAttrib
			 * @param {tinymce.Editor} sender Editor instance.
			 * @example
			 * // Adds an observer to the onSetAttrib event using tinyMCE.init
			 *tinyMCE.init({
			 *    ...
			 *    setup : function(ed) {
			 *       ed.onSetAttrib.add(function(ed, node, attribute, attributeValue) {
			 *            console.log('onSetAttrib tag');
			 *       });
			 *    }
			 * });
			 */
			'onSetAttrib'
		], function(name) {
			self[name] = new tinymce.util.Dispatcher(self);
		});

		// Handle legacy cleanup_callback option
		if (settings.cleanup_callback) {
			self.onBeforeSetContent.add(function(ed, o) {
				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
			});

			self.onPreProcess.add(function(ed, o) {
				if (o.set)
					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);

				if (o.get)
					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
			});

			self.onPostProcess.add(function(ed, o) {
				if (o.set)
					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);

				if (o.get)						
					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
			});
		}

		// Handle legacy save_callback option
		if (settings.save_callback) {
			self.onGetContent.add(function(ed, o) {
				if (o.save)
					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
			});
		}

		// Handle legacy handle_event_callback option
		if (settings.handle_event_callback) {
			self.onEvent.add(function(ed, e, o) {
				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
					e.preventDefault();
					e.stopPropagation();
				}
			});
		}

		// Handle legacy handle_node_change_callback option
		if (settings.handle_node_change_callback) {
			self.onNodeChange.add(function(ed, cm, n) {
				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
			});
		}

		// Handle legacy save_callback option
		if (settings.save_callback) {
			self.onSaveContent.add(function(ed, o) {
				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());

				if (h)
					o.content = h;
			});
		}

		// Handle legacy onchange_callback option
		if (settings.onchange_callback) {
			self.onChange.add(function(ed, l) {
				ed.execCallback('onchange_callback', ed, l);
			});
		}
	};

	/**
	 * Binds native DOM events and sends these out to the dispatchers.
	 */
	tinymce.Editor.prototype.bindNativeEvents = function() {
		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;

		nativeToDispatcherMap = {
			mouseup : 'onMouseUp',
			mousedown : 'onMouseDown',
			click : 'onClick',
			keyup : 'onKeyUp',
			keydown : 'onKeyDown',
			keypress : 'onKeyPress',
			submit : 'onSubmit',
			reset : 'onReset',
			contextmenu : 'onContextMenu',
			dblclick : 'onDblClick',
			paste : 'onPaste' // Doesn't work in all browsers yet
		};

		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
		function eventHandler(evt, args) {
			var type = evt.type;

			// Don't fire events when it's removed
			if (self.removed)
				return;

			// Sends the native event out to a global dispatcher then to the specific event dispatcher
			if (self.onEvent.dispatch(self, evt, args) !== false) {
				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
			}
		};

		// Opera doesn't support focus event for contentEditable elements so we need to fake it
		function doOperaFocus(e) {
			self.focus(true);
		};

		function nodeChanged(ed, e) {
			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
				self.selection.normalize();
			}

			self.nodeChanged();
		}

		// Add DOM events
		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
			var root = settings.content_editable ? self.getBody() : self.getDoc();

			switch (nativeName) {
				case 'contextmenu':
					dom.bind(root, nativeName, eventHandler);
					break;

				case 'paste':
					dom.bind(self.getBody(), nativeName, eventHandler);
					break;

				case 'submit':
				case 'reset':
					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
					break;

				default:
					dom.bind(root, nativeName, eventHandler);
			}
		});

		// Set the editor as active when focused
		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
			self.focus(true);
		});

		if (settings.content_editable && tinymce.isOpera) {
			dom.bind(self.getBody(), 'click', doOperaFocus);
			dom.bind(self.getBody(), 'keydown', doOperaFocus);
		}

		// Add node change handler
		self.onMouseUp.add(nodeChanged);

		self.onKeyUp.add(function(ed, e) {
			var keyCode = e.keyCode;

			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
				nodeChanged(ed, e);
		});

		// Add reset handler
		self.onReset.add(function() {
			self.setContent(self.startContent, {format : 'raw'});
		});

		// Add shortcuts
		function handleShortcut(e, execute) {
			if (e.altKey || e.ctrlKey || e.metaKey) {
				each(self.shortcuts, function(shortcut) {
					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;

					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
						return;

					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
						e.preventDefault();

						if (execute) {
							shortcut.func.call(shortcut.scope);
						}

						return true;
					}
				});
			}
		};

		self.onKeyUp.add(function(ed, e) {
			handleShortcut(e);
		});

		self.onKeyPress.add(function(ed, e) {
			handleShortcut(e);
		});

		self.onKeyDown.add(function(ed, e) {
			handleShortcut(e, true);
		});

		if (tinymce.isOpera) {
			self.onClick.add(function(ed, e) {
				e.preventDefault();
			});
		}
	};
})(tinymce);