mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 02:10:08 +00:00
prepared to move to new keyboard handler...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
7eb40b05d1
commit
06bdde0532
@ -392,6 +392,8 @@ module.GLOBAL_KEYBOARD = {
|
||||
},
|
||||
|
||||
'?': 'showKeyboardBindings',
|
||||
|
||||
W: 'testAction',
|
||||
},
|
||||
}
|
||||
|
||||
@ -400,7 +402,12 @@ module.GLOBAL_KEYBOARD = {
|
||||
/*********************************************************************/
|
||||
// + simpler to group bindings
|
||||
// - harder to automate binding creation (e.g. via customScale(..))
|
||||
//
|
||||
|
||||
var keyboard2 = require('lib/keyboard2')
|
||||
|
||||
|
||||
// XXX do we want to add sub-sections to better organize keys for
|
||||
// documentation???
|
||||
var GLOBAL_KEYBOARD2 =
|
||||
module.GLOBAL_KEYBOARD2 = {
|
||||
'Global': {
|
||||
@ -612,6 +619,7 @@ module.GLOBAL_KEYBOARD2 = {
|
||||
ctrl_Left: 'prevScreen',
|
||||
// XXX need to prevent default on mac + browser...
|
||||
meta_Left: 'prevScreen',
|
||||
|
||||
PgDown: 'nextScreen',
|
||||
ctrl_Right: 'nextScreen',
|
||||
// XXX need to prevent default on mac + browser...
|
||||
@ -736,346 +744,17 @@ module.GLOBAL_KEYBOARD2 = {
|
||||
// XXX for debug...
|
||||
//ctrl_G: function(){ $('.viewer').toggleClass('visible-gid') },
|
||||
'?': 'showKeyboardBindings',
|
||||
|
||||
|
||||
W: 'testAction',
|
||||
},
|
||||
}
|
||||
|
||||
// Format:
|
||||
// {
|
||||
// <mode>: {
|
||||
// doc: <doc>,
|
||||
// drop: [ <key>, ... ] | '*',
|
||||
//
|
||||
// <alias>: <handler>,
|
||||
//
|
||||
// <key>: <handler>,
|
||||
// <key>: <alias>,
|
||||
// },
|
||||
// ...
|
||||
// }
|
||||
var Keyboard2HandlerProto = {
|
||||
key_separators: ['+', '-', '_'],
|
||||
modifiers: ['ctrl', 'alt', 'meta', 'shift'],
|
||||
service_fields: ['doc', 'drop'],
|
||||
|
||||
// object/function
|
||||
keyboard: null,
|
||||
// XXX is this needed???
|
||||
context: null,
|
||||
//keyboard = keyboard2
|
||||
//GLOBAL_KEYBOARD = GLOBAL_KEYBOARD2
|
||||
|
||||
// helpers...
|
||||
event2key: function(evt){
|
||||
evt = evt || event
|
||||
|
||||
var key = []
|
||||
evt.ctrlKey && key.push('ctrl')
|
||||
evt.altKey && key.push('alt')
|
||||
evt.metaKey && key.push('meta')
|
||||
evt.shiftKey && key.push('shift')
|
||||
key.push(this.code2key(evt.keyCode))
|
||||
|
||||
return key
|
||||
},
|
||||
key2code: function(key){
|
||||
return key in keyboard._KEY_CODES ?
|
||||
keyboard._KEY_CODES[key]
|
||||
: key.charCodeAt(0) },
|
||||
code2key: function(code){
|
||||
var name = String.fromCharCode(code)
|
||||
return code in keyboard._SPECIAL_KEYS ? keyboard._SPECIAL_KEYS[code]
|
||||
: name != '' ? name
|
||||
: null },
|
||||
shifted: function(key){
|
||||
var output = key instanceof Array ? 'array' : 'string'
|
||||
key = this.normalizeKey(this.splitKey(key)).slice()
|
||||
var k = key.pop()
|
||||
|
||||
var s = (key.indexOf('shift') >= 0 ?
|
||||
keyboard._SHIFT_KEYS[k]
|
||||
: keyboard._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' ? res.join(this.key_separators[0])
|
||||
: res
|
||||
},
|
||||
// XXX handle .key_separators as keys...
|
||||
splitKey: function(key){
|
||||
return key instanceof Array ?
|
||||
key
|
||||
: key
|
||||
//.slice(0, -1)
|
||||
.split(RegExp('['+this.key_separators.join('\\')+']'))
|
||||
//.concat(key.slice(-1))
|
||||
.filter(function(c){ return c != '' }) },
|
||||
normalizeKey: function(key){
|
||||
var output = key instanceof Array ? 'array' : 'string'
|
||||
var modifiers = this.modifiers
|
||||
// sort modifiers via .modifiers and keep the key last...
|
||||
key = this.splitKey(key)
|
||||
.sort(function(a, b){
|
||||
a = modifiers.indexOf(a)
|
||||
b = modifiers.indexOf(b)
|
||||
return a >= 0 && b >= 0 ? a - b
|
||||
: a < 0 ? 1
|
||||
: -1 })
|
||||
key.push(key.pop().capitalize())
|
||||
return output == 'array' ? key : key.join(this.key_separators[0] || '+')
|
||||
},
|
||||
|
||||
/*/ XXX not sure if this is needed...
|
||||
normalizeBindings: function(keyboard){
|
||||
keyboard = keyboard || this.keyboard
|
||||
var that = this
|
||||
var service_fields = this.service_fields
|
||||
Object.keys(keyboard).forEach(function(mode){
|
||||
mode = keyboard[mode]
|
||||
|
||||
Object.keys(mode).forEach(function(key){
|
||||
// skip service fields...
|
||||
if(service_fields.indexOf(key) >= 0){
|
||||
return
|
||||
}
|
||||
|
||||
var n = that.normalizeKey(key)
|
||||
|
||||
if(n != key){
|
||||
// duplicate key...
|
||||
if(n in mode){
|
||||
console.warn('duplicate keys: "'+ n +'" and "'+ k +'"')
|
||||
}
|
||||
|
||||
mode[n] = mode[key]
|
||||
delete mode[key]
|
||||
}
|
||||
})
|
||||
})
|
||||
return keyboard
|
||||
},
|
||||
//*/
|
||||
|
||||
//isModeApplicable: function(mode, context){ return true },
|
||||
|
||||
// get keys for handler...
|
||||
//
|
||||
keys: function(handler){
|
||||
var res = {}
|
||||
var keyboard = this.keyboard
|
||||
|
||||
Object.keys(keyboard).forEach(function(mode){
|
||||
var bindings = keyboard[mode]
|
||||
var keys = Object.keys(bindings)
|
||||
// filter out the handler...
|
||||
.filter(function(key){
|
||||
return handler instanceof Function ?
|
||||
handler(bindings[key])
|
||||
: handler == bindings[key] })
|
||||
// walk aliases...
|
||||
.map(function(key){
|
||||
var seen = []
|
||||
while(bindings[key] in bindings){
|
||||
key = bindings[key]
|
||||
if(seen.indexOf(key) >= 0){
|
||||
return null
|
||||
}
|
||||
seen.push(key)
|
||||
}
|
||||
return key
|
||||
})
|
||||
// clear out the loops from last stage...
|
||||
.filter(function(key){ return !!key })
|
||||
|
||||
if(keys.length > 0){
|
||||
res[mode] = keys
|
||||
}
|
||||
})
|
||||
return res
|
||||
},
|
||||
|
||||
// get/set handler for key...
|
||||
//
|
||||
handler: function(mode, key, handler){
|
||||
var that = this
|
||||
var keyboard = this.keyboard
|
||||
var key_separators = this.key_separators
|
||||
|
||||
key = this.normalizeKey(this.splitKey(key))
|
||||
var shift_key = this.shifted(key)
|
||||
|
||||
// match candidates...
|
||||
var keys = 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) })
|
||||
: [])
|
||||
|
||||
// get modes...
|
||||
var modes = mode == '*' ? Object.keys(keyboard)
|
||||
: mode == 'applicable' || mode == '?' ? this.modes()
|
||||
: mode instanceof Array ? mode
|
||||
: [mode]
|
||||
|
||||
var walkAliases = function(bindings, handler){
|
||||
// walk aliases...
|
||||
var seen = []
|
||||
while(handler in bindings){
|
||||
handler = bindings[handler]
|
||||
|
||||
// check for loops...
|
||||
if(seen.indexOf(handler) >= 0){
|
||||
handler = null
|
||||
break
|
||||
}
|
||||
seen.push(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// get...
|
||||
if(handler === undefined){
|
||||
var res = {}
|
||||
var k = key.slice(-1)[0]
|
||||
var c = this.key2code(k)
|
||||
|
||||
// also test single key and code if everything else fails...
|
||||
keys = keys
|
||||
.concat([ k, c ])
|
||||
.unique()
|
||||
|
||||
var drop = mode == 'applicable' || mode == '?'
|
||||
for(var i=0; i < modes.length; i++){
|
||||
var m = modes[i]
|
||||
|
||||
var bindings = keyboard[m]
|
||||
|
||||
handler = walkAliases(
|
||||
bindings,
|
||||
keys
|
||||
.filter(function(k){ return bindings[k] })[0])
|
||||
|
||||
// handle explicit IGNORE...
|
||||
if(drop && handler == 'IGNORE'){
|
||||
break
|
||||
}
|
||||
|
||||
// we got a match...
|
||||
if(handler){
|
||||
res[m] = handler
|
||||
}
|
||||
|
||||
// if key in .drop then ignore the rest...
|
||||
if(drop
|
||||
&& (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')
|
||||
&& ['*', 'applicable', '?'].indexOf(mode) < 0) ?
|
||||
res[mode]
|
||||
: res
|
||||
|
||||
// set / remove...
|
||||
} else {
|
||||
modes.forEach(function(m){
|
||||
var bindings = keyboard[m]
|
||||
|
||||
// remove all matching keys...
|
||||
keys
|
||||
.unique()
|
||||
.forEach(function(k){
|
||||
delete bindings[k]
|
||||
})
|
||||
|
||||
// set handler if given...
|
||||
if(handler && handler != ''){
|
||||
keyboard[mode][key] = handler
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
// get applicable modes...
|
||||
//
|
||||
modes: function(context){
|
||||
var that = this
|
||||
return Object.keys(this.keyboard)
|
||||
.filter(function(mode){
|
||||
return !that.isModeApplicable
|
||||
|| that.isModeApplicable(mode, context || this.context) }) },
|
||||
}
|
||||
|
||||
|
||||
/*/ XXX for testing...
|
||||
var kb = window.kb = Object.create(Keyboard2HandlerProto)
|
||||
kb.keyboard = GLOBAL_KEYBOARD2
|
||||
kb.isModeApplicable = function(mode, context){
|
||||
var pattern = this.keyboard[mode].pattern
|
||||
return !pattern
|
||||
|| pattern == '*'
|
||||
|| $(this.keyboard[mode].pattern).length > 0
|
||||
}
|
||||
//*/
|
||||
|
||||
|
||||
// XXX this is not compatible with GLOBAL_KEYBOARD use GLOBAL_KEYBOARD2!!
|
||||
function makeKeyboardHandler(keyboard, unhandled, actions){
|
||||
var kb = Object.create(Keyboard2HandlerProto)
|
||||
kb.keyboard = keyboard
|
||||
// XXX this is specific to ImageGrid ...
|
||||
kb.isModeApplicable = function(mode, context){
|
||||
var pattern = this.keyboard[mode].pattern
|
||||
return !pattern
|
||||
|| pattern == '*'
|
||||
|| $(this.keyboard[mode].pattern).length > 0
|
||||
}
|
||||
|
||||
return function(evt){
|
||||
var res
|
||||
var did_handling = false
|
||||
var key = kb.event2key(evt)
|
||||
var handlers = kb.handler('applicable', key)
|
||||
|
||||
Object.keys(handlers).forEach(function(mode){
|
||||
// XXX do we need this???
|
||||
if(res === false){
|
||||
return
|
||||
}
|
||||
|
||||
var h = keyboard.parseActionCall(handlers[mode])
|
||||
|
||||
if(h && h.action in actions){
|
||||
did_handling = true
|
||||
|
||||
h.no_default
|
||||
&& evt.preventDefault()
|
||||
|
||||
// call the handler...
|
||||
res = actions[h.action].apply(actions, h.args)
|
||||
}
|
||||
})
|
||||
|
||||
unhandled
|
||||
&& !did_handling
|
||||
&& unhandled.call(actions, evt)
|
||||
}
|
||||
}
|
||||
window.kb = keyboard2.Keyboard(GLOBAL_KEYBOARD2, keyboard2.checkGlobalMode)
|
||||
|
||||
|
||||
|
||||
@ -1475,7 +1154,7 @@ var KeyboardActions = actions.Actions({
|
||||
// XXX
|
||||
resetKeyBindings: ['Interface/Restore default key bindings',
|
||||
function(){
|
||||
this.__keyboard_config = GLOBAL_KEYBOARD }],
|
||||
thiis.__keyboard_config = GLOBAL_KEYBOARD }],
|
||||
|
||||
// XXX do we look for aliases in this mode only or in all modes?
|
||||
getKeyHandler: ['- Interface/',
|
||||
|
||||
720
ui (gen4)/lib/keyboard2.js
Executable file
720
ui (gen4)/lib/keyboard2.js
Executable file
@ -0,0 +1,720 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
((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', 'alt', 'meta', 'shift' ]
|
||||
|
||||
|
||||
var KEY_SEPARATORS =
|
||||
module.KEY_SEPARATORS = ['+', '-', '_']
|
||||
|
||||
|
||||
// Neither SPECIAL_KEYS nor KEY_CODES are meant for direct access, use
|
||||
// toKeyName(<code>) and toKeyCode(<name>) 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(<code>) or <letter>.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
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
// documentation wrapper...
|
||||
var doc =
|
||||
module.doc =
|
||||
function doc(text, func){
|
||||
func = !func ? function(){return true}: func
|
||||
func.doc = text
|
||||
return func
|
||||
}
|
||||
|
||||
|
||||
// supported action format:
|
||||
// <actio-name>[!][: <args>][-- <doc>]
|
||||
//
|
||||
// <args> can contain space seporated:
|
||||
// - numbers
|
||||
// - strings
|
||||
// - non-nested arrays or objects
|
||||
//
|
||||
// XXX should this be here???
|
||||
// XXX add support for suffix to return false...
|
||||
var parseActionCall =
|
||||
module.parseActionCall =
|
||||
function parseActionCall(txt){
|
||||
// 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(','))+']')
|
||||
|
||||
return {
|
||||
action: action,
|
||||
arguments: args,
|
||||
doc: doc,
|
||||
'no-default': no_default,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
var event2key =
|
||||
module.event2key =
|
||||
function event2key(evt){
|
||||
evt = evt || event
|
||||
|
||||
var key = []
|
||||
evt.ctrlKey && key.push('ctrl')
|
||||
evt.altKey && key.push('alt')
|
||||
evt.metaKey && key.push('meta')
|
||||
evt.shiftKey && key.push('shift')
|
||||
key.push(code2key(evt.keyCode))
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
|
||||
var key2code =
|
||||
module.key2code =
|
||||
function key2code(key){
|
||||
return key in KEY_CODES ? KEY_CODES[key]
|
||||
: key.length > 1 ? null
|
||||
: key.charCodeAt(0) }
|
||||
|
||||
|
||||
var code2key =
|
||||
module.code2key =
|
||||
function code2key(code){
|
||||
var name = String.fromCharCode(code)
|
||||
return code in SPECIAL_KEYS ? SPECIAL_KEYS[code]
|
||||
: name != '' ? name
|
||||
: null }
|
||||
|
||||
|
||||
var isKey =
|
||||
module.isKey =
|
||||
function isKey(key){
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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 != '' }) }
|
||||
|
||||
|
||||
// NOTE: this will not check if a key is a key...
|
||||
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)
|
||||
b = modifiers.indexOf(b)
|
||||
return a >= 0 && b >= 0 ? a - b
|
||||
: a < 0 ? 1
|
||||
: -1 })
|
||||
|
||||
var k = key.pop()
|
||||
k = parseInt(k) ? code2key(parseInt(k)) : k.capitalize()
|
||||
key = key.unique()
|
||||
key.push(k)
|
||||
|
||||
return output == 'array' ?
|
||||
key
|
||||
: key.join(KEY_SEPARATORS[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' ?
|
||||
res.join(KEY_SEPARATORS[0] || '+')
|
||||
: res
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var checkGlobalMode =
|
||||
module.checkGlobalMode =
|
||||
function checkGlobalMode(mode, keyboard, context){
|
||||
var pattern = keyboard[mode].pattern
|
||||
return !pattern
|
||||
|| pattern == '*'
|
||||
|| $(keyboard[mode].pattern).length > 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var KeyboardHandlerClassPrototype = {
|
||||
service_fields: ['doc', 'drop'],
|
||||
|
||||
event2key: event2key,
|
||||
key2code: key2code,
|
||||
code2key: code2key,
|
||||
isKey: isKey,
|
||||
splitKey: splitKey,
|
||||
normalizeKey: normalizeKey,
|
||||
shifted: shifted
|
||||
}
|
||||
|
||||
var KeyboardHandlerPrototype = {
|
||||
//service_fields: ['doc', 'drop'],
|
||||
|
||||
// Format:
|
||||
// {
|
||||
// <mode>: {
|
||||
// doc: <doc>,
|
||||
// drop: [ <key>, ... ] | '*',
|
||||
//
|
||||
// <alias>: <handler>,
|
||||
//
|
||||
// <key>: <handler>,
|
||||
// <key>: <alias>,
|
||||
// },
|
||||
// ...
|
||||
// }
|
||||
__keyboard: null,
|
||||
get keyboard(){
|
||||
return this.__keyboard instanceof Function ?
|
||||
this.__keyboard()
|
||||
: this.__keyboard },
|
||||
set keyboard(value){
|
||||
this.__keyboard = value },
|
||||
|
||||
// XXX is this needed???
|
||||
context: null,
|
||||
|
||||
// utils...
|
||||
event2key: KeyboardHandlerClassPrototype.event2key,
|
||||
key2code: KeyboardHandlerClassPrototype.key2code,
|
||||
code2key: KeyboardHandlerClassPrototype.code2key,
|
||||
shifted: KeyboardHandlerClassPrototype.shifted,
|
||||
splitKey: KeyboardHandlerClassPrototype.splitKey,
|
||||
normalizeKey: KeyboardHandlerClassPrototype.normalizeKey,
|
||||
isKey: KeyboardHandlerClassPrototype.isKey,
|
||||
|
||||
/*/ XXX not sure if this is needed...
|
||||
normalizeBindings: function(keyboard){
|
||||
keyboard = keyboard || this.keyboard
|
||||
var that = this
|
||||
var service_fields = this.service_fields
|
||||
Object.keys(keyboard).forEach(function(mode){
|
||||
mode = keyboard[mode]
|
||||
|
||||
Object.keys(mode).forEach(function(key){
|
||||
// skip service fields...
|
||||
if(service_fields.indexOf(key) >= 0){
|
||||
return
|
||||
}
|
||||
|
||||
var n = that.normalizeKey(key)
|
||||
|
||||
if(n != key){
|
||||
// duplicate key...
|
||||
if(n in mode){
|
||||
console.warn('duplicate keys: "'+ n +'" and "'+ k +'"')
|
||||
}
|
||||
|
||||
mode[n] = mode[key]
|
||||
delete mode[key]
|
||||
}
|
||||
})
|
||||
})
|
||||
return keyboard
|
||||
},
|
||||
//*/
|
||||
|
||||
//isModeApplicable: function(mode, keyboard, context){ return true },
|
||||
//isModeApplicable: checkGlobalMode,
|
||||
|
||||
// get keys for handler...
|
||||
//
|
||||
// NOTE: this will also return non-key aliases...
|
||||
keys: function(handler){
|
||||
var that = this
|
||||
var res = {}
|
||||
var keyboard = this.keyboard
|
||||
var key_separators = KEY_SEPARATORS
|
||||
|
||||
var walkAliases = function(res, rev, bindings, key, mod){
|
||||
mod = mod || []
|
||||
if(key in rev){
|
||||
rev[key].forEach(function(k){
|
||||
k = that.normalizeKey(mod
|
||||
.concat(that.splitKey(k))
|
||||
.unique()
|
||||
.join(key_separators[0]))
|
||||
res.indexOf(k) < 0
|
||||
&& res.push(k)
|
||||
&& walkAliases(res, rev, bindings, k, mod)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(keyboard).forEach(function(mode){
|
||||
var bindings = keyboard[mode]
|
||||
|
||||
// build a reverse index...
|
||||
var rev = {}
|
||||
// XXX this will not work for handlers that are not strings...
|
||||
Object.keys(bindings).forEach(function(key){
|
||||
rev[bindings[key]] = (rev[bindings[key]] || []).concat([key])
|
||||
})
|
||||
|
||||
var keys = (rev[handler] || []).map(that.normalizeKey.bind(that))
|
||||
|
||||
// find all reachable keys from the ones we just found in reverse...
|
||||
keys.slice().forEach(function(key){
|
||||
walkAliases(keys, rev, bindings, key)
|
||||
|
||||
var mod = that.splitKey(key)
|
||||
var k = mod.pop()
|
||||
|
||||
k != key
|
||||
&& walkAliases(keys, rev, bindings, k, mod)
|
||||
})
|
||||
|
||||
if(keys.length > 0){
|
||||
res[mode] = keys
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
},
|
||||
|
||||
// get/set handler for key...
|
||||
//
|
||||
// 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
|
||||
//
|
||||
handler: function(mode, key, handler){
|
||||
var that = this
|
||||
var keyboard = this.keyboard
|
||||
var key_separators = KEY_SEPARATORS
|
||||
|
||||
var genKeys = 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() }
|
||||
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.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 = genKeys(key, shift_key)
|
||||
|
||||
// get modes...
|
||||
var modes = mode == '*' ? Object.keys(keyboard)
|
||||
: mode == 'applicable' || 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 = genKeys(key.slice(0, -1).concat(''))
|
||||
|
||||
// also test single key and code if everything else fails...
|
||||
// XXX make this an option...
|
||||
keys = keys
|
||||
// .concat([k, c])
|
||||
.unique()
|
||||
|
||||
var drop = mode == 'applicable' || 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)
|
||||
}
|
||||
|
||||
// handle explicit IGNORE...
|
||||
if(drop && handler == 'IGNORE'){
|
||||
break
|
||||
}
|
||||
|
||||
// we got a match...
|
||||
if(handler){
|
||||
res[m] = handler
|
||||
}
|
||||
|
||||
// if key in .drop then ignore the rest...
|
||||
if(drop
|
||||
&& (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')
|
||||
&& ['*', 'applicable', '?'].indexOf(mode) < 0) ?
|
||||
res[mode]
|
||||
: res
|
||||
|
||||
// set / remove...
|
||||
} else {
|
||||
modes.forEach(function(m){
|
||||
var bindings = keyboard[m]
|
||||
|
||||
// remove all matching keys...
|
||||
keys
|
||||
.unique()
|
||||
.forEach(function(k){
|
||||
delete bindings[k]
|
||||
})
|
||||
|
||||
// set handler if given...
|
||||
if(handler && handler != ''){
|
||||
keyboard[mode][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__: 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',
|
||||
KeyboardHandlerClassPrototype,
|
||||
KeyboardHandlerPrototype)
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var makeKeyboardHandler =
|
||||
module.makeKeyboardHandler =
|
||||
function makeKeyboardHandler(keyboard, unhandled, actions){
|
||||
|
||||
var kb = keyboard instanceof Keyboard ?
|
||||
keyboard
|
||||
: Keyboard(keyboard, checkGlobalMode)
|
||||
|
||||
return function(evt){
|
||||
var res = undefined
|
||||
var did_handling = false
|
||||
|
||||
var key = kb.event2key(evt)
|
||||
var handlers = kb.handler('applicable', key)
|
||||
|
||||
Object.keys(handlers).forEach(function(mode){
|
||||
if(res === false){
|
||||
return
|
||||
}
|
||||
|
||||
var h = parseActionCall(handlers[mode])
|
||||
|
||||
if(h && h.action in actions){
|
||||
did_handling = true
|
||||
|
||||
h.no_default
|
||||
&& evt.preventDefault()
|
||||
|
||||
// call the handler...
|
||||
res = actions[h.action].apply(actions, h.args)
|
||||
}
|
||||
})
|
||||
|
||||
unhandled
|
||||
&& !did_handling
|
||||
&& unhandled.call(actions, evt)
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// 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 })
|
||||
Loading…
x
Reference in New Issue
Block a user