blob: df1d7cbc095f9f769d5bb7fc5706350ee09e53fc [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
'use strict';
/**
* Creates a new list of extension commands.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {cr.ui.div}
*/
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 (cr.isMac && event.metaKey)
output = 'Command+';
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 = '';
// Iterate over the extension data and add each item to the list.
this.data_.commands.forEach(this.createNodeForExtension_.bind(this));
},
/**
* Synthesizes and initializes an HTML element for the extension command
* metadata given in |extension|.
* @param {Object} extension A dictionary of extension metadata.
* @private
*/
createNodeForExtension_: function(extension) {
var template = $('template-collection-extension-commands').querySelector(
'.extension-command-list-extension-item-wrapper');
var node = template.cloneNode(true);
var title = node.querySelector('.extension-title');
title.textContent = extension.name;
this.appendChild(node);
// Iterate over the commands data within the extension and add each item
// to the list.
extension.commands.forEach(this.createNodeForCommand_.bind(this));
},
/**
* Synthesizes and initializes an HTML element for the extension command
* metadata given in |command|.
* @param {Object} command A dictionary of extension command metadata.
* @private
*/
createNodeForCommand_: function(command) {
var template = $('template-collection-extension-commands').querySelector(
'.extension-command-list-command-item-wrapper');
var node = template.cloneNode(true);
node.id = 'command-' + command.extension_id + '-' + command.command_name;
var description = node.querySelector('.command-description');
description.textContent = command.description;
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) {
command_shortcut.textContent =
loadTimeData.getString('extensionCommandsInactive');
command_shortcut.classList.add('inactive-keybinding');
} else {
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 && (!cr.isMac || !event.metaKey))
return; // Ctrl or Alt is a must (or Cmd on Mac).
var keystroke = keystrokeToString(event);
event.target.textContent = keystroke;
event.target.classList.add('contains-chars');
if (validChar(event.keyCode)) {
var node = event.target;
while (node && !node.id)
node = node.parentElement;
// The id is set to namespacePrefix-extensionId-commandName. We don't
// care about the namespacePrefix (but we care about the other two).
var id = node.id.substring(8, 40);
var command_name = node.id.substring(41);
this.oldValue_ = keystroke; // Forget what the old value was.
chrome.send('setExtensionCommandShortcut',
[id, command_name, keystroke]);
this.endCapture_(event);
}
this.currentKeyEvent_ = event;
},
};
return {
ExtensionCommandList: ExtensionCommandList
};
});