/**
 * @class Ext.EventObject

Just as {@link Ext.core.Element} wraps around a native DOM node, Ext.EventObject
wraps the browser's native event-object normalizing cross-browser differences,
such as which mouse button is clicked, keys pressed, mechanisms to stop
event-propagation along with a method to prevent default actions from taking place.

For example:

    function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
        e.preventDefault();
        var target = e.getTarget(); // same as t (the target HTMLElement)
        ...
    }

    var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.core.Element}
    myDiv.on(         // 'on' is shorthand for addListener
        "click",      // perform an action on click of myDiv
        handleClick   // reference to the action handler
    );

    // other methods to do the same:
    Ext.EventManager.on("myDiv", 'click', handleClick);
    Ext.EventManager.addListener("myDiv", 'click', handleClick);

 * @singleton
 * @markdown
 */

Ext.define('Ext.EventObjectImpl', {
    uses
: ['Ext.util.Point'],

    /** Key constant @type Number */
    BACKSPACE: 8,
    /** Key constant @type Number */
    TAB: 9,
    /** Key constant @type Number */
    NUM_CENTER: 12,
    /** Key constant @type Number */
    ENTER: 13,
    /** Key constant @type Number */
    RETURN: 13,
    /** Key constant @type Number */
    SHIFT: 16,
    /** Key constant @type Number */
    CTRL: 17,
    /** Key constant @type Number */
    ALT: 18,
    /** Key constant @type Number */
    PAUSE: 19,
    /** Key constant @type Number */
    CAPS_LOCK: 20,
    /** Key constant @type Number */
    ESC: 27,
    /** Key constant @type Number */
    SPACE: 32,
    /** Key constant @type Number */
    PAGE_UP: 33,
    /** Key constant @type Number */
    PAGE_DOWN: 34,
    /** Key constant @type Number */
    END: 35,
    /** Key constant @type Number */
    HOME: 36,
    /** Key constant @type Number */
    LEFT: 37,
    /** Key constant @type Number */
    UP: 38,
    /** Key constant @type Number */
    RIGHT: 39,
    /** Key constant @type Number */
    DOWN: 40,
    /** Key constant @type Number */
    PRINT_SCREEN: 44,
    /** Key constant @type Number */
    INSERT: 45,
    /** Key constant @type Number */
    DELETE: 46,
    /** Key constant @type Number */
    ZERO: 48,
    /** Key constant @type Number */
    ONE: 49,
    /** Key constant @type Number */
    TWO: 50,
    /** Key constant @type Number */
    THREE: 51,
    /** Key constant @type Number */
    FOUR: 52,
    /** Key constant @type Number */
    FIVE: 53,
    /** Key constant @type Number */
    SIX: 54,
    /** Key constant @type Number */
    SEVEN: 55,
    /** Key constant @type Number */
    EIGHT: 56,
    /** Key constant @type Number */
    NINE: 57,
    /** Key constant @type Number */
    A: 65,
    /** Key constant @type Number */
    B: 66,
    /** Key constant @type Number */
    C: 67,
    /** Key constant @type Number */
    D: 68,
    /** Key constant @type Number */
    E: 69,
    /** Key constant @type Number */
    F: 70,
    /** Key constant @type Number */
    G: 71,
    /** Key constant @type Number */
    H: 72,
    /** Key constant @type Number */
    I: 73,
    /** Key constant @type Number */
    J: 74,
    /** Key constant @type Number */
    K: 75,
    /** Key constant @type Number */
    L: 76,
    /** Key constant @type Number */
    M: 77,
    /** Key constant @type Number */
    N: 78,
    /** Key constant @type Number */
    O: 79,
    /** Key constant @type Number */
    P: 80,
    /** Key constant @type Number */
    Q: 81,
    /** Key constant @type Number */
    R: 82,
    /** Key constant @type Number */
    S: 83,
    /** Key constant @type Number */
    T: 84,
    /** Key constant @type Number */
    U: 85,
    /** Key constant @type Number */
    V: 86,
    /** Key constant @type Number */
    W: 87,
    /** Key constant @type Number */
    X: 88,
    /** Key constant @type Number */
    Y: 89,
    /** Key constant @type Number */
    Z: 90,
    /** Key constant @type Number */
    CONTEXT_MENU: 93,
    /** Key constant @type Number */
    NUM_ZERO: 96,
    /** Key constant @type Number */
    NUM_ONE: 97,
    /** Key constant @type Number */
    NUM_TWO: 98,
    /** Key constant @type Number */
    NUM_THREE: 99,
    /** Key constant @type Number */
    NUM_FOUR: 100,
    /** Key constant @type Number */
    NUM_FIVE: 101,
    /** Key constant @type Number */
    NUM_SIX: 102,
    /** Key constant @type Number */
    NUM_SEVEN: 103,
    /** Key constant @type Number */
    NUM_EIGHT: 104,
    /** Key constant @type Number */
    NUM_NINE: 105,
    /** Key constant @type Number */
    NUM_MULTIPLY: 106,
    /** Key constant @type Number */
    NUM_PLUS: 107,
    /** Key constant @type Number */
    NUM_MINUS: 109,
    /** Key constant @type Number */
    NUM_PERIOD: 110,
    /** Key constant @type Number */
    NUM_DIVISION: 111,
    /** Key constant @type Number */
    F1: 112,
    /** Key constant @type Number */
    F2: 113,
    /** Key constant @type Number */
    F3: 114,
    /** Key constant @type Number */
    F4: 115,
    /** Key constant @type Number */
    F5: 116,
    /** Key constant @type Number */
    F6: 117,
    /** Key constant @type Number */
    F7: 118,
    /** Key constant @type Number */
    F8: 119,
    /** Key constant @type Number */
    F9: 120,
    /** Key constant @type Number */
    F10: 121,
    /** Key constant @type Number */
    F11: 122,
    /** Key constant @type Number */
    F12: 123,

    /**
     * Simple click regex
     * @private
     */

    clickRe
: /(dbl)?click/,
   
// safari keypress events for special keys return bad keycodes
    safariKeys
: {
       
3: 13, // enter
       
63234: 37, // left
       
63235: 39, // right
       
63232: 38, // up
       
63233: 40, // down
       
63276: 33, // page up
       
63277: 34, // page down
       
63272: 46, // delete
       
63273: 36, // home
       
63275: 35 // end
   
},
   
// normalize button clicks, don't see any way to feature detect this.
    btnMap
: Ext.isIE ? {
       
1: 0,
       
4: 1,
       
2: 2
   
} : {
       
0: 0,
       
1: 1,
       
2: 2
   
},

    constructor
: function(event, freezeEvent){
       
if (event) {
           
this.setEvent(event.browserEvent || event, freezeEvent);
       
}
   
},

    setEvent
: function(event, freezeEvent){
       
var me = this, button, options;

       
if (event == me || (event && event.browserEvent)) { // already wrapped
           
return event;
       
}
        me
.browserEvent = event;
       
if (event) {
           
// normalize buttons
            button
= event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
           
if (me.clickRe.test(event.type) && button == -1) {
                button
= 0;
           
}
            options
= {
                type
: event.type,
                button
: button,
                shiftKey
: event.shiftKey,
               
// mac metaKey behaves like ctrlKey
                ctrlKey
: event.ctrlKey || event.metaKey || false,
                altKey
: event.altKey,
               
// in getKey these will be normalized for the mac
                keyCode
: event.keyCode,
                charCode
: event.charCode,
               
// cache the targets for the delayed and or buffered events
                target
: Ext.EventManager.getTarget(event),
                relatedTarget
: Ext.EventManager.getRelatedTarget(event),
                currentTarget
: event.currentTarget,
                xy
: (freezeEvent ? me.getXY() : null)
           
};
       
} else {
            options
= {
                button
: -1,
                shiftKey
: false,
                ctrlKey
: false,
                altKey
: false,
                keyCode
: 0,
                charCode
: 0,
                target
: null,
                xy
: [0, 0]
           
};
       
}
       
Ext.apply(me, options);
       
return me;
   
},

    /**
     * Stop the event (preventDefault and stopPropagation)
     */

    stopEvent
: function(){
       
this.stopPropagation();
       
this.preventDefault();
   
},

    /**
     * Prevents the browsers default handling of the event.
     */

    preventDefault
: function(){
       
if (this.browserEvent) {
           
Ext.EventManager.preventDefault(this.browserEvent);
       
}
   
},

    /**
     * Cancels bubbling of the event.
     */

    stopPropagation
: function(){
       
var browserEvent = this.browserEvent;

       
if (browserEvent) {
           
if (browserEvent.type == 'mousedown') {
               
Ext.EventManager.stoppedMouseDownEvent.fire(this);
           
}
           
Ext.EventManager.stopPropagation(browserEvent);
       
}
   
},

    /**
     * Gets the character code for the event.
     * @return {Number}
     */

    getCharCode
: function(){
       
return this.charCode || this.keyCode;
   
},

    /**
     * Returns a normalized keyCode for the event.
     * @return {Number} The key code
     */

    getKey
: function(){
       
return this.normalizeKey(this.keyCode || this.charCode);
   
},

    /**
     * Normalize key codes across browsers
     * @private
     * @param {Number} key The key code
     * @return {Number} The normalized code
     */

    normalizeKey
: function(key){
       
// can't feature detect this
       
return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
   
},

    /**
     * Gets the x coordinate of the event.
     * @return {Number}
     * @deprecated 4.0 Replaced by {@link #getX}
     */

    getPageX
: function(){
       
return this.getX();
   
},

    /**
     * Gets the y coordinate of the event.
     * @return {Number}
     * @deprecated 4.0 Replaced by {@link #getY}
     */

    getPageY
: function(){
       
return this.getY();
   
},
   
    /**
     * Gets the x coordinate of the event.
     * @return {Number}
     */

    getX
: function() {
       
return this.getXY()[0];
   
},    
   
    /**
     * Gets the y coordinate of the event.
     * @return {Number}
     */

    getY
: function() {
       
return this.getXY()[1];
   
},
       
    /**
     * Gets the page coordinates of the event.
     * @return {Array} The xy values like [x, y]
     */

    getXY
: function() {
       
if (!this.xy) {
           
// same for XY
           
this.xy = Ext.EventManager.getPageXY(this.browserEvent);
       
}
       
return this.xy;
   
},

    /**
     * Gets the target for the event.
     * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
     * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
     * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
     * @return {HTMLelement}
     */

    getTarget
: function(selector, maxDepth, returnEl){
       
if (selector) {
           
return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
       
}
       
return returnEl ? Ext.get(this.target) : this.target;
   
},

    /**
     * Gets the related target.
     * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
     * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
     * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
     * @return {HTMLElement}
     */

    getRelatedTarget
: function(selector, maxDepth, returnEl){
       
if (selector) {
           
return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
       
}
       
return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
   
},

    /**
     * Normalizes mouse wheel delta across browsers
     * @return {Number} The delta
     */

    getWheelDelta
: function(){
       
var event = this.browserEvent,
            delta
= 0;

       
if (event.wheelDelta) { /* IE/Opera. */
            delta
= event.wheelDelta / 120;
       
} else if (event.detail){ /* Mozilla case. */
            delta
= -event.detail / 3;
       
}
       
return delta;
   
},

    /**
    * Returns true if the target of this event is a child of el.  Unless the allowEl parameter is set, it will return false if if the target is el.
    * Example usage:<pre><code>
// Handle click on any child of an element
Ext.getBody().on('click', function(e){
    if(e.within('some-el')){
        alert('Clicked on a child of some-el!');
    }
});

// Handle click directly on an element, ignoring clicks on child nodes
Ext.getBody().on('click', function(e,t){
    if((t.id == 'some-el') && !e.within(t, true)){
        alert('Clicked directly on some-el!');
    }
});
</code></pre>
     * @param {Mixed} el The id, DOM element or Ext.core.Element to check
     * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
     * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
     * @return {Boolean}
     */

    within
: function(el, related, allowEl){
       
if(el){
           
var t = related ? this.getRelatedTarget() : this.getTarget(),
                result
;

           
if (t) {
                result
= Ext.fly(el).contains(t);
               
if (!result && allowEl) {
                    result
= t == Ext.getDom(el);
               
}
               
return result;
           
}
       
}
       
return false;
   
},

    /**
     * Checks if the key pressed was a "navigation" key
     * @return {Boolean} True if the press is a navigation keypress
     */

    isNavKeyPress
: function(){
       
var me = this,
            k
= this.normalizeKey(me.keyCode);

       
return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
       k
== me.RETURN ||
       k
== me.TAB ||
       k
== me.ESC;
   
},

    /**
     * Checks if the key pressed was a "special" key
     * @return {Boolean} True if the press is a special keypress
     */

    isSpecialKey
: function(){
       
var k = this.normalizeKey(this.keyCode);
       
return (this.type == 'keypress' && this.ctrlKey) ||
       
this.isNavKeyPress() ||
       
(k == this.BACKSPACE) || // Backspace
       
(k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
       
(k >= 44 && k <= 46);   // Print Screen, Insert, Delete
   
},

    /**
     * Returns a point object that consists of the object coordinates.
     * @return {Ext.util.Point} point
     */

    getPoint
: function(){
       
var xy = this.getXY();
       
return Ext.create('Ext.util.Point', xy[0], xy[1]);
   
},

   /**
    * Returns true if the control, meta, shift or alt key was pressed during this event.
    * @return {Boolean}
    */

    hasModifier
: function(){
       
return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
   
},

    /**
     * Injects a DOM event using the data in this object and (optionally) a new target.
     * This is a low-level technique and not likely to be used by application code. The
     * currently supported event types are:
     * <p><b>HTMLEvents</b></p>
     * <ul>
     * <li>load</li>
     * <li>unload</li>
     * <li>select</li>
     * <li>change</li>
     * <li>submit</li>
     * <li>reset</li>
     * <li>resize</li>
     * <li>scroll</li>
     * </ul>
     * <p><b>MouseEvents</b></p>
     * <ul>
     * <li>click</li>
     * <li>dblclick</li>
     * <li>mousedown</li>
     * <li>mouseup</li>
     * <li>mouseover</li>
     * <li>mousemove</li>
     * <li>mouseout</li>
     * </ul>
     * <p><b>UIEvents</b></p>
     * <ul>
     * <li>focusin</li>
     * <li>focusout</li>
     * <li>activate</li>
     * <li>focus</li>
     * <li>blur</li>
     * </ul>
     * @param {Element/HTMLElement} target If specified, the target for the event. This
     * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
     * is used to determine the target.
     */

    injectEvent
: function () {
       
var API,
            dispatchers
= {}; // keyed by event type (e.g., 'mousedown')

       
// Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html

       
// IE9 has createEvent, but this code causes major problems with htmleditor (it
       
// blocks all mouse events and maybe more). TODO

       
if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
            API
= {
                createHtmlEvent
: function (doc, type, bubbles, cancelable) {
                   
var event = doc.createEvent('HTMLEvents');

                   
event.initEvent(type, bubbles, cancelable);
                   
return event;
               
},

                createMouseEvent
: function (doc, type, bubbles, cancelable, detail,
                                            clientX
, clientY, ctrlKey, altKey, shiftKey, metaKey,
                                            button
, relatedTarget) {
                   
var event = doc.createEvent('MouseEvents'),
                        view
= doc.defaultView || window;

                   
if (event.initMouseEvent) {
                       
event.initMouseEvent(type, bubbles, cancelable, view, detail,
                                    clientX
, clientY, clientX, clientY, ctrlKey, altKey,
                                    shiftKey
, metaKey, button, relatedTarget);
                   
} else { // old Safari
                       
event = doc.createEvent('UIEvents');
                       
event.initEvent(type, bubbles, cancelable);
                       
event.view = view;
                       
event.detail = detail;
                       
event.screenX = clientX;
                       
event.screenY = clientY;
                       
event.clientX = clientX;
                       
event.clientY = clientY;
                       
event.ctrlKey = ctrlKey;
                       
event.altKey = altKey;
                       
event.metaKey = metaKey;
                       
event.shiftKey = shiftKey;
                       
event.button = button;
                       
event.relatedTarget = relatedTarget;
                   
}

                   
return event;
               
},

                createUIEvent
: function (doc, type, bubbles, cancelable, detail) {
                   
var event = doc.createEvent('UIEvents'),
                        view
= doc.defaultView || window;

                   
event.initUIEvent(type, bubbles, cancelable, view, detail);
                   
return event;
               
},

                fireEvent
: function (target, type, event) {
                    target
.dispatchEvent(event);
               
},

                fixTarget
: function (target) {
                   
// Safari3 doesn't have window.dispatchEvent()
                   
if (target == window && !target.dispatchEvent) {
                       
return document;
                   
}

                   
return target;
               
}
           
}
       
} else if (document.createEventObject) { // else if (IE)
           
var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };

            API
= {
                createHtmlEvent
: function (doc, type, bubbles, cancelable) {
                   
var event = doc.createEventObject();
                   
event.bubbles = bubbles;
                   
event.cancelable = cancelable;
                   
return event;
               
},

                createMouseEvent
: function (doc, type, bubbles, cancelable, detail,
                                            clientX
, clientY, ctrlKey, altKey, shiftKey, metaKey,
                                            button
, relatedTarget) {
                   
var event = doc.createEventObject();
                   
event.bubbles = bubbles;
                   
event.cancelable = cancelable;
                   
event.detail = detail;
                   
event.screenX = clientX;
                   
event.screenY = clientY;
                   
event.clientX = clientX;
                   
event.clientY = clientY;
                   
event.ctrlKey = ctrlKey;
                   
event.altKey = altKey;
                   
event.shiftKey = shiftKey;
                   
event.metaKey = metaKey;
                   
event.button = crazyIEButtons[button] || button;
                   
event.relatedTarget = relatedTarget; // cannot assign to/fromElement
                   
return event;
               
},

                createUIEvent
: function (doc, type, bubbles, cancelable, detail) {
                   
var event = doc.createEventObject();
                   
event.bubbles = bubbles;
                   
event.cancelable = cancelable;
                   
return event;
               
},

                fireEvent
: function (target, type, event) {
                    target
.fireEvent('on' + type, event);
               
},

                fixTarget
: function (target) {
                   
if (target == document) {
                       
// IE6,IE7 thinks window==document and doesn't have window.fireEvent()
                       
// IE6,IE7 cannot properly call document.fireEvent()
                       
return document.documentElement;
                   
}

                   
return target;
               
}
           
};
       
}

       
//----------------
       
// HTMLEvents

       
Ext.Object.each({
                load
:   [false, false],
                unload
: [false, false],
               
select: [true, false],
                change
: [true, false],
                submit
: [true, true],
                reset
:  [true, false],
                resize
: [true, false],
                scroll
: [true, false]
           
},
           
function (name, value) {
               
var bubbles = value[0], cancelable = value[1];
                dispatchers
[name] = function (targetEl, srcEvent) {
                   
var e = API.createHtmlEvent(name, bubbles, cancelable);
                    API
.fireEvent(targetEl, name, e);
               
};
           
});

       
//----------------
       
// MouseEvents

       
function createMouseEventDispatcher (type, detail) {
           
var cancelable = (type != 'mousemove');
           
return function (targetEl, srcEvent) {
               
var xy = srcEvent.getXY(),
                    e
= API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
                                detail
, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
                                srcEvent
.shiftKey, srcEvent.metaKey, srcEvent.button,
                                srcEvent
.relatedTarget);
                API
.fireEvent(targetEl, type, e);
           
};
       
}

       
Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
           
function (eventName) {
                dispatchers
[eventName] = createMouseEventDispatcher(eventName, 1);
           
});

       
//----------------
       
// UIEvents

       
Ext.Object.each({
                focusin
:  [true, false],
                focusout
: [true, false],
                activate
: [true, true],
                focus
:    [false, false],
                blur
:     [false, false]
           
},
           
function (name, value) {
               
var bubbles = value[0], cancelable = value[1];
                dispatchers
[name] = function (targetEl, srcEvent) {
                   
var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
                    API
.fireEvent(targetEl, name, e);
               
};
           
});

       
//---------
       
if (!API) {
           
// not even sure what ancient browsers fall into this category...

            dispatchers
= {}; // never mind all those we just built :P

            API
= {
                fixTarget
: function (t) {
                   
return t;
               
}
           
};
       
}

       
function cannotInject (target, srcEvent) {
           
//<debug>
           
// TODO log something
           
//</debug>
       
}

       
return function (target) {
           
var me = this,
                dispatcher
= dispatchers[me.type] || cannotInject,
                t
= target ? (target.dom || target) : me.getTarget();

            t
= API.fixTarget(t);
            dispatcher
(t, me);
       
};
   
}() // call to produce method

}, function() {

Ext.EventObject = new Ext.EventObjectImpl();

});