Index: chrome/browser/resources/extensions/extension_command_list.js |
=================================================================== |
--- chrome/browser/resources/extensions/extension_command_list.js (revision 140271) |
+++ chrome/browser/resources/extensions/extension_command_list.js (working copy) |
@@ -13,9 +13,65 @@ |
*/ |
var ExtensionCommandList = cr.ui.define('div'); |
+ /** |
+ * Returns whether the passed in |keyCode| is a valid extension command |
+ * char or not. This is restricted to A-Z and 0-9 (ignoring modifiers) at |
+ * the moment. |
+ * @param {int} keyCode The keycode to consider. |
+ * @return {boolean} Returns whether the char is valid. |
+ */ |
+ function validChar(keyCode) { |
+ return (keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) || |
+ (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0)); |
+ } |
+ |
+ /** |
+ * Convert a keystroke event to string form, while taking into account |
+ * (ignoring) invalid extension commands. |
+ * @param {Event} event The keyboard event to convert. |
+ * @return {string} The keystroke as a string. |
+ */ |
+ function keystrokeToString(event) { |
+ var output = ''; |
+ if (event.ctrlKey) |
+ output = 'Ctrl + '; |
+ if (!event.ctrlKey && event.altKey) |
+ output += 'Alt + '; |
+ if (event.shiftKey) |
+ output += 'Shift + '; |
+ if (validChar(event.keyCode)) |
+ output += String.fromCharCode('A'.charCodeAt(0) + event.keyCode - 65); |
+ return output; |
+ } |
+ |
ExtensionCommandList.prototype = { |
__proto__: HTMLDivElement.prototype, |
+ /** |
+ * While capturing, this records the current (last) keyboard event generated |
+ * by the user. Will be |null| after capture and during capture when no |
+ * keyboard event has been generated. |
+ * @type: {keyboard event}. |
+ * @private |
+ */ |
+ currentKeyEvent_: null, |
+ |
+ /** |
+ * While capturing, this keeps track of the previous selection so we can |
+ * revert back to if no valid assignment is made during capture. |
+ * @type: {string}. |
+ * @private |
+ */ |
+ oldValue_: '', |
+ |
+ /** |
+ * While capturing, this keeps track of which element the user asked to |
+ * change. |
+ * @type: {HTMLElement}. |
+ * @private |
+ */ |
+ capturingElement_: null, |
+ |
/** @inheritDoc */ |
decorate: function() { |
this.textContent = ''; |
@@ -59,17 +115,137 @@ |
var description = node.querySelector('.command-description'); |
description.textContent = command.description; |
- var shortcut = node.querySelector('.command-shortcut'); |
+ var command_shortcut = node.querySelector('.command-shortcut'); |
+ command_shortcut.addEventListener('mouseup', |
+ this.startCapture_.bind(this)); |
+ command_shortcut.addEventListener('blur', this.endCapture_.bind(this)); |
+ command_shortcut.addEventListener('keydown', |
+ this.handleKeyDown_.bind(this)); |
+ command_shortcut.addEventListener('keyup', this.handleKeyUp_.bind(this)); |
+ |
if (!command.active) { |
- shortcut.textContent = |
+ command_shortcut.textContent = |
loadTimeData.getString('extensionCommandsInactive'); |
- shortcut.classList.add('inactive-keybinding'); |
+ command_shortcut.classList.add('inactive-keybinding'); |
} else { |
- shortcut.textContent = command.keybinding; |
+ command_shortcut.textContent = command.keybinding; |
} |
this.appendChild(node); |
}, |
+ |
+ /** |
+ * Starts keystroke capture to determine which key to use for a particular |
+ * extension command. |
+ * @param {Event} event The keyboard event to consider. |
+ * @private |
+ */ |
+ startCapture_: function(event) { |
+ if (this.capturingElement_) |
+ return; // Already capturing. |
+ |
+ chrome.send('setShortcutHandlingSuspended', [true]); |
+ |
+ this.oldValue_ = event.target.textContent; |
+ event.target.textContent = |
+ loadTimeData.getString('extensionCommandsStartTyping'); |
+ event.target.classList.add('capturing'); |
+ |
+ this.capturingElement_ = event.target; |
+ }, |
+ |
+ /** |
+ * Ends keystroke capture and either restores the old value or (if valid |
+ * value) sets the new value as active.. |
+ * @param {Event} event The keyboard event to consider. |
+ * @private |
+ */ |
+ endCapture_: function(event) { |
+ if (!this.capturingElement_) |
+ return; // Not capturing. |
+ |
+ chrome.send('setShortcutHandlingSuspended', [false]); |
+ |
+ this.capturingElement_.classList.remove('capturing'); |
+ this.capturingElement_.classList.remove('contains-chars'); |
+ if (!this.currentKeyEvent_ || !validChar(this.currentKeyEvent_.keyCode)) |
+ this.capturingElement_.textContent = this.oldValue_; |
+ |
+ if (this.oldValue_ == '') |
+ this.capturingElement_.classList.remove('clearable'); |
+ else |
+ this.capturingElement_.classList.add('clearable'); |
+ |
+ this.oldValue_ = ''; |
+ this.capturingElement_ = null; |
+ this.currentKeyEvent_ = null; |
+ }, |
+ |
+ /** |
+ * The KeyDown handler. |
+ * @param {Event} event The keyboard event to consider. |
+ * @private |
+ */ |
+ handleKeyDown_: function(event) { |
+ if (!this.capturingElement_) |
+ this.startCapture_(event); |
+ |
+ this.handleKey_(event); |
+ }, |
+ |
+ /** |
+ * The KeyUp handler. |
+ * @param {Event} event The keyboard event to consider. |
+ * @private |
+ */ |
+ handleKeyUp_: function(event) { |
+ // We want to make it easy to change from Ctrl+Shift+ to just Ctrl+ by |
+ // releasing Shift, but we also don't want it to be easy to lose for |
+ // example Ctrl+Shift+F to Ctrl+ just because you didn't release Ctrl |
+ // as fast as the other two keys. Therefore, we process KeyUp until you |
+ // have a valid combination and then stop processing it (meaning that once |
+ // you have a valid combination, we won't change it until the next |
+ // KeyDown message arrives). |
+ if (!this.currentKeyEvent_ || !validChar(this.currentKeyEvent_.keyCode)) { |
+ if (!event.ctrlKey && !event.altKey) { |
+ // If neither Ctrl nor Alt is pressed then it is not a valid shortcut. |
+ // That means we're back at the starting point so we should restart |
+ // capture. |
+ this.endCapture_(event); |
+ this.startCapture_(event); |
+ } else { |
+ this.handleKey_(event); |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * A general key handler (used for both KeyDown and KeyUp). |
+ * @param {Event} event The keyboard event to consider. |
+ * @private |
+ */ |
+ handleKey_: function(event) { |
+ // While capturing, we prevent all events from bubbling, to prevent |
+ // shortcuts lacking the right modifier (F3 for example) from activating |
+ // and ending capture prematurely. |
+ event.preventDefault(); |
+ event.stopPropagation(); |
+ |
+ if (!event.ctrlKey && !event.altKey) |
+ return; // Ctrl or Alt is a must. |
+ |
+ var keystroke = keystrokeToString(event); |
+ event.target.textContent = keystroke; |
+ event.target.classList.add('contains-chars'); |
+ |
+ if (validChar(event.keyCode)) { |
+ this.oldValue_ = keystroke; // Forget what the old value was. |
+ chrome.send('setExtensionCommandShortcut', ['id', keystroke]); |
+ this.endCapture_(event); |
+ } |
+ |
+ this.currentKeyEvent_ = event; |
+ }, |
}; |
return { |