/********************************************************************** * * * **********************************************************************/ ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ var object = require('lib/object') /*********************************************************************/ var MODIFIERS = //module.MODIFIERS = [ 'ctrl', 'meta', 'alt', 'shift' ] module.MODIFIERS = [ 'caps', 'ctrl', 'meta', 'alt', 'shift' ] var KEY_SEPARATORS = module.KEY_SEPARATORS = ['+', '-', '_'] // Neither SPECIAL_KEYS nor KEY_CODES are meant for direct access, use // toKeyName() and toKeyCode() for a more uniform access. // // NOTE: these are un-shifted ASCII key names rather than actual key // code translations. // NOTE: ASCII letters (capital) are not present because they actually // match their key codes and are accessible via: // String.fromCharCode() or .charCodeAt(0) // NOTE: the lower case letters are accessible by adding 32 to the // capital key code. // NOTE: don't understand why am I the one who has to write this... var SPECIAL_KEYS = module.SPECIAL_KEYS = { // Special Keys... 9: 'Tab', 33: 'PgUp', 45: 'Ins', 13: 'Enter', 34: 'PgDown', 46: 'Del', 16: 'Shift', 35: 'End', 8: 'Backspace', 17: 'Ctrl', 36: 'Home', 91: 'Win', 18: 'Alt', 37: 'Left', 93: 'Menu', 20: 'Caps Lock',38: 'Up', 27: 'Esc', 39: 'Right', 32: 'Space', 40: 'Down', // Function Keys... 112: 'F1', 116: 'F5', 120: 'F9', 113: 'F2', 117: 'F6', 121: 'F10', 114: 'F3', 118: 'F7', 122: 'F11', 115: 'F4', 119: 'F8', 123: 'F12', // Number row.. // NOTE: to avoid conflicts with keys that have a code the same as // the value of a number key... // Ex: // 'Backspace' (8) vs. '8' (56) // 'Tab' (9) vs. '9' (57) // ...all of the numbers start with a '#' // this is a problem due to JS coercing the types to string // on object attr access. // Ex: // o = {1: 2} // o[1] == o['1'] == true 49: '#1', 50: '#2', 51: '#3', 52: '#4', 53: '#5', 54: '#6', 55: '#7', 56: '#8', 57: '#9', 48: '#0', // Punctuation... // top row... 192: '`', /* Numbers */ 189: '-', 187: '=', // right side of keyboard... 219: '[', 221: ']', 220: '\\', 186: ';', 222: '\'', 188: ',', 190: '.', 191: '/', } var SHIFT_KEYS = module.SHIFT_KEYS = { '`': '~', '-': '_', '=':'+', '#1': '!', '#2': '@', '#3': '#', '#4': '$', '#5': '%', '#6':'^', '#7':'&', '#8': '*', '#9': '(', '#0': ')', '[': '{', ']': '}', '\\': '|', ';': ':', '\'': '"', ',': '<', '.': '>', '/': '?' } var UNSHIFT_KEYS = module.UNSHIFT_KEYS = {} for(var k in SHIFT_KEYS){ UNSHIFT_KEYS[SHIFT_KEYS[k]] = k } // build a reverse map of SPECIAL_KEYS var KEY_CODES = module.KEY_CODES = {} for(var k in SPECIAL_KEYS){ KEY_CODES[SPECIAL_KEYS[k]] = k } // This is used to identify and correct key notation... // NOTE: the keys here are intentionally lowercase... var SPECIAL_KEY_ALTERNATIVE_TITLES = { 1: '#1', 2: '#2', 3: '#3', 4: '#4', 5: '#5', 6: '#6', 7: '#7', 8: '#8', 9: '#9', 0: '#0', ctl: 'Ctrl', control: 'Ctrl', 'capslock': 'Caps Lock', 'page up': 'PgUp', 'pageup': 'PgUp', 'page down': 'PgDown', 'pagedown': 'PgDown', insert: 'Ins', delete: 'Del', 'bkspace' : 'Backspace', 'back space' : 'Backspace', windows: 'Win', } var SPECIAL_KEYS_DICT = {} for(var k in SPECIAL_KEYS){ SPECIAL_KEYS_DICT[SPECIAL_KEYS[k].toLowerCase()] = SPECIAL_KEYS[k] } /*********************************************************************/ // Documentation wrapper... var doc = module.doc = function doc(text, func){ func = !func ? function(){return true}: func func.doc = text return func } // Parse action call format... // // supported format: // [!][: ][-- ] // // can contain space seporated: // - numbers // - strings // - non-nested arrays or objects // // XXX EXPERIMENTAL... // This will resolve names to context attributes // // XXX should this be here??? // XXX add support for suffix to return false / stop_propagation... // XXX should this handle calls??? // i.e. have .call(..) / .apply(..) methods??? // XXX this is the same as actions.parseStringAction(..), reuse in a logical manner... var parseActionCall = module.parseActionCall = function parseActionCall(txt, context){ context = context || this // split off the doc... var c = txt.split('--') var doc = (c[1] || '').trim() // the actual code... c = c[0].split(':') // action and no default flag... var action = c[0].trim() var no_default = action.slice(-1) == '!' action = no_default ? action.slice(0, -1) : action // parse arguments... /* var args = JSON.parse('['+( ((c[1] || '') .match(/"[^"]*"|'[^']*'|\{[^\}]*\}|\[[^\]]*\]|\d+|\d+\.\d*|null/gm) || []) .join(','))+']') //*/ // XXX EXPERIMENTAL -- is this safe??? var args = ((c[1] || '') .match(RegExp([ '"[^"]*"', "'[^']*", '`[^`]*`', '\\{[^\\}]*\\}', '\\[[^\\]]*\\]', '\\d+|\\d+\\.\\d*', '[a-zA-Z$@#_][0-9a-zA-Z$@#_]*', 'null', ].join('|'), 'gm')) || []) .map(function(e){ // resolve vars to context attrs... return /^[a-zA-Z$@#_][0-9a-zA-Z$@#_]*$/.test(e) ? (context || {})[e] : JSON.parse(e) }) return { action: action, arguments: args, doc: doc, no_default: no_default, stop_propagation: false, } } //--------------------------------------------------------------------- // Helpers and utility functions... // Form standard key string from keyboard event... // // Format: // "[ctrl+][meta+][alt+][shift+]" // // - string returned by code2key(..) // var event2key = module.event2key = function event2key(evt){ evt = evt || event // NOTE: we do not care about the jQuery wrapper here... evt = evt.originalEvent || evt var key = [] evt.ctrlKey && key.push('ctrl') evt.altKey && key.push('alt') evt.metaKey && key.push('meta') evt.shiftKey && key.push('shift') evt.getModifierState && evt.getModifierState('CapsLock') && key.push('caps') var k = code2key(evt.keyCode) // add the key if it's not already in, this can happen if we just // pressed a modifier key... key.indexOf(k.toLowerCase()) < 0 && key.push(k) return key } // Get key code from key name... var key2code = module.key2code = function key2code(key){ return key in KEY_CODES ? KEY_CODES[key] : key.length > 1 ? null : key.charCodeAt(0) } // Get key name from key code... var code2key = module.code2key = function code2key(code){ var name = String.fromCharCode(code) return code in SPECIAL_KEYS ? SPECIAL_KEYS[code] : name != '' ? name : null } // Check if string is a standard key string... var isKey = module.isKey = function isKey(key){ if(!key || key.length == 0 || key.trim() == ''){ return false } var modifiers = MODIFIERS var mod = normalizeKey(splitKey(key)) var k = mod.pop() // key is either a key code or a valid key name... return (!!parseInt(k) || key2code(k) != null) // mod must be a subset of modifiers... && mod.filter(function(m){ return modifiers.indexOf(m) < 0 }).length == 0 } // Split key... // // NOTE: if this gets an array, it will get returned as-is... // NOTE: no checks are made on the key, use isKey(..) in conjunction // with normalizeKey(..) for checking... var splitKey = module.splitKey = function splitKey(key){ var sep = KEY_SEPARATORS return key instanceof Array ? key : typeof(key) == typeof(123) ? [key] : key .split(RegExp('[' +sep.join('\\') +']')) .concat(sep.indexOf(key.slice(-1)) >= 0 ? key.slice(-1) : []) .filter(function(c){ return c != '' }) } var joinKey = module.joinKey = function joinKey(key){ return key instanceof Array ? key.join(KEY_SEPARATORS[0] || '+') : key } // Normalize key string/array... // // NOTE: this will not check if a key is a key use isKey(..) for that. var normalizeKey = module.normalizeKey = function normalizeKey(key){ var output = key instanceof Array ? 'array' : 'string' var modifiers = MODIFIERS // special case: got a number... if(typeof(key) == typeof(123)){ return code2key(key) } // sort modifiers via .modifiers and keep the key last... key = splitKey(key) .slice() .sort(function(a, b){ a = modifiers.indexOf(a.toLowerCase()) b = modifiers.indexOf(b.toLowerCase()) return a >= 0 && b >= 0 ? a - b : a < 0 ? 1 : -1 }) var k = key.pop() k = parseInt(k) ? code2key(parseInt(k)) : k if(!k){ return k } // get the propper name... k = SPECIAL_KEY_ALTERNATIVE_TITLES[k.toLowerCase()] || k k = SPECIAL_KEYS_DICT[k.toLowerCase()] || k k = modifiers.indexOf(k.toLowerCase()) >= 0 ? k.toLowerCase() : k.capitalize() key.push(k) key = key.unique() return output == 'array' ? key : joinKey(key) } // Get shifted key if available... // // Examples: // - '{' -> 'shift+[' // - ')' -> 'shift+#0' var shifted = module.shifted = function shifted(key){ var output = key instanceof Array ? 'array' : 'string' key = normalizeKey(splitKey(key)).slice() var k = key.pop() var s = (key.indexOf('shift') >= 0 ? SHIFT_KEYS[k] : UNSHIFT_KEYS[k]) || null var res = s == null ? key : (key.indexOf('shift') >= 0 ? key.filter(function(k){ return k != 'shift' }) : key.concat(['shift'])) res.push(s) return s == null ? null : output == 'string' ? joinKey(res) : res } /*********************************************************************/ // Generic keyboard handler... // // Key binding format: // { // : { // doc: , // // // list of keys to drop after this section is done. // // // // Setting this to '*' will drop all keys... // // // // NOTE: these keys will be handled in current section. // // NOTE: these keys will not get propagated to the next // // matching section... // // NOTE: it is possible to override this and explicitly pass // // a key to the next section via 'NEXT' (see below). // drop: [ , ... ] | '*', // // // Key mapped to action... // // // // NOTE: the system poses no restrictions on action format, // // but it is recommended to stick to strings or use the // // doc(..) wrapper... // : , // // // Key mapped to an alias... // // // // An alias is any string that is also a key in bindings, it // // can be just a string or a key, when matching the string of // // aliases will be resolved till either an action (non-alias) // // is found or a loop is detected. // // // // NOTE: in case of a loop, nothing will get called... // : | , // // // Alias-action mapping... // : , // // // Explicitly drop key... // // // // NOTE: this is similar in effect to .drop // : 'DROP', // // // Explicitly pass key to next section... // // // // This can be useful when it is needed to drop all keys // // except for a small sub-group, this can be dune by setting // // .drop to '*' (drop all) and explicitly setting the keys to // // be propagated to 'NEXT'. // // // // NOTE: his takes precedence over .drop // : 'NEXT', // // ... // }, // ... // } // var KeyboardClassPrototype = { service_fields: ['doc', 'drop'], event2key: event2key, key2code: key2code, code2key: code2key, isKey: isKey, splitKey: splitKey, joinKey: joinKey, normalizeKey: normalizeKey, shifted: shifted } var KeyboardPrototype = { //service_fields: ['doc', 'drop'], special_handlers: { DROP: 'drop key', NEXT: 'handle key in next section', }, // Format: // { // : { // doc: , // drop: [ , ... ] | '*', // // : , // // : , // : , // }, // ... // } // // Reserved special handlers: // - DROP - drop checking of key // NOTE: this will prevent handling next sections // for this key. // - NEXT - force check next section, this has priority // over .drop // // NOTE: if .__keyboard is set to a function, it will be used both as // a getter and as a setter via the .keyboard prop, to overwrite // write to .__keyboard directly... __keyboard: null, get keyboard(){ return this.__keyboard instanceof Function ? this.__keyboard() : this.__keyboard }, set keyboard(value){ if(this.__keyboard instanceof Function){ this.__keyboard(value) } else { this.__keyboard = value } }, // XXX is this needed??? //context: null, // string handler parser... // // Return format: // { // action: , // arguments: , // doc: || null, // no_default: , // stop_propagation: , // } // // XXX should this be a Keyboard thing or a context thing??? // XXX revise name... parseStringHandler: parseActionCall, // utils... event2key: KeyboardClassPrototype.event2key, key2code: KeyboardClassPrototype.key2code, code2key: KeyboardClassPrototype.code2key, shifted: KeyboardClassPrototype.shifted, splitKey: KeyboardClassPrototype.splitKey, joinKey: KeyboardClassPrototype.joinKey, normalizeKey: KeyboardClassPrototype.normalizeKey, isKey: KeyboardClassPrototype.isKey, //isModeApplicable: function(mode, keyboard, context){ return true }, // XXX merge configs... // - need to match and order groups (use 1'st as reference)... // - need to create new set w/o changing the originals... merge: function(){ }, // Sort modes... // // Sort via cmp function... // .sortModes(func) // -> this // // Sort to the same order as list... // .sortModes(list) // -> this // // // NOTE: calling this with no arguments will have no effect. // // XXX should this update the kb in-place??? sortModes: function(cmp){ var ordered = {} var bindings = this.keyboard if(cmp == null){ return } cmp = cmp instanceof Function ? Object.keys(bindings).sort(cmp) : cmp .concat(Object.keys(bindings)) .unique() cmp .forEach(function(mode){ ordered[mode] = bindings[mode] }) // reorder only if we moved all the modes... if(Object.keys(bindings).length == Object.keys(ordered).length){ this.keyboard = ordered } return this }, // Get keys for handler... // // List all handlers... // .keys() // .keys('*') // -> keys // // List only applicable handlers... // .keys('?') // -> keys // // List keys for handler... // .keys(handler) // -> keys // // List keys for given handlers... // .keys(handler, ...) // .keys([handler, ...]) // -> keys // // List keys for handlers that pass the func predicate... // .keys(func) // -> keys // // // Format: // { // : { // : [ , ... ], // ... // }, // ... // } // // // NOTE: this will also return non-key aliases... // NOTE: to match several compatible handlers, pass a list of handlers, // the result for each will be merged into one common list. // // XXX drop/DROP/NEXT handling need more testing... // XXX this and .handler(..) in part repeat handling dropped keys, // can we unify this??? keys: function(handler){ var that = this var res = {} var keyboard = this.keyboard var key_separators = KEY_SEPARATORS var service_fields = this.service_fields || this.constructor.service_fields handler = arguments.length > 1 ? [].slice.call(arguments) : handler == null ? '*' : handler == '*' || handler == '?' || handler instanceof Function ? handler : handler instanceof Array ? handler : [handler] var walkAliases = function(res, rev, bindings, key, mod){ mod = mod || [] if(key in rev){ rev[key].forEach(function(k){ k = that.normalizeKey( that.joinKey(mod .concat(that.splitKey(k)) .unique())) res.indexOf(k) < 0 && res.push(k) && walkAliases(res, rev, bindings, k, mod) }) } } var modes = handler == '?' ? this.modes() : '*' var drop = [] var next = [] Object.keys(keyboard).forEach(function(mode){ // skip non-applicable modes... if(modes != '*' && modes.indexOf(mode) < 0){ return } var bindings = keyboard[mode] if(handler == '?'){ next = next.concat(bindings.NEXT || []) } // build a reverse index... var rev = {} // NOTE: this will not work for handlers that are not strings // and have no .doc... Object.keys(bindings) .filter(function(key){ return service_fields.indexOf(key) < 0 }) .forEach(function(key){ var h = bindings[key] h = typeof(h) == typeof('str') ? h : (h.doc || h.name) rev[h] = (rev[h] || []) .concat((rev[bindings[key]] || []).concat([key])) .unique() }) var seen = [] var handlers = handler == '*' || handler == '?' ? Object.keys(rev) : handler instanceof Function ? Object.keys(rev) .filter(handler) : handler handlers .forEach(function(h){ if(handler == '?'&& h == 'NEXT'){ return } var keys = (rev[h] || []).map(that.normalizeKey.bind(that)) if(handler == '?' && h == 'DROP'){ drop = drop == '*' ? '*' : drop.concat(keys) next = next .filter(function(k){ return keys.indexOf(k) >= 0 }) return } var keys = (rev[h] || []).map(that.normalizeKey.bind(that)) // find all reachable keys from the ones we just found in reverse... keys.slice() .filter(function(key){ return seen.indexOf(key) < 0 }) .forEach(function(key){ // direct aliases... walkAliases(keys, rev, bindings, key) var mod = that.splitKey(key) var k = mod.pop() // aliases with modifiers... k != key && walkAliases(keys, rev, bindings, k, mod) seen.push(seen) }) if(handler == '?'){ keys = keys .filter(function(key){ var k = that.splitKey(key) return next.indexOf(key) >= 0 || next.indexOf(k) >= 0 || (drop != '*' && drop.indexOf(key) < 0 && drop.indexOf(k) < 0) }) } if(keys.length > 0){ var m = res[mode] = res[mode] || {} m[h] = keys } }) if(handler == '?'){ drop = drop == '*' || bindings.drop == '*' ? '*' : drop.concat(bindings.drop || []) } }) return res }, // Get/set/unset handler for key... // // In general if handler is not passed this will get the handlers, // if a handler is given this will set the handler, if the passed // handler is either null or '' then it will be unbound. // // Get handler for key in all modes... // .handler(key) // .handler('*', key) // -> key-spec // // Get handlers for key in applicable modes... // .handler('?', key) // .handler('test', key) // -> key-spec // // Get handler for key in a specific mode... // .handler(mode, key) // -> key-spec // // Get handler for key in a specific list of modes... // .handler([mode, ..], key) // -> key-spec // // // Bind handler to key in specific mode... // .handler(mode, key, handler) // -> this // // Bind handler to key in all modes... // .handler('*', key, handler) // -> this // // Bind handler to key in applicable modes... // .handler('?', key, handler) // .handler('test', key, handler) // -> this // // Bind handler to key in a specific list of modes... // .handler([mode, ..], key, handler) // -> this // // // Unbind handler from key in specific mode... // .handler(mode, key, null) // .handler(mode, key, '') // -> this // // Unbind handler from key in all modes... // .handler('*', key, null) // .handler('*', key, '') // -> this // // Unbind handler from key in applicable modes... // .handler('?', key, null) // .handler('?', key, '') // .handler('test', key, null) // .handler('test', key, '') // -> this // // Unbind handler from key in a specific list of modes... // .handler([mode, ..], key, null) // .handler([mode, ..], key, '') // -> this // // // Format: // { // : , // ... // } // // // Search order: // - search for full key // - search for shifted key if applicable // - search for key without modifiers // - if an alias is found it is first checked with and then // without modifiers // - search for key code without modifiers // - if an alias is found it is first checked with and then // without modifiers // // XXX this and .keys('?') in part repeat handling dropped keys, // can we unify this??? handler: function(mode, key, handler){ var that = this var keyboard = this.keyboard var key_separators = KEY_SEPARATORS if(mode == null){ return null } if(key == null && this.isKey(mode)){ key = mode mode = '*' } var joinKeys = function(key, shift_key){ // match candidates... return key_separators // full key... .map(function(s){ return key.join(s) }) // full shift key... .concat(shift_key ? key_separators .map(function(s){ return shift_key.join(s) }) : []) .unique() } // NOTE: the generated list is in the following order: // - longest chain first // - shifted keys first // - modifiers are skipped in order, left to right // XXX carefully revise key search order... var keyCombinations = function(key, shift_key, remove_single_keys){ if(key.length <= 1){ //return shift_key ? [key, shift_key] : [key] return key } var k = remove_single_keys ? 1 : 0 // generate recursively, breadth first... var _combinations = function(level, res){ var next = [] level .map(function(elem){ var c = elem.join('+++') res.indexOf(c) < 0 && res.push(c) && elem .slice(0, -1) .map(function(_, i){ var s = elem.slice() s.splice(i, 1) // NOTE: we do not include single keys // as they are searched separately... //s.length > 0 s.length > k && next.push(s) }) }) next.length > 0 && _combinations(next, res) return res } return _combinations(shift_key && shift_key.length > 0 ? [key, shift_key] : [key], []) .map(function(e){ return joinKeys(e.split(/\+\+\+/g)) }) .reduce(function(a, b){ return a.concat(b) }, []) } var walkAliases = function(bindings, handler, modifiers){ var seen = [] var modifiers = modifiers || [] while(handler in bindings){ handler = bindings[handler] handler = modifiers .filter(function(m){ return handler instanceof Function || (handler.indexOf(m) < 0 && seen.indexOf(m+handler) < 0 && m+handler in bindings) }) .map(function(m){ return m+handler })[0] || handler // check for loops... if(seen.indexOf(handler) >= 0){ handler = null break } seen.push(handler) } return handler } key = this.normalizeKey(this.splitKey(key)) var shift_key = this.shifted(key) // match candidates... //var keys = joinKeys(key, shift_key).unique() // NOTE: we are skipping single keys from list as they are searched // separately... var keys = keyCombinations(key, shift_key, true) // XXX //console.log(keys, '--', joinKeys(key, shift_key).unique()) // get modes... var modes = mode == '*' ? Object.keys(keyboard) : mode == 'test' || mode == '?' ? this.modes() : mode instanceof Array ? mode : [mode] // get... if(handler === undefined){ var res = {} var k = key.slice(-1)[0] var c = this.key2code(k) //var mod = joinKeys(key.slice(0, -1).concat('')) var mod = keyCombinations(key.slice(0, -1).concat(''), null, true) var drop = mode == 'test' || mode == '?' for(var i=0; i < modes.length; i++){ var m = modes[i] var bindings = keyboard[m] // stage 1: check key aliases with modifiers... handler = walkAliases( bindings, keys.filter(function(k){ return bindings[k] })[0]) // stage 2: check raw key aliases with and without modifiers... if(!handler){ handler = walkAliases( bindings, [k, c].filter(function(k){ return bindings[k] })[0], mod) } // explicit DROP -- ignore next sections... if(drop && handler == 'DROP'){ break } // we got a match... if(handler){ res[m] = handler } // if key in .drop then ignore the rest... if(drop // explicit go to next section... && (!handler || (typeof(handler) == typeof('str') && handler.slice(0, 4) != 'NEXT')) && (bindings.drop == '*' // XXX should this be more flexible by adding a // specific key combo? // ... if yes, we'll need to differentiate // between X meaning drop only X and drop // all combos with X... || (bindings.drop || []).indexOf(k) >= 0)){ break } } return (typeof(mode) == typeof('str') && ['*', 'test', '?'].indexOf(mode) < 0) ? res[mode] : res // set / remove... } else { modes.forEach(function(m){ var bindings = keyboard[m] // remove all matching keys... // NOTE: if key with modifiers, then this will remove only // the full matched keys and shifted matches but will // leave the key without modifiers as-is... keys.forEach(function(k){ delete bindings[k] }) // set handler if given... if(handler && handler != ''){ keyboard[mode][that.joinKey(key)] = handler } }) } return this }, // get applicable modes... // modes: function(context){ var that = this return that.isModeApplicable ? Object.keys(this.keyboard) .filter(function(mode){ return that.isModeApplicable( mode, that.keyboard, context || that.context) }) : Object.keys(this.keyboard) }, // init base data... __init__: function(keyboard, is_mode_applicable){ this.keyboard = keyboard if(is_mode_applicable instanceof Function){ this.isModeApplicable = is_mode_applicable } }, } var Keyboard = module.Keyboard = object.makeConstructor('Keyboard', KeyboardClassPrototype, KeyboardPrototype) //--------------------------------------------------------------------- // Keyboard handler with modes identified by CSS selectors... var KeyboardWithCSSModesPrototype = { service_fields: ['doc', 'drop', 'pattern'], isModeApplicable: function(mode, keyboard, context){ var pattern = keyboard[mode].pattern || mode context = context || this.context return !pattern || pattern == '*' // XXX can we join these into one search??? || context.is(pattern) || context.find(pattern).length > 0 }, __init__: function(keyboard, context){ object.superMethod(KeyboardWithCSSModes, '__init__').call(this, keyboard) if(context instanceof Function){ Object.defineProperty(this, 'context', { get: context, }) } else { this.context = context } }, } var KeyboardWithCSSModes = module.KeyboardWithCSSModes = object.makeConstructor('KeyboardWithCSSModes', KeyboardClassPrototype, KeyboardWithCSSModesPrototype) // inherit from Keyboard... KeyboardWithCSSModes.prototype.__proto__ = Keyboard.prototype /*********************************************************************/ // Base event handler wrapper of Keyboard... // // This will produce a handler that can be used in one of two ways: // - event handler // - an event is passed as the only argument // - the function can be used directly as an event handler // - direct key handler // - a key and optionally a no_match handler are passed // // Example: // var handler = makeKeyboardHandler(kb, null, action) // // // event handler... // $(window).keydown(handler) // // // used directly... // handler('ctrl_C', function(k){ console.log('Not bound:', k) }) // // NOTE: the handler will also set the .capslock attribute on the // keyboard object and update it on each key press... // NOTE: if .capslock is false means that either it is not on or // undetectable... // NOTE: before any key is pressed the .capslock is set to undefined // // XXX not sure if handler calling mechanics should be outside of the // Keyboard object... var makeKeyboardHandler = module.makeKeyboardHandler = function makeKeyboardHandler(keyboard, unhandled, actions){ var kb = keyboard instanceof Keyboard ? keyboard //: Keyboard(keyboard, checkGlobalMode) : Keyboard(keyboard) kb.capslock = undefined return function(key, no_match){ no_match = no_match || unhandled var did_handling = false var evt = event var res //if(key instanceof Event || key instanceof $.Event){ if(typeof(key) != typeof('str')){ evt = key key = kb.event2key(evt) kb.capslock = key.indexOf('caps') >= 0 } var handlers = kb.handler('test', key) Object.keys(handlers).forEach(function(mode){ if(res === false){ return } var handler = handlers[mode] // raw function handler... if(handler instanceof Function){ res = handler.call(actions) // action call syntax... // XXX should this be a Keyboard thing or a context thing??? } else if(actions.parseStringHandler || kb.parseStringHandler){ //} else if(kb.parseStringHandler){ var h = (actions.parseStringHandler || kb.parseStringHandler)(handler, actions) //var h = kb.parseStringHandler(handler) if(h && h.action in actions){ did_handling = true evt && h.no_default && evt.preventDefault() // call the handler... res = actions[h.action].apply(actions, h.arguments) evt && h.stop_propagation && evt.stopPropagation() && (res = false) } } }) no_match && !did_handling && no_match.call(actions, evt, key) return res } } //--------------------------------------------------------------------- // Pausable base event handler wrapper of Keyboard... // // This is the same as .makeKeyboardHandler(..) but adds ability to // pause repeating key handling... // // This will extend the keyboard object by adding: // .pauseRepeat() - will pause repeating keys... // // XXX should we drop only when the same key is repeating or any keys // repeating (as is now)??? var makePausableKeyboardHandler = module.makePausableKeyboardHandler = function makePausableKeyboardHandler(keyboard, unhandled, actions, check_interval){ var kb = keyboard instanceof Keyboard ? keyboard //: Keyboard(keyboard, checkGlobalMode) : Keyboard(keyboard) kb.pauseRepeat = function(){ this.__repeat_pause_timeout && clearTimeout(this.__repeat_pause_timeout) this.__repeat_pause_timeout = setTimeout( function(){ delete kb.__repeat_pause_timeout }, (check_interval instanceof Function ? check_interval.call(actions) : (check_interval || 100))) } return stoppableKeyboardRepeat( makeKeyboardHandler(kb, unhandled, actions), function(){ if(kb.__repeat_pause_timeout){ kb.pauseRepeat() return false } return true }) } //--------------------------------------------------------------------- // handler wrappers... // Event handler wrapper to stop handling keys if check callback does // not pass (returns false)... // var stoppableKeyboardRepeat = module.stoppableKeyboardRepeat = function(handler, check){ return function(evt){ return check() && handler(evt) } } // Event handler wrapper that will drop identical keys repeating at rate // greater than max_rate // // NOTE: this will only limit repeating key combinations thus no lag is // introduced... var dropRepeatingkeys = module.dropRepeatingkeys = function dropRepeatingkeys(handler, max_rate){ var _timeout = null var key = null var ctrl = null var meta = null var alt = null var shift = null return function(evt){ if(_timeout != null && key == evt.keyCode && ctrl == evt.ctrlKey && meta == evt.metaKey && alt == evt.altKey && shift == evt.shiftKey){ return } key = evt.keyCode ctrl = evt.ctrlKey meta = evt.metaKey alt = evt.altKey shift = evt.shiftKey _timeout = setTimeout(function(){ _timeout = null }, // XXX is this the right way to go??? typeof(max_rate) == typeof(123) ? max_rate : max_rate()) return handler(evt) } } /********************************************************************** * vim:set ts=4 sw=4 : */ return module })