//------------------键盘处理类

Namespace.register("U.K");

//#region 全局变量区域

//变量来存储_MAP的翻转版从上面
U.K._REVERSE_MAP;

//特殊键码的映射到其对应的键
U.K._MAP = {
    8: 'backspace',
    9: 'tab',
    13: 'enter',
    16: 'shift',
    17: 'ctrl',
    18: 'alt',
    20: 'capslock',
    27: 'esc',
    32: 'space',
    33: 'pageup',
    34: 'pagedown',
    35: 'end',
    36: 'home',
    37: 'left',
    38: 'up',
    39: 'right',
    40: 'down',
    45: 'ins',
    46: 'del',
    91: 'meta',
    93: 'meta',
    224: 'meta'
};

//特殊字符,使他们能够支持映射
U.K._KEYCODE_MAP = {
    106: '*',
    107: '+',
    109: '-',
    110: '.',
    111: '/',
    186: ';',
    187: '=',
    188: ',',
    189: '-',
    190: '.',
    191: '/',
    192: '`',
    219: '[',
    220: '\\',
    221: ']',
    222: '\''
};

//这是需要一个美国小键盘上的shift键的映射
U.K._SHIFT_MAP = {
    '~': '`',
    '!': '1',
    '@': '2',
    '#': '3',
    '$': '4',
    '%': '5',
    '^': '6',
    '&': '7',
    '*': '8',
    '(': '9',
    ')': '0',
    '_': '-',
    '+': '=',
    ':': ';',
    '\"': '\'',
    '<': ',',
    '>': '.',
    '?': '/',
    '|': '\\'
};

//这是你可以用它来绘制特殊字符串列表
U.K._SPECIAL_ALIASES = {
    'option': 'alt',
    'command': 'meta',
    'return': 'enter',
    'escape': 'esc',
    'plus': '+',
    'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
};

U.K.start = function () {
    //遍历F键,F1至F19,并把它们添加到地图
    for (var i = 1; i < 20; ++i) {
        U.K._MAP[111 + i] = 'f' + i;
    }

    //数字键盘的数字添加
    for (i = 0; i <= 9; ++i) {
        U.K._MAP[i + 96] = i.toString();
    }
}

//#endregion

//#region 方法使用区域

//取事件,并返回的键值
U.K._characterFromEvent = function (e) {

    //为按键事件,我们应该回到该字符是
    if (e.type == 'keypress') {
        var character = String.fromCharCode(e.which);

        // 如果没有然后按下Shift键它是安全的假设
        // 我们想要的字符是小写。这意味着,如果
        // 你不小心有大写锁定,然后键绑定
        // 将继续工作
        //
        // 可能是不希望的唯一副作用是,如果你
        // 绑定类似'A',因为你要触发
        // 事件被按下大写字母A时,大写锁定将不再
        // 触发事件。尽管绑定了shift+a。
        if (!e.shiftKey) {
            character = character.toLowerCase();
        }

        return character;
    }

    // 非按键事件所需的专用地图
    if (U.K._MAP[e.which]) {
        return U.K._MAP[e.which];
    }

    if (U.K._KEYCODE_MAP[e.which]) {
        return U.K._KEYCODE_MAP[e.which];
    }

    // 如果它不是在特殊的地图

    // 与KEYDOWN和KeyUp事件的性质似乎总
    // 进来作为一个大写字符无论您是按住Shift
    //  或不。我们应该确保它始终是小写的比较
    return String.fromCharCode(e.which).toLowerCase();
}

/**
* 检查,如果两个数组相等,
*
* @param {Array} modifiers1
* @param {Array} modifiers2
* @returns {boolean}
*/
U.K._modifiersMatch = function (modifiers1, modifiers2) {
    return modifiers1.sort().join(',') === modifiers2.sort().join(',');
}

/**
* 需要一个关键事件,并计算出的修饰符是什么
*
* @param {Event} e
* @returns {Array}
*/
U.K._eventModifiers = function (e) {
    var modifiers = [];

    if (e.shiftKey) {
        modifiers.push('shift');
    }

    if (e.altKey) {
        modifiers.push('alt');
    }

    if (e.ctrlKey) {
        modifiers.push('ctrl');
    }

    if (e.metaKey) {
        modifiers.push('meta');
    }

    return modifiers;
}


