diff options
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js')
-rw-r--r-- | chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js new file mode 100644 index 00000000000..c0bd759c6bc --- /dev/null +++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js @@ -0,0 +1,453 @@ +// Copyright 2014 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. + + +/** + * @fileoverview This class provides a stable interface for initializing, + * querying, and modifying a ChromeVox key map. + * + * An instance contains an object-based bi-directional mapping from key binding + * to a function name of a user command (herein simply called a command). + * A caller is responsible for providing a JSON keymap (a simple Object key + * value structure), which has (key, command) key value pairs. + * + * Due to execution of user commands within the content script, the function + * name of the command is not explicitly checked within the background page via + * Closure. Any errors would only be caught at runtime. + * + * To retrieve static data about user commands, see both cvox.CommandStore and + * cvox.UserCommands. + */ + +goog.provide('cvox.KeyMap'); + +// TODO(dtseng): Only needed for sticky mode. +goog.require('cvox.KeyUtil'); +goog.require('cvox.PlatformUtil'); + +/** + * @param {Array.<Object.<string, + * {command: string, sequence: cvox.KeySequence}>>} + * commandsAndKeySequences An array of pairs - KeySequences and commands. + * @constructor + */ +cvox.KeyMap = function(commandsAndKeySequences) { + /** + * An array of bindings - commands and KeySequences. + * @type {Array.<Object.<string, + * {command: string, sequence: cvox.KeySequence}>>} + * @private + */ + this.bindings_ = commandsAndKeySequences; + + /** + * Maps a command to a key. This optimizes the process of searching for a + * key sequence when you already know the command. + * @type {Object.<string, cvox.KeySequence>} + * @private + */ + this.commandToKey_ = {}; + this.buildCommandToKey_(); +}; + + +/** + * Path to dir containing ChromeVox keymap json definitions. + * @type {string} + * @const + */ +cvox.KeyMap.KEYMAP_PATH = 'chromevox/background/keymaps/'; + + +/** + * An array of available key maps sorted by priority. + * (The first map is the default, the last is the least important). + * TODO(dtseng): Not really sure this belongs here, but it doesn't seem to be + * user configurable, so it doesn't make sense to json-stringify it. + * Should have class to siwtch among and manage multiple key maps. + * @type {Object.<string, Object.<string, string>>} + * @const + */ +cvox.KeyMap.AVAILABLE_MAP_INFO = { +'keymap_classic': { + 'file': 'classic_keymap.json' + }, +'keymap_flat': { + 'file': 'flat_keymap.json' + }, +'keymap_experimental': { + 'file': 'experimental.json' + } +}; + + +/** + * The index of the default key map info in cvox.KeyMap.AVAIABLE_KEYMAP_INFO. + * @type {number} + * @const + */ +cvox.KeyMap.DEFAULT_KEYMAP = 0; + + +/** + * The number of mappings in the keymap. + * @return {number} The number of mappings. + */ +cvox.KeyMap.prototype.length = function() { + return this.bindings_.length; +}; + + +/** + * Returns a copy of all KeySequences in this map. + * @return {Array.<cvox.KeySequence>} Array of all keys. + */ +cvox.KeyMap.prototype.keys = function() { + return this.bindings_.map(function(binding) { + return binding.sequence; + }); +}; + + +/** + * Returns a collection of command, KeySequence bindings. + * @return {Array.<Object.<string, cvox.KeySequence>>} Array of all command, + * key bindings. + * @suppress {checkTypes} inconsistent return type + * found : (Array.<(Object.<{command: string, + * sequence: (cvox.KeySequence|null)}>|null)>|null) + * required: (Array.<(Object.<(cvox.KeySequence|null)>|null)>|null) + */ +cvox.KeyMap.prototype.bindings = function() { + return this.bindings_; +}; + + +/** + * This method is called when cvox.KeyMap instances are stringified via + * JSON.stringify. + * @return {string} The JSON representation of this instance. + */ +cvox.KeyMap.prototype.toJSON = function() { + return JSON.stringify({bindings: this.bindings_}); +}; + + +/** + * Writes to local storage. + */ +cvox.KeyMap.prototype.toLocalStorage = function() { + localStorage['keyBindings'] = this.toJSON(); +}; + + +/** + * Checks if this key map has a given binding. + * @param {string} command The command. + * @param {cvox.KeySequence} sequence The key sequence. + * @return {boolean} Whether the binding exists. + */ +cvox.KeyMap.prototype.hasBinding = function(command, sequence) { + if (this.commandToKey_ != null) { + return this.commandToKey_[command] == sequence; + } else { + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (binding.command == command && binding.sequence == sequence) { + return true; + } + } + } + return false; +}; + + +/** + * Checks if this key map has a given command. + * @param {string} command The command to check. + * @return {boolean} Whether 'command' has a binding. + */ +cvox.KeyMap.prototype.hasCommand = function(command) { + if (this.commandToKey_ != null) { + return this.commandToKey_[command] != undefined; + } else { + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (binding.command == command) { + return true; + } + } + } + return false; +}; + + +/** + * Checks if this key map has a given key. + * @param {cvox.KeySequence} key The key to check. + * @return {boolean} Whether 'key' has a binding. + */ +cvox.KeyMap.prototype.hasKey = function(key) { + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (binding.sequence.equals(key)) { + return true; + } + } + return false; +}; + + +/** + * Gets a command given a key. + * @param {cvox.KeySequence} key The key to query. + * @return {?string} The command, if any. + */ +cvox.KeyMap.prototype.commandForKey = function(key) { + if (key != null) { + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (binding.sequence.equals(key)) { + return binding.command; + } + } + } + return null; +}; + + +/** + * Gets a key given a command. + * @param {string} command The command to query. + * @return {!Array.<cvox.KeySequence>} The keys associated with that command, + * if any. + */ +cvox.KeyMap.prototype.keyForCommand = function(command) { + if (this.commandToKey_ != null) { + return [this.commandToKey_[command]]; + } else { + var keySequenceArray = []; + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (binding.command == command) { + keySequenceArray.push(binding.sequence); + } + } + } + return (keySequenceArray.length > 0) ? keySequenceArray : []; +}; + + +/** + * Merges an input map with this one. The merge preserves this instance's + * mappings. It only adds new bindings if there isn't one already. + * If either the incoming binding's command or key exist in this, it will be + * ignored. + * @param {!cvox.KeyMap} inputMap The map to merge with this. + * @return {boolean} True if there were no merge conflicts. + */ +cvox.KeyMap.prototype.merge = function(inputMap) { + var keys = inputMap.keys(); + var cleanMerge = true; + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var command = inputMap.commandForKey(key); + if (command == 'toggleStickyMode') { + // TODO(dtseng): More uglyness because of sticky key. + continue; + } else if (key && command && + !this.hasKey(key) && !this.hasCommand(command)) { + this.bind_(command, key); + } else { + cleanMerge = false; + } + } + return cleanMerge; +}; + + +/** + * Changes an existing key binding to a new key. If the key is already bound to + * a command, the rebind will fail. + * @param {string} command The command to set. + * @param {cvox.KeySequence} newKey The new key to assign it to. + * @return {boolean} Whether the rebinding succeeds. + */ +cvox.KeyMap.prototype.rebind = function(command, newKey) { + if (this.hasCommand(command) && !this.hasKey(newKey)) { + this.bind_(command, newKey); + return true; + } + return false; +}; + + +/** + * Changes a key binding. Any existing bindings to the given key will be + * deleted. Use this.rebind to have non-overwrite behavior. + * @param {string} command The command to set. + * @param {cvox.KeySequence} newKey The new key to assign it to. + * @private + */ +cvox.KeyMap.prototype.bind_ = function(command, newKey) { + // TODO(dtseng): Need unit test to ensure command is valid for every *.json + // keymap. + var bound = false; + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (binding.command == command) { + // Replace the key with the new key. + delete binding.sequence; + binding.sequence = newKey; + if (this.commandToKey_ != null) { + this.commandToKey_[binding.command] = newKey; + } + bound = true; + } + } + if (!bound) { + var binding = { + 'command': command, + 'sequence': newKey + }; + this.bindings_.push(binding); + this.commandToKey_[binding.command] = binding.sequence; + } +}; + + +// TODO(dtseng): Move to a manager class. +/** + * Convenience method for getting a default key map. + * @return {!cvox.KeyMap} The default key map. + */ +cvox.KeyMap.fromDefaults = function() { + return /** @type {!cvox.KeyMap} */ ( + cvox.KeyMap.fromPath(cvox.KeyMap.KEYMAP_PATH + + cvox.KeyMap.AVAILABLE_MAP_INFO['keymap_classic'].file)); +}; + + +/** + * Convenience method for creating a key map based on a JSON (key, value) Object + * where the key is a literal keyboard string and value is a command string. + * @param {string} json The JSON. + * @return {cvox.KeyMap} The resulting object; null if unable to parse. + */ +cvox.KeyMap.fromJSON = function(json) { + try { + var commandsAndKeySequences = + /** @type {Array.<Object.<string, + * {command: string, sequence: cvox.KeySequence}>>} */ + (JSON.parse(json).bindings); + commandsAndKeySequences = commandsAndKeySequences.filter(function(value) { + return value.sequence.platformFilter === undefined || + cvox.PlatformUtil.matchesPlatform(value.sequence.platformFilter); + }); + } catch (e) { + return null; + } + + // Validate the type of the commandsAndKeySequences array. + if (typeof(commandsAndKeySequences) != 'object') { + return null; + } + for (var i = 0; i < commandsAndKeySequences.length; i++) { + if (commandsAndKeySequences[i].command == undefined || + commandsAndKeySequences[i].sequence == undefined) { + return null; + } else { + commandsAndKeySequences[i].sequence = /** @type {cvox.KeySequence} */ + (cvox.KeySequence.deserialize(commandsAndKeySequences[i].sequence)); + } + } + return new cvox.KeyMap(commandsAndKeySequences); +}; + + +/** + * Convenience method for creating a map local storage. + * @return {cvox.KeyMap} A map that reads from local storage. + */ +cvox.KeyMap.fromLocalStorage = function() { + if (localStorage['keyBindings']) { + return cvox.KeyMap.fromJSON(localStorage['keyBindings']); + } + return null; +}; + + +/** + * Convenience method for creating a cvox.KeyMap based on a path. + * Warning: you should only call this within a background page context. + * @param {string} path A valid path of the form + * chromevox/background/keymaps/*.json. + * @return {cvox.KeyMap} A valid KeyMap object; null on error. + */ +cvox.KeyMap.fromPath = function(path) { + return cvox.KeyMap.fromJSON(cvox.KeyMap.readJSON_(path)); +}; + + +/** + * Convenience method for getting a currently selected key map. + * @return {!cvox.KeyMap} The currently selected key map. + */ +cvox.KeyMap.fromCurrentKeyMap = function() { + var map = localStorage['currentKeyMap']; + if (map && cvox.KeyMap.AVAILABLE_MAP_INFO[map]) { + return /** @type {!cvox.KeyMap} */ (cvox.KeyMap.fromPath( + cvox.KeyMap.KEYMAP_PATH + cvox.KeyMap.AVAILABLE_MAP_INFO[map].file)); + } else { + return cvox.KeyMap.fromDefaults(); + } +}; + + +/** + * Takes a path to a JSON file and returns a JSON Object. + * @param {string} path Contains the path to a JSON file. + * @return {string} JSON. + * @private + * @suppress {missingProperties} + */ +cvox.KeyMap.readJSON_ = function(path) { + var url = chrome.extension.getURL(path); + if (!url) { + throw 'Invalid path: ' + path; + } + + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.send(); + return xhr.responseText; +}; + + +/** + * Resets the default modifier keys. + * TODO(dtseng): Move elsewhere when we figure out our localStorage story. + */ +cvox.KeyMap.prototype.resetModifier = function() { + localStorage['cvoxKey'] = cvox.ChromeVox.modKeyStr; +}; + + +/** + * Builds the map of commands to keys. + * @private + */ +cvox.KeyMap.prototype.buildCommandToKey_ = function() { + // TODO (dtseng): What about more than one sequence mapped to the same + // command? + for (var i = 0; i < this.bindings_.length; i++) { + var binding = this.bindings_[i]; + if (this.commandToKey_[binding.command] != undefined) { + // There's at least two key sequences mapped to the same + // command. continue. + continue; + } + this.commandToKey_[binding.command] = binding.sequence; + } +}; |