diff --git a/index.html b/index.html index 0e9ddf8..cde3bb6 100755 --- a/index.html +++ b/index.html @@ -343,18 +343,21 @@ var KEYBOARD_CONFIG = { Home: firstPage, End: lastPage, Left: { - default: prevPage, + default: function(){ prevPage() }, + shift: prevBookmark, ctrl: prevArticle, }, Right: { - default: nextPage, + default: function(){ nextPage() }, + shift: nextBookmark, ctrl: nextArticle, }, Space: { - default: nextPage, - shift: prevPage + default: 'Right', + shift: 'Left' }, - Tab: 'Space', + //Tab: 'Space', + Tab: function(){ return false }, Enter: function(){ togglePageView('on') }, // combined navigation with actions.. Up: function(){ togglePageView() }, @@ -362,7 +365,7 @@ var KEYBOARD_CONFIG = { F: function(){ togglePageFitMode() }, B: { - default: toggleBookmark, + default: function(){ toggleBookmark() }, ctrl: function(){ toggleThemes() }, }, @@ -386,8 +389,8 @@ $(document).ready(function(){ // keyboard... $(document) - .keydown(makeKeyboardHandler(KEYBOARD_CONFIG, - function(k){console.log(k)})) + .keydown(makeKeyboardHandler(KEYBOARD_CONFIG, + function(k){console.log(k)})) window.MagazineScroller = makeScrollHandler($('.viewer'), { hScroll: true, diff --git a/lib/keyboard.js b/lib/keyboard.js index 13cede6..5ac38d6 100755 --- a/lib/keyboard.js +++ b/lib/keyboard.js @@ -25,7 +25,7 @@ var _SPECIAL_KEYS = { // Special Keys... 9: 'Tab', 33: 'PgUp', 45: 'Ins', 13: 'Enter', 34: 'PgDown', 46: 'Del', - 16: 'Shift', 35: 'End', 80: 'Backspace', + 16: 'Shift', 35: 'End', 8: 'Backspace', 17: 'Ctrl', 36: 'Home', 91: 'Win', 18: 'Alt', 37: 'Left', 93: 'Menu', 20: 'Caps Lock',38: 'Up', @@ -39,8 +39,19 @@ var _SPECIAL_KEYS = { 115: 'F4', 119: 'F8', 123: 'F12', // Number row.. - 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', - 54: '6', 55: '7', 56: '8', 57: '9', 48: '0', + // 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... @@ -51,6 +62,20 @@ var _SPECIAL_KEYS = { 188: ',', 190: '.', 191: '/', } + +var _SHIFT_KEYS = { + '`': '~', '-': '_', '=':'+', + + 1: '!', 2: '@', 3: '#', 4: '$', 5: '%', + 6:'^', 7:'&', 8: '*', 9: '(', 0: ')', + + '[': '{', ']': '}', '\\': '|', + ';': ':', '\'': '"', + ',': '<', '.': '>', '/': '?' +} + + +// build a reverse map of _SPECIAL_KEYS var _KEY_CODES = {} for(var k in _SPECIAL_KEYS){ _KEY_CODES[_SPECIAL_KEYS[k]] = k @@ -73,6 +98,7 @@ function toKeyName(code){ return null } + function toKeyCode(c){ if(c in _KEY_CODES){ return _KEY_CODES[c] @@ -81,8 +107,203 @@ function toKeyCode(c){ } -// if set to false the event handlers will always return false... -var KEYBOARD_HANDLER_PROPAGATE = true +// documentation wrapper... +function doc(text, func){ + func = func == null ? function(){return true}: func + func.doc = text + return func +} + + +/* Key handler getter + * + * For doc on format see makeKeyboardHandler(...) + * + * modes can be: + * - 'any' (default) - Get list of all applicable handlers up until + * the first applicable ignore. + * - 'all' - Get ALL handlers, including ignores + * - - Get handlers for an explicit mode + * + * modifiers can be: + * - '' (default) - No modifiers + * - '?' - Return list of applicable modifiers per mode + * - - Any of 'ctrl', 'alt' or 'shift' alone or in + * combination. + * Combinations MUST be ordered as shown above. + * Combination elements are separated by '+' + * Ex: + * 'ctrl+shift' + * NOTE: 'shift+ctrl' is wrong. + * + * This will also resolve several shifted keys by name, for example: + * 'shift-/' is the same as '?', and either can be used, but the shorter + * direct notation has priority (see _SHIFT_KEYS for supported keys). + * + * + * Returns: + * { + * : , + * ... + * } + * + * + * NOTE: it is not possible to do a shift-? as it is already shifted. + * NOTE: if a key is not handled in a mode, that mode will not be + * present in the resulting object. + * NOTE: this will not unwrap lisp-style (see below) handlers. + * NOTE: modes are prioritized by order of occurrence. + * + * XXX need an explicit way to prioritize modes, avoiding object attr + * ordering... + * XXX check do we need did_handling here... + * + * XXX BUG explicitly given modes do not yield results if the pattern + * does not match... + */ +function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){ + var chr = null + var s_chr = null + var did_handling = false + modifiers = modifiers == null ? '' : modifiers + modes = modes == null ? 'any' : modes + shifted_keys = shifted_keys == null ? _SHIFT_KEYS : shifted_keys + + if(typeof(key) == typeof(123)){ + key = key + chr = toKeyName(key) + } else { + chr = key + key = toKeyCode(key) + } + + // XXX this is not done yet... + if(shifted_keys != false && /shift/i.test(modifiers)){ + var s_chr = shifted_keys[chr] + } + + res = {} + + for(var mode in keybindings){ + + // test for mode compatibility... + // XXX this fails for explicitly given mode... + if(modes != 'all' + && (modes != 'any' + && modes != mode + || $(mode).length == 0)){ + continue + } + + var bindings = keybindings[mode] + + if(s_chr != null && s_chr in bindings){ + var handler = bindings[s_chr] + chr = s_chr + modifiers = modifiers.replace(/\+?shift/i, '') + } else if(chr in bindings){ + var handler = bindings[chr] + } else { + var handler = bindings[key] + } + + // alias... + while( handler != null + && (typeof(handler) == typeof(123) + || typeof(handler) == typeof('str') + || typeof(handler) == typeof({}) + && handler.constructor.name == 'Object') ){ + + // do the complex handler aliases... + if(typeof(handler) == typeof({}) && handler.constructor.name == 'Object'){ + // build modifier list... + if(modifiers == '?'){ + break + } + if(modifiers in handler){ + if(typeof(handler[modifiers]) == typeof('str')){ + handler = handler[modifiers] + } else { + break + } + } else if(typeof(handler['default']) == typeof('str')){ + handler = handler['default'] + } else { + break + } + } + + // simple handlers... + if(handler in bindings){ + // XXX need to take care of that we can always be a number or a string... + handler = bindings[handler] + } else if(typeof(handler) == typeof(1)) { + handler = bindings[toKeyName(handler)] + } else { + handler = bindings[toKeyCode(handler)] + } + } + + // no handler... + if(handler == null){ + // if something is ignored then just breakout and stop handling... + if(bindings.ignore == '*' + || bindings.ignore != null + && (bindings.ignore.indexOf(key) != -1 + || bindings.ignore.indexOf(chr) != -1)){ + did_handling = true + // ignoring a key will stop processing it... + if(modes == 'all' || mode == modes){ + res[mode] = 'IGNORE' + } else { + break + } + } + continue + } + + // complex handler... + if(typeof(handler) == typeof({}) && handler.constructor.name == 'Object'){ + // build modifier list... + if(modifiers == '?'){ + res[mode] = Object.keys(handler) + did_handling = true + continue + } + + var callback = handler[modifiers] + if(callback == null){ + callback = handler['default'] + } + + if(callback != null){ + res[mode] = callback + + did_handling = true + continue + } + + // simple callback... + } else { + // build modifier list... + if(modifiers == '?'){ + res[mode] = 'none' + } else { + res[mode] = handler + } + + did_handling = true + continue + } + + if(modes != 'all' && did_handling){ + break + } + } + + return res +} + /* Basic key binding format: * @@ -95,14 +316,29 @@ var KEYBOARD_HANDLER_PROPAGATE = true * // this defines the list of keys to ignore by the handler. * // NOTE: use "*" to ignore all keys other than explicitly * // defined in the current section. + * // NOTE: ignoring a key will stop processing it in other + * // compatible modes. * ignore: * + * // NOTE: a callback can have a .doc attr containing + * // documentation... * : , * + * : [ + * // this can be any type of handler except for an alias... + * , + * + * ], + * * : { - * 'default': , + * // modifiers can either have a callback or an alias as + * // a value... + * // NOTE: when the alias is resolved, the same modifiers + * // will be applied to the final resolved handler. + * default: | , + * * // a modifier can be any single modifier, like shift or a - * // combination of modifers like 'ctrl+shift', given in order + * // combination of modifiers like 'ctrl+shift', given in order * // of priority. * // supported modifiers are (in order of priority): * // - ctrl @@ -112,12 +348,6 @@ var KEYBOARD_HANDLER_PROPAGATE = true * ... * }, * - * : [ - * // this can be any type of handler except for an alias... - * , - * - * ], - * * // alias... * : , * @@ -132,117 +362,79 @@ var KEYBOARD_HANDLER_PROPAGATE = true * - explicit key code, e.g. 65 * - key name, if present in _SPECIAL_KEYS, e.g. Enter * - key char (uppercase), as is returned by String.fromCharCode(...) e.g. A + * - action -- any arbitrary string that is not in the above categories. * * - * NOTE: to rest what to use as use toKeyCode(..) / toKeyName(..). + * NOTE: actions,the last case, are used for alias referencing, they will + * never match a real key, but will get resolved in alias searches. + * NOTE: to test what to use as use toKeyCode(..) / toKeyName(..). * NOTE: all fields are optional. * NOTE: if a handler explicitly returns false then that will break the * event propagation chain and exit the handler. * i.e. no other matching handlers will be called. + * NOTE: if more than one match is found all matching handlers will be + * called in sequence until one returns false explicitly. * NOTE: a is used as a predicate to select a section to * use. if multiple selectors match something then multiple sections * will be resolved in order of occurrence. + * NOTE: the number keys are named with a leading hash '#' (e.g. '#8') + * to avoid conflicsts with keys that have the code with the same + * value (e.g. 'backspace' (8)). + * NOTE: one can use a doc(, ) as a shorthand to assign + * a docstring to a handler. + * it will only assign .doc attr and return the original function. * - * XXX might need to add meta information to generate sensible help... + * XXX need an explicit way to prioritize modes... + * XXX will aliases get resolved if they are in a different mode?? */ function makeKeyboardHandler(keybindings, unhandled){ if(unhandled == null){ - //unhandled = function(){return false} - unhandled = function(){return KEYBOARD_HANDLER_PROPAGATE} + unhandled = function(){} } return function(evt){ var did_handling = false var res = null - for(var mode in keybindings){ - if($(mode).length > 0){ - var bindings = keybindings[mode] - var key = evt.keyCode - var chr = toKeyName(evt.keyCode) - // normalize the modifiers... - var modifers = evt.ctrlKey ? 'ctrl' : '' - modifers += evt.altKey ? (modifers != '' ? '+alt' : 'alt') : '' - modifers += evt.shiftKey ? (modifers != '' ? '+shift' : 'shift') : '' + // key data... + var key = evt.keyCode - if(chr in bindings){ - var handler = bindings[chr] - } else { - var handler = bindings[key] - } + // normalize the modifiers... + var modifiers = evt.ctrlKey ? 'ctrl' : '' + modifiers += evt.altKey ? (modifiers != '' ? '+alt' : 'alt') : '' + modifiers += evt.shiftKey ? (modifiers != '' ? '+shift' : 'shift') : '' + + //window.DEBUG && console.log('KEY:', key, chr, modifiers) + + var handlers = getKeyHandlers(key, modifiers, keybindings) + + for(var mode in handlers){ + var handler = handlers[mode] + if(handler != null){ - // alias... - while (typeof(handler) == typeof(123) || typeof(handler) == typeof('str')) { - if(handler in bindings){ - // XXX need to take care of that we can always be a number or a string... - handler = bindings[handler] - } else if(typeof(h) == typeof(1)) { - handler = bindings[toKeyName(handler)] - } else { - handler = bindings[toKeyCode(handler)] - } - } - // no handler... - if(handler == null){ - // if something is ignored then just breakout and stop handling... - if(bindings.ignore == '*' - || bindings.ignore != null && bindings.ignore.indexOf(key) != -1){ - res = res == null ? true : res - did_handling = true - // ignoring a key will stop processing it... - break - } - continue - } // Array, lisp style with docs... - // XXX for some odd reason typeof([]) == typeof({})!!! if(typeof(handler) == typeof([]) && handler.constructor.name == 'Array'){ // we do not care about docs here, so just get the handler... handler = handler[0] } - // complex handler... - if(typeof(handler) == typeof({})){ - var callback = handler[modifers] - if(callback == null){ - callback = handler['default'] - } - if(callback != null){ - res = callback() - did_handling = true - continue - } - } else { - // simple callback... - //res = handler(evt) - res = handler() - // if the handler explicitly returned false break out... - if(res === false){ - // XXX is this corrent??? - // XXX should we just break here instead of return... - return KEYBOARD_HANDLER_PROPAGATE ? res : null - } - did_handling = true - continue + + did_handling = true + res = handler(evt) + + if(res === false){ + break } } } if(!did_handling){ - // key is unhandled by any modes... return unhandled(key) - } else { - // XXX should we handle multiple hits??? - return KEYBOARD_HANDLER_PROPAGATE&&res?true:false } + return res } } -// helper... -function doc(text, func){ - func.doc = text - return func -} - /* Build structure ready for conversion to HTML help. +* * Structure: * { * : { @@ -255,72 +447,151 @@ function doc(text, func){ * * - list of key names. * +* +* NOTE: this will not add keys (key names) that are not explicit key names. */ -function getKeyHandler(key, keybindings){ +function buildKeybindingsHelp(keybindings, shifted_keys){ + shifted_keys = shifted_keys == null ? _SHIFT_KEYS : shifted_keys + var res = {} + var mode, title + + for(var pattern in keybindings){ + mode = keybindings[pattern] + + // titles and docs... + title = mode.title == null ? pattern : mode.title + res[title] = { + doc: mode.doc == null ? '' : mode.doc + } + section = res[title] + + // handlers... + for(var key in mode){ + if(key == 'doc' || key == 'title' || key == 'ignore'){ + continue + } + //var modifiers = getKeyHandlers(key, '?', keybindings, pattern)[pattern] + var modifiers = getKeyHandlers(key, '?', keybindings, 'all')[pattern] + modifiers = modifiers == 'none' || modifiers == undefined ? [''] : modifiers + + for(var i=0; i < modifiers.length; i++){ + var mod = modifiers[i] + + //var handler = getKeyHandlers(key, mod, keybindings, pattern)[pattern] + var handler = getKeyHandlers(key, mod, keybindings, 'all')[pattern] + + // standard object doc... + if('doc' in handler){ + var doc = handler.doc + + // lisp style... + } else if(typeof(handler) == typeof([]) && handler.constructor.name == 'Array'){ + var doc = handler[1] + + // no doc... + } else { + if('name' in handler && handler.name != ''){ + var doc = handler.name + } else { + // XXX is this the right way to do this? + var doc = handler + } + } + + // populate the section... + // NOTE: we need a list of keys per action... + if(doc in section){ + var keys = section[doc] + } else { + var keys = [] + section[doc] = keys + } + + // translate shifted keys... + if(shifted_keys != false && mod == 'shift' && key in shifted_keys){ + mod = '' + key = shifted_keys[key] + } + + // skip anything that is not a key... + if(key.length > 1 && !(key in _KEY_CODES)){ + continue + } + + keys.push((mod == '' || mod == 'default') ? key : (mod +'+'+ key)) + } + + } + } + + return res } +// Build a basic HTML table with keyboard help... +// +// The table will look like this: +// +// +// +// +// +// +// +// +// +// +// +// +// +// ... +// +//
SECTION TITLE +//
SECTION DESCRIPTION +//
KEYS +// ACTION DESCRIPTION +//
+// +// NOTE: section are not separated in any way other than the element. +// NOTE: the actual HTML is created by jQuery, so the table may get +// slightly structurally changed, i.e. a element will be +// added etc. +// +function buildKeybindingsHelpHTML(keybindings){ + var doc = buildKeybindingsHelp(keybindings) -function buildKeyindingsHelp(keybindings){ - var res = {} - - for(var selector in keybindings){ - var section = keybindings[selector] - var title = section.title == null ? selector : section.title - var res_sec = { - doc: section.doc, + var res = '' + for(var mode in doc){ + if(mode == 'doc'){ + continue } - res.title = res_sec + // section head... + res += ' \n' + mode = doc[mode] + res += ' \n' - for(var k in section){ - // handler... - var h = section[k] - var doc - var key = typeof(k) == typeof(1) ? toKeyName(k) : k - - // an alias... - while(typeof(h) == typeof(1) || typeof(h) == typeof('s')){ - if(h in section){ - // XXX need to take care of that we can always be a number or a string... - h = section[h] - } else if(typeof(h) == typeof(1)) { - h = section[toKeyName(h)] - } else { - h = section[toKeyCode(h)] - } - } - - // no handler... - if(h == null){ - doc = 'Nothing' - - // a handler with doc (array)... - } else if(typeof(h) == typeof([]) && handler.constructor.name == 'Array'){ - doc = h[1] - - // complex handler (object)... - } else if(typeof(h) == typeof({})){ - // XXX - - - // simple handler (function)... - } else { - doc = h.doc != null ? h.doc : h - - } - - // push the actual data... - if(doc in res_sec){ - res_sec[doc].push(key) - } else { - res_sec[doc] = [key] + // keys... + for(var action in mode){ + if(action == 'doc'){ + continue } + res += ' \n' } } - return res + res += '
' + mode + '
'+ mode.doc + '
' + mode[action].join(', ') +''+ action + '
' + + return $(res) } +/********************************************************************** +* Key binding editor... +*/ + +// XXX + + + /********************************************************************** * vim:set ts=4 sw=4 : */