/**
* 确定指定的键码是一个修改键或不
*
* @param {string} key
* @returns {boolean}
*/
U.K._isModifier = function (key) {
    return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
}

/**
* 颠倒了地图查找,这样我们可以寻找特定键
*看看有什么可以和不能使用的按键
*
* @return {Object}
*/
U.K._getReverseMap = function () {
    if (!_REVERSE_MAP) {
        _REVERSE_MAP = {};
        for (var key in U.K._MAP) {

            //从这里拉出数字小键盘按键的事业宜
            //能够从字符检测的按键
            if (key > 95 && key < 112) {
                continue;
            }

            if (U.K._MAP.hasOwnProperty(key)) {
                _REVERSE_MAP[U.K._MAP[key]] = key;
            }
        }
    }
    return _REVERSE_MAP;
}

/**
* 挑选基础上,组合键最佳动作
*
* @param {string} key - character for key
* @param {Array} modifiers
* @param {string=} action passed in
*/
U.K._pickBestAction = function (key, modifiers, action) {

    //如果没有动作,拿起我们应该尽量挑选一个
    //我们认为将工作最适合这一关键
    if (!action) {
        action = U.K._getReverseMap()[key] ? 'keydown' : 'keypress';
    }

    //与预期的按键组合键不起作用,
    //切换到KEYDOWN
    if (action == 'keypress' && modifiers.length) {
        action = 'keydown';
    }

    return action;
}

/**
* *从一个字符串组合键转换到一个数组
*
* @param  {string} 的组合,如 "command+shift+l"
* @return {Array}
*/
U.K._keysFromString = function (combination) {
    if (combination === '+') {
        return ['+'];
    }

    combination = combination.replace(/\+{2}/g, '+plus');
    return combination.split('+');
}

/**
* Gets 获取信息的特定的组合键
*
* @param  {string} combination key combination ("command+s" or "a" or "*")
* @param  {string=} action
* @returns {Object}
*/
U.K._getKeyInfo = function (combination, action) {
    var keys;
    var key;
    var i;
    var modifiers = [];

    // 从这种模式带钥匙,弄清实际
    // 模式是所有
    keys = U.K._keysFromString(combination);

    for (i = 0; i < keys.length; ++i) {
        key = keys[i];

        // normalize key names
        if (U.K._SPECIAL_ALIASES[key]) {
            key = U.K._SPECIAL_ALIASES[key];
        }
        //如果这不是一个按键事件那么我们应该
        //聪明地使用shift键
        //这只会为我们工作不过键盘
        if (action && action != 'keypress' && U.K._SHIFT_MAP[key]) {
            key = U.K._SHIFT_MAP[key];
            modifiers.push('shift');
        }

        //如果该键是一个修改,然后将其添加到修改器列表
        if (U.K._isModifier(key)) {
            modifiers.push(key);
        }
    }

    //根据密钥的组合是什么
    //我们会尽力挑选最好的事件它
    action = U.K._pickBestAction(key, modifiers, action);

    return {
        key: key,
        modifiers: modifiers,
        action: action
    };
}

//判断监听来源
U.K._belongsTo = function (element, ancestor) {
    if (element === null || element === document) {
        return false;
    }

    if (element === ancestor) {
        return true;
    }

    return U.K._getReverseMap(element.parentNode, ancestor);
}

//#endregion

//#region 对外接口区域

