From 28ffd5a10326b708ba52f3f6631de60ef58c9ca2 Mon Sep 17 00:00:00 2001 From: tetsuo Date: Wed, 7 Jan 2015 23:56:47 +0000 Subject: [PATCH 1/2] dependencies: add mousetrap-record plugin --- libs/mousetrap-record.js | 189 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 libs/mousetrap-record.js diff --git a/libs/mousetrap-record.js b/libs/mousetrap-record.js new file mode 100644 index 00000000..1b935337 --- /dev/null +++ b/libs/mousetrap-record.js @@ -0,0 +1,189 @@ +/** + * This extension allows you to record a sequence using Mousetrap. + * + * @author Dan Tao + */ +(function(Mousetrap) { + /** + * the sequence currently being recorded + * + * @type {Array} + */ + var _recordedSequence = [], + + /** + * a callback to invoke after recording a sequence + * + * @type {Function|null} + */ + _recordedSequenceCallback = null, + + /** + * a list of all of the keys currently held down + * + * @type {Array} + */ + _currentRecordedKeys = [], + + /** + * temporary state where we remember if we've already captured a + * character key in the current combo + * + * @type {boolean} + */ + _recordedCharacterKey = false, + + /** + * a handle for the timer of the current recording + * + * @type {null|number} + */ + _recordTimer = null, + + /** + * the original handleKey method to override when Mousetrap.record() is + * called + * + * @type {Function} + */ + _origHandleKey = Mousetrap.handleKey; + + /** + * handles a character key event + * + * @param {string} character + * @param {Array} modifiers + * @param {Event} e + * @returns void + */ + function _handleKey(character, modifiers, e) { + // remember this character if we're currently recording a sequence + if (e.type == 'keydown') { + if (character.length === 1 && _recordedCharacterKey) { + _recordCurrentCombo(); + } + + for (i = 0; i < modifiers.length; ++i) { + _recordKey(modifiers[i]); + } + _recordKey(character); + + // once a key is released, all keys that were held down at the time + // count as a keypress + } else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) { + _recordCurrentCombo(); + } + } + + /** + * marks a character key as held down while recording a sequence + * + * @param {string} key + * @returns void + */ + function _recordKey(key) { + var i; + + // one-off implementation of Array.indexOf, since IE6-9 don't support it + for (i = 0; i < _currentRecordedKeys.length; ++i) { + if (_currentRecordedKeys[i] === key) { + return; + } + } + + _currentRecordedKeys.push(key); + + if (key.length === 1) { + _recordedCharacterKey = true; + } + } + + /** + * marks whatever key combination that's been recorded so far as finished + * and gets ready for the next combo + * + * @returns void + */ + function _recordCurrentCombo() { + _recordedSequence.push(_currentRecordedKeys); + _currentRecordedKeys = []; + _recordedCharacterKey = false; + _restartRecordTimer(); + } + + /** + * ensures each combo in a sequence is in a predictable order and formats + * key combos to be '+'-delimited + * + * modifies the sequence in-place + * + * @param {Array} sequence + * @returns void + */ + function _normalizeSequence(sequence) { + var i; + + for (i = 0; i < sequence.length; ++i) { + sequence[i].sort(function(x, y) { + // modifier keys always come first, in alphabetical order + if (x.length > 1 && y.length === 1) { + return -1; + } else if (x.length === 1 && y.length > 1) { + return 1; + } + + // character keys come next (list should contain no duplicates, + // so no need for equality check) + return x > y ? 1 : -1; + }); + + sequence[i] = sequence[i].join('+'); + } + } + + /** + * finishes the current recording, passes the recorded sequence to the stored + * callback, and sets Mousetrap.handleKey back to its original function + * + * @returns void + */ + function _finishRecording() { + if (_recordedSequenceCallback) { + _normalizeSequence(_recordedSequence); + _recordedSequenceCallback(_recordedSequence); + } + + // reset all recorded state + _recordedSequence = []; + _recordedSequenceCallback = null; + _currentRecordedKeys = []; + + Mousetrap.handleKey = _origHandleKey; + } + + /** + * called to set a 1 second timeout on the current recording + * + * this is so after each key press in the sequence the recording will wait for + * 1 more second before executing the callback + * + * @returns void + */ + function _restartRecordTimer() { + clearTimeout(_recordTimer); + _recordTimer = setTimeout(_finishRecording, 1000); + } + + /** + * records the next sequence and passes it to a callback once it's + * completed + * + * @param {Function} callback + * @returns void + */ + Mousetrap.record = function(callback) { + Mousetrap.handleKey = _handleKey; + _recordedSequenceCallback = callback; + }; + +})(Mousetrap); From cf76451423bb23c342d7d3f6dd643e98c78c1d95 Mon Sep 17 00:00:00 2001 From: tetsuo Date: Thu, 8 Jan 2015 00:01:37 +0000 Subject: [PATCH 2/2] KDKeyboardListener: add static method 'record(callback)' that lets recording keyboard sequences --- src/core/keyboard/listener.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/keyboard/listener.coffee b/src/core/keyboard/listener.coffee index 90fcd6f8..43ff1d38 100644 --- a/src/core/keyboard/listener.coffee +++ b/src/core/keyboard/listener.coffee @@ -1,5 +1,6 @@ require './../../../libs/mousetrap.js' require './../../../libs/mousetrap-global-bind.js' +require './../../../libs/mousetrap-record.js' module.exports = class KDKeyboardListener @@ -72,3 +73,8 @@ module.exports = class KDKeyboardListener @currentListener = new this @currentListener.listen() return @currentListener + + @record = (fn) -> + Mousetrap.record fn.bind fn + return +