//对外公布函数
U.K.Mousetrap = function (targetElement) {
    var self = this;

    targetElement = targetElement || document;

    if (!(self instanceof U.K.Mousetrap)) {
        return new U.K.Mousetrap(targetElement);
    }

    /**
    * 元素附加关键事件
    *
    * @type {Element}
    */
    self.target = targetElement;

    /**
    * 通过Mousetrap.bind所有的回调设置列表()
    *
    * @type {Object}
    */
    self._callbacks = {};

    /**
    * 字符串组合的直接映射到用于触发回调()
    *
    * @type {Object}
    */
    self._directMap = {};

    /**
    * 跟踪什么级别的每个序列是因为多个
    *序列可以开始时具有相同的序列
    *
    * @type {Object}
    */
    var _sequenceLevels = {};

    /**
    * 变量来存储方法setTimeout
    *
    * @type {null|number}
    */
    var _resetTimer;

    /**
    * 临时的状态,我们会忽略下一个KEYUP
    *
    * @type {boolean|string}
    */
    var _ignoreNextKeyup = false;

    /**
    * 临时的状态,我们会忽略下一个按键
    *
    * @type {boolean}
    */
    var _ignoreNextKeypress = false;

    /**
    * 是我们目前的序列里面?
    * acticon(“KEYUP”或“的keydown”或“keypress”)或flase的类型
    *
    * @type {boolean|string}
    */
    var _nextExpectedAction = false;

    /**
    * 重置所有序列柜台,除了传递的那些
    *
    * @param {Object} doNotReset
    * @returns void
    */
    function _resetSequences(doNotReset) {
        doNotReset = doNotReset || {};

        var activeSequences = false,
        key;

        for (key in _sequenceLevels) {
            if (doNotReset[key]) {
                activeSequences = true;
                continue;
            }
            _sequenceLevels[key] = 0;
        }

        if (!activeSequences) {
            _nextExpectedAction = false;
        }
    }

    /**
    * *查找匹配基础上的keyCode,所有的callback,modifiers,action
    *
    * @param {string} character
    * @param {Array} modifiers
    * @param {Event|Object} e
    * @param {string=} sequenceName - name of the sequence we are looking for
    * @param {string=} combination
    * @param {number=} level
    * @returns {Array}
    */
    function _getMatches(character, modifiers, e, sequenceName, combination, level) {
        var i;
        var callback;
        var matches = [];
        var action = e.type;

        // 如果没有与此相关的键码的事件
        if (!self._callbacks[character]) {
            return [];
        }

        // 如果修改键快到了自身,我们应该允许它
        if (action == 'keyup' && U.K._isModifier(character)) {
            modifiers = [character];
        }

        // 通过被按下该键所有的回调循环
        //,看看其中是否匹配
        for (i = 0; i < self._callbacks[character].length; ++i) {
            callback = self._callbacks[character][i];

            //如果一个序列名未指定,但是这是一个序列的
            //错误的水平,那么移动到下一个match
            if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
                continue;
            }

            // //如果我们正在寻找的action不符合我们的action
            //那么我们就应该继续执行
            if (action != callback.action) {
                continue;
            }

            //如果这是一个keypress event和meta和control key
            //没有按下这意味着我们只需要看
            //字符,否则检查改性剂以及
            //
            // Chrome浏览器不会触发一个keypress如果meta or control is down
            // Safari会触发一个 keypress 如果 meta or meta+shift is down
            // Firefox会触发一个keypress 如果 meta or control is down
            if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || U.K._modifiersMatch(modifiers, callback.modifiers)) {

                //当你绑定的组合或序列的第二次它
                //应该覆盖的第一个。如果sequenceName或
                //组合在这个指定的调用它做到了这一点
                //
                //@todo使删除它自己的方法?
                var deleteCombo = !sequenceName && callback.combo == combination;
                var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
                if (deleteCombo || deleteSequence) {
                    self._callbacks[character].splice(i, 1);
                }

                matches.push(callback);
            }
        }

        return matches;
    }

    /**
    * 实际调用回调函数
    *
    *
    *
    *如果您的回调函数返回false,这将使用jQuery的
    * convention - 防止违约和对事件停止传播史
    *
    * @param {Function} callback
    * @param {Event} e
    * @returns void
    */
    function _fireCallback(callback, e, combo, sequence) {

        // //如果此事件不应该发生到此为止
        if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
            return;
        }

        if (callback(e, combo) === false) {
            U.M.StopDefault(e);
            U.M.StopBubble(e);
        }
    }

    /**
    * 处理一个字符键事件
    *
    * @param {string} character
    * @param {Array} modifiers
    * @param {Event} e
    * @returns void
    */
    self._handleKey = function (character, modifiers, e) {
        var callbacks = _getMatches(character, modifiers, e);
        var i;
        var doNotReset = {};
        var maxLevel = 0;
        var processedSequenceCallback = false;

        // 计算maxLevel的序列,所以我们只能执行时间最长的回调序列
        for (i = 0; i < callbacks.length; ++i) {
            if (callbacks[i].seq) {
                maxLevel = Math.max(maxLevel, callbacks[i].level);
            }
        }

        //通过匹配回调回路这一关键事件
        for (i = 0; i < callbacks.length; ++i) {

            // 触发有序回调
            // 这是因为例如,如果您有多个序列
            // 结合诸如“G i”和“G t”,他们都需要触发
            // 回调匹配摹原因,否则你永远只能
            // 匹配到第一个
            if (callbacks[i].seq) {

                //只触发回调的maxLevel防止
                //
                //
                //例如 “a option b' 应该不会造成'option b' 触发
                //即使'option b'为其它序列的一部分
                //
                //这里不匹配任何序列都将被丢弃
                //下面的_resetSequences通话
                if (callbacks[i].level != maxLevel) {
                    continue;
                }

                processedSequenceCallback = true;

                // //保留其中的序列是匹配为以后列表
                doNotReset[callbacks[i].seq] = 1;
                _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
                continue;
            }

            // 如果没有序列相匹配,但我们还在这里
            // 这意味着这是一个普通的匹配,所以我们应该触发了
            if (!processedSequenceCallback) {
                _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
            }
        }

        // if the key you pressed matches the type of sequence without
        // being a modifier (ie "keyup" or "keypress") then we should
        // reset all sequences that were not matched by this event
        //
        // this is so, for example, if you have the sequence "h a t" and you
        // type "h e a r t" it does not match.  in this case the "e" will
        // cause the sequence to reset
        //
        // modifier keys are ignored because you can have a sequence
        // that contains modifiers such as "enter ctrl+space" and in most
        // cases the modifier key will be pressed before the next key
        //
        // also if you have a sequence such as "ctrl+b a" then pressing the
        // "b" key will trigger a "keypress" and a "keydown"
        //
        // the "keydown" is expected when there is a modifier, but the
        // "keypress" ends up matching the _nextExpectedAction since it occurs
        // after and that causes the sequence to reset
        //
        // we ignore keypresses in a sequence that directly follow a keydown
        // for the same character
        //如果你按下键序列无类型相匹配
        //是一个修改(即“KEYUP”或“按键”),那么,我们应该
        //重新设置那些没有此事件相匹配的所有序列
        //
        //是这样,例如,如果你有序列“哈t”和你
        //型“听到T”不匹配。在这种情况下,将“e”的意愿
        //导致序列复位
        //
        //修饰键被忽略,因为你可以有一个序列
        //包含修饰,如“输入Ctrl +空格”,并在最
        //情况下,修改键将在下键之前按下
        //
        //此外,如果你有一个序列,例如“CTRL + B A”,然后按
        //“b”的键将触发一个“按键”和一个“的keydown”
        //
        //在“的keydown”时,有一个改性剂预期,但
        //“按键”结束了,因为它发生_nextExpectedAction匹配
        //之后和使该序列重新设置
        //
        //我们忽略了一个顺序按键直接遵循的keydown
        //对于相同的字符
        var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
        if (e.type == _nextExpectedAction && !U.K._isModifier(character) && !ignoreThisKeypress) {
            _resetSequences(doNotReset);
        }

        _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
    };

    /**
    *处理一个keydown事件
    *
    * @param {Event} e
    * @returns void
    */
    function _handleKeyEvent(e) {

        // 为正常化的关键事件e.which
        if (typeof e.which !== 'number') {
            e.which = e.keyCode;
        }

        var character = U.K._characterFromEvent(e);

        //没有字符发现后停止
        if (!character) {
            return;
        }

        //需要使用===用于字符检查,因为该字符可以是0
        if (e.type == 'keyup' && _ignoreNextKeyup === character) {
            _ignoreNextKeyup = false;
            return;
        }

        self.handleKey(character, U.K._eventModifiers(e), e);
    }

    /**
    * 称为设置1秒超时指定的顺序
    *
    *这是为了让序列中的每个按键后,你长为1秒
    *按下一个键,你必须重新开始之前
    *
    * @returns void
    */
    function _resetSequenceTimer() {
        clearTimeout(_resetTimer);
        _resetTimer = setTimeout(_resetSequences, 1000);
    }

    /**
    * 绑定一个键序列事件
    *
    * @param {string} combo - combo specified in bind call
    * @param {Array} keys
    * @param {Function} callback
    * @param {string=} action
    * @returns void
    */
    function _bindSequence(combo, keys, callback, action) {

        // 通过增加一个序列水平记录这个组合开始
        // 和level设定为0
        _sequenceLevels[combo] = 0;

        /**
        * *回调,以增加该序列的序列水平和复位
        * 处于活动状态的所有其他序列
        *
        * @param {string} nextAction
        * @returns {Function}
        */
        function _increaseSequence(nextAction) {
            return function () {
                _nextExpectedAction = nextAction;
                ++_sequenceLevels[combo];
                _resetSequenceTimer();
            };
        }

        /**
        * 包装指定的回调另一个函数内,以便
        *尽快此序列完成后重置所有序列计数器
        *
        * @param {Event} e
        * @returns void
        */
        function _callbackAndReset(e) {
            _fireCallback(callback, e, combo);

            //我们应该忽略下一个关键了,如果动作键不放
            //或按键。这是如此,如果你完成一个序列
            //松开按键的最后一个关键不会触发KEYUP
            if (action !== 'keyup') {
                _ignoreNextKeyup = U.K._characterFromEvent(e);
            }

            //怪异的竞争条件,如果一个序列与该键结束
            //另一序列始于
            setTimeout(_resetSequences, 10);
        }

        //通过按键一次,并结合适当的回调循环
        //功能。任何关键领导到最后,就应
        //增加的顺序。决赛之后,应该重置所有序列
        //
        //如果在原来的绑定调用指定的操作那么就会
        //在整个使用。否则,我们将通过动作的
        //下一个关键应该匹配。这允许顺序
        //混搭按键和KEYDOWN事件,这取决于
        //的是更好地适合于提供的密钥
        for (var i = 0; i < keys.length; ++i) {
            var isFinal = i + 1 === keys.length;
            var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || U.K._getKeyInfo(keys[i + 1]).action);
            _bindSingle(keys[i], wrappedCallback, action, combo, i);
        }
    }

    /**
    * 绑定一个键盘组合
    *
    * @param {string} combination
    * @param {Function} callback
    * @param {string=} action
    * @param {string=} sequenceName - name of sequence if part of sequence
    * @param {number=} level - what part of the sequence the command is
    * @returns void
    */
    function _bindSingle(combination, callback, action, sequenceName, level) {

        //  //存储与Mousetrap.trigger使用直接映射参考
        self._directMap[combination + ':' + action] = callback;

        // 使连续务必多个空格成为一个空格
        combination = combination.replace(/\s+/g, ' ');

        var sequence = combination.split(' ');
        var info;

        //如果该模式是键的序列然后通过此方法运行
        //重新处理每个模式一键在同一时间
        if (sequence.length > 1) {
            _bindSequence(combination, sequence, callback, action);
            return;
        }

        info = U.K._getKeyInfo(combination, action);

        //确保初始化数组,如果这是第一次
        //回调增加了对这一关键
        self._callbacks[info.key] = self._callbacks[info.key] || [];

        //删除现有的比赛,如果有一个
        _getMatches(info.key, info.modifiers, { type: info.action }, sequenceName, combination, level);

        //后面添加此调用数组
        //如果它是一个序列把它在开始时
        //如果不把它在最后
        //
        //这是重要的,因为这些方式处理预期
        //序列的人是第一位的
        self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({
            callback: callback,
            modifiers: info.modifiers,
            action: info.action,
            seq: sequenceName,
            level: level,
            combo: combination
        });
    }

    /**
    * 结合多种组合到同一个回调
    *
    * @param {Array} combinations
    * @param {Function} callback
    * @param {string|undefined} action
    * @returns void
    */
    self._bindMultiple = function (combinations, callback, action) {
        for (var i = 0; i < combinations.length; ++i) {
            _bindSingle(combinations[i], callback, action);
        }
    };

    // start!
    U.M.AddEvent(targetElement, 'keypress', _handleKeyEvent);
    U.M.AddEvent(targetElement, 'keydown', _handleKeyEvent);
    U.M.AddEvent(targetElement, 'keyup', _handleKeyEvent);
}


U.K.Mousetrap = function () { };
/**
*  / **
*绑定的事件捕鼠器
*
*可以是单一的键,以+分离键的组合,
*键的阵列,或由空格分隔键序列
*
*请务必先列出的组合键,以确保
*正确的密钥最终得到的约束(在模式的最后一个键)
*
* @param {string|Array} keys
* @param {Function} callback
* @param {string=} action - 'keypress', 'keydown', or 'keyup'
* @returns void
*/
U.K.Mousetrap.prototype.bind = function (keys, callback, action) {
    var self = this;
    keys = keys instanceof Array ? keys : [keys];
    self._bindMultiple.call(self, keys, callback, action);
    return self;
};

/**
**
*解除绑定的事件捕鼠器
*
*在解除绑定设置指定组合键的回调函数
*到一个空的功能,并在删除相应的键
* _directMap字典。
*
* TODO:其实从_callbacks词典中删除这个代替
结合一个空函数*
*
*在keycombo +操作必须是完全一样
*它在绑定方法定义
*
* @param {string|Array} keys
* @param {string} action
* @returns void
*/
U.K.Mousetrap.prototype.unbind = function (keys, action) {
    var self = this;
    return self.bind.call(self, keys, function () { }, action);
};

/**
*  *触发器已被绑定的事件
*
* @param {string} keys
* @param {string=} action
* @returns void
*/
U.K.Mousetrap.prototype.trigger = function (keys, action) {
    var self = this;
    if (self._directMap[keys + ':' + action]) {
        self._directMap[keys + ':' + action]({}, keys);
    }
    return self;
};

/**
* *重置库恢复到初始状态。这是非常有用的
*如果您想清除出当前的键盘快捷键和绑定
*新的 - 例如,如果您切换到另一页
* @returns void
*/
U.K.Mousetrap.prototype.reset = function () {
    var self = this;
    self._callbacks = {};
    self._directMap = {};
    return self;
};

/**
*  *我们应该发射了回调之前停止该事件
*
* @param {Event} e
* @param {Element} element
* @return {boolean}
*/
U.K.Mousetrap.prototype.stopCallback = function (e, element) {
    var self = this;

    // if the element has the class "mousetrap" then no need to stop
    if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
        return false;
    }

    if (U.K._getReverseMap(element, self.target)) {
        return false;
    }

    // stop for input, select, and textarea
    return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
};

/**
* 公开暴露_handleKey,因此它可以通过扩展覆盖
*/
U.K.Mousetrap.prototype.handleKey = function () {
    var self = this;
    return self._handleKey.apply(self, arguments);
};

/**
* 允许自定义键映射
*/
U.K.Mousetrap.addKeycodes = function (object) {
    for (var key in object) {
        if (object.hasOwnProperty(key)) {
            U.K._MAP[key] = object[key];
        }
    }
    _REVERSE_MAP = null;
};

/**
* *初始化全球捕鼠器功能
*
*需要使用此方法来允许在全球捕鼠器职能工作
*现在的捕鼠器是一个构造函数。
*/
U.K.Mousetrap.init = function () {
    var documentMousetrap = Mousetrap(document);
    for (var method in documentMousetrap) {
        if (method.charAt(0) !== '_') {
            Mousetrap[method] = (function (method) {
                return function () {
                    return documentMousetrap[method].apply(documentMousetrap, arguments);
                };
            } (method));
        }
    }
};

//#endregion