Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / common / key_util.js
blob5ff64d01d7aa1c0110e2827a588913faa36a2ae3
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview A collection of JavaScript utilities used to simplify working
7 * with keyboard events.
8 */
11 goog.provide('cvox.KeyUtil');
12 goog.provide('cvox.SimpleKeyEvent');
14 goog.require('cvox.ChromeVox');
15 goog.require('cvox.KeySequence');
17 /**
18 * @typedef {{ctrlKey: (boolean|undefined),
19 * altKey: (boolean|undefined),
20 * shiftKey: (boolean|undefined),
21 * keyCode: (number|undefined)}}
23 cvox.SimpleKeyEvent;
25 /**
26 * Create the namespace
27 * @constructor
29 cvox.KeyUtil = function() {
32 /**
33 * The time in ms at which the ChromeVox Sticky Mode key was pressed.
34 * @type {number}
36 cvox.KeyUtil.modeKeyPressTime = 0;
38 /**
39 * Indicates if sequencing is currently active for building a keyboard shortcut.
40 * @type {boolean}
42 cvox.KeyUtil.sequencing = false;
44 /**
45 * The previous KeySequence when sequencing is ON.
46 * @type {cvox.KeySequence}
48 cvox.KeyUtil.prevKeySequence = null;
51 /**
52 * The sticky key sequence.
53 * @type {cvox.KeySequence}
55 cvox.KeyUtil.stickyKeySequence = null;
57 /**
58 * Maximum number of key codes the sequence buffer may hold. This is the max
59 * length of a sequential keyboard shortcut, i.e. the number of key that can be
60 * pressed one after the other while modifier keys (Cros+Shift) are held down.
61 * @const
62 * @type {number}
64 cvox.KeyUtil.maxSeqLength = 2;
67 /**
68 * Convert a key event into a Key Sequence representation.
70 * @param {Event|cvox.SimpleKeyEvent} keyEvent The keyEvent to convert.
71 * @return {cvox.KeySequence} A key sequence representation of the key event.
73 cvox.KeyUtil.keyEventToKeySequence = function(keyEvent) {
74 var util = cvox.KeyUtil;
75 if (util.prevKeySequence &&
76 (util.maxSeqLength == util.prevKeySequence.length())) {
77 // Reset the sequence buffer if max sequence length is reached.
78 util.sequencing = false;
79 util.prevKeySequence = null;
81 // Either we are in the middle of a key sequence (N > H), or the key prefix
82 // was pressed before (Ctrl+Z), or sticky mode is enabled
83 var keyIsPrefixed = util.sequencing || keyEvent['keyPrefix'] ||
84 keyEvent['stickyMode'];
86 // Create key sequence.
87 var keySequence = new cvox.KeySequence(keyEvent);
89 // Check if the Cvox key should be considered as pressed because the
90 // modifier key combination is active.
91 var keyWasCvox = keySequence.cvoxModifier;
93 if (keyIsPrefixed || keyWasCvox) {
94 if (!util.sequencing && util.isSequenceSwitchKeyCode(keySequence)) {
95 // If this is the beginning of a sequence.
96 util.sequencing = true;
97 util.prevKeySequence = keySequence;
98 return keySequence;
99 } else if (util.sequencing) {
100 if (util.prevKeySequence.addKeyEvent(keyEvent)) {
101 keySequence = util.prevKeySequence;
102 util.prevKeySequence = null;
103 util.sequencing = false;
104 return keySequence;
105 } else {
106 throw 'Think sequencing is enabled, yet util.prevKeySequence already' +
107 'has two key codes' + util.prevKeySequence;
110 } else {
111 util.sequencing = false;
114 // Repeated keys pressed.
115 var currTime = new Date().getTime();
116 if (cvox.KeyUtil.isDoubleTapKey(keySequence) &&
117 util.prevKeySequence &&
118 keySequence.equals(util.prevKeySequence)) {
119 var prevTime = util.modeKeyPressTime;
120 if (prevTime > 0 && currTime - prevTime < 300) { // Double tap
121 keySequence = util.prevKeySequence;
122 keySequence.doubleTap = true;
123 util.prevKeySequence = null;
124 util.sequencing = false;
125 // Resets the search key state tracked for ChromeOS because in OOBE,
126 // we never get a key up for the key down (keyCode 91).
127 if (cvox.ChromeVox.isChromeOS &&
128 keyEvent.keyCode == cvox.KeyUtil.getStickyKeyCode()) {
129 cvox.ChromeVox.searchKeyHeld = false;
131 return keySequence;
133 // The user double tapped the sticky key but didn't do it within the
134 // required time. It's possible they will try again, so keep track of the
135 // time the sticky key was pressed and keep track of the corresponding
136 // key sequence.
138 util.prevKeySequence = keySequence;
139 util.modeKeyPressTime = currTime;
140 return keySequence;
144 * Returns the string representation of the specified key code.
146 * @param {number} keyCode key code.
147 * @return {string} A string representation of the key event.
149 cvox.KeyUtil.keyCodeToString = function(keyCode) {
150 if (keyCode == 17) {
151 return 'Ctrl';
153 if (keyCode == 18) {
154 return 'Alt';
156 if (keyCode == 16) {
157 return 'Shift';
159 if ((keyCode == 91) || (keyCode == 93)) {
160 if (cvox.ChromeVox.isChromeOS) {
161 return 'Search';
162 } else if (cvox.ChromeVox.isMac) {
163 return 'Cmd';
164 } else {
165 return 'Win';
168 // TODO(rshearer): This is a hack to work around the special casing of the
169 // sticky mode string that used to happen in keyEventToString. We won't need
170 // it once we move away from strings completely.
171 if (keyCode == 45) {
172 return 'Insert';
174 if (keyCode >= 65 && keyCode <= 90) {
175 // A - Z
176 return String.fromCharCode(keyCode);
177 } else if (keyCode >= 48 && keyCode <= 57) {
178 // 0 - 9
179 return String.fromCharCode(keyCode);
180 } else {
181 // Anything else
182 return '#' + keyCode;
187 * Returns the keycode of a string representation of the specified modifier.
189 * @param {string} keyString Modifier key.
190 * @return {number} Key code.
192 cvox.KeyUtil.modStringToKeyCode = function(keyString) {
193 switch (keyString) {
194 case 'Ctrl':
195 return 17;
196 case 'Alt':
197 return 18;
198 case 'Shift':
199 return 16;
200 case 'Cmd':
201 case 'Win':
202 return 91;
204 return -1;
208 * Returns the key codes of a string respresentation of the ChromeVox modifiers.
210 * @return {Array<number>} Array of key codes.
212 cvox.KeyUtil.cvoxModKeyCodes = function() {
213 var modKeyCombo = cvox.ChromeVox.modKeyStr.split(/\+/g);
214 var modKeyCodes = modKeyCombo.map(function(keyString) {
215 return cvox.KeyUtil.modStringToKeyCode(keyString);
217 return modKeyCodes;
221 * Checks if the specified key code is a key used for switching into a sequence
222 * mode. Sequence switch keys are specified in
223 * cvox.KeyUtil.sequenceSwitchKeyCodes
225 * @param {!cvox.KeySequence} rhKeySeq The key sequence to check.
226 * @return {boolean} true if it is a sequence switch keycode, false otherwise.
228 cvox.KeyUtil.isSequenceSwitchKeyCode = function(rhKeySeq) {
229 for (var i = 0; i < cvox.ChromeVox.sequenceSwitchKeyCodes.length; i++) {
230 var lhKeySeq = cvox.ChromeVox.sequenceSwitchKeyCodes[i];
231 if (lhKeySeq.equals(rhKeySeq)) {
232 return true;
235 return false;
240 * Get readable string description of the specified keycode.
242 * @param {number} keyCode The key code.
243 * @return {string} Returns a string description.
245 cvox.KeyUtil.getReadableNameForKeyCode = function(keyCode) {
246 if (keyCode == 0) {
247 return 'Power button';
248 } else if (keyCode == 17) {
249 return 'Control';
250 } else if (keyCode == 18) {
251 return 'Alt';
252 } else if (keyCode == 16) {
253 return 'Shift';
254 } else if (keyCode == 9) {
255 return 'Tab';
256 } else if ((keyCode == 91) || (keyCode == 93)) {
257 if (cvox.ChromeVox.isChromeOS) {
258 return 'Search';
259 } else if (cvox.ChromeVox.isMac) {
260 return 'Cmd';
261 } else {
262 return 'Win';
264 } else if (keyCode == 8) {
265 return 'Backspace';
266 } else if (keyCode == 32) {
267 return 'Space';
268 } else if (keyCode == 35) {
269 return'end';
270 } else if (keyCode == 36) {
271 return 'home';
272 } else if (keyCode == 37) {
273 return 'Left arrow';
274 } else if (keyCode == 38) {
275 return 'Up arrow';
276 } else if (keyCode == 39) {
277 return 'Right arrow';
278 } else if (keyCode == 40) {
279 return 'Down arrow';
280 } else if (keyCode == 45) {
281 return 'Insert';
282 } else if (keyCode == 13) {
283 return 'Enter';
284 } else if (keyCode == 27) {
285 return 'Escape';
286 } else if (keyCode == 112) {
287 return cvox.ChromeVox.isChromeOS ? 'Back' : 'F1';
288 } else if (keyCode == 113) {
289 return cvox.ChromeVox.isChromeOS ? 'Forward' : 'F2';
290 } else if (keyCode == 114) {
291 return cvox.ChromeVox.isChromeOS ? 'Refresh' : 'F3';
292 } else if (keyCode == 115) {
293 return cvox.ChromeVox.isChromeOS ? 'Toggle full screen' : 'F4';
294 } else if (keyCode == 116) {
295 return 'F5';
296 } else if (keyCode == 117) {
297 return 'F6';
298 } else if (keyCode == 118) {
299 return 'F7';
300 } else if (keyCode == 119) {
301 return 'F8';
302 } else if (keyCode == 120) {
303 return 'F9';
304 } else if (keyCode == 121) {
305 return 'F10';
306 } else if (keyCode == 122) {
307 return 'F11';
308 } else if (keyCode == 123) {
309 return 'F12';
310 } else if (keyCode == 186) {
311 return 'Semicolon';
312 } else if (keyCode == 187) {
313 return 'Equal sign';
314 } else if (keyCode == 188) {
315 return 'Comma';
316 } else if (keyCode == 189) {
317 return 'Dash';
318 } else if (keyCode == 190) {
319 return 'Period';
320 } else if (keyCode == 191) {
321 return 'Forward slash';
322 } else if (keyCode == 192) {
323 return 'Grave accent';
324 } else if (keyCode == 219) {
325 return 'Open bracket';
326 } else if (keyCode == 220) {
327 return 'Back slash';
328 } else if (keyCode == 221) {
329 return 'Close bracket';
330 } else if (keyCode == 222) {
331 return 'Single quote';
332 } else if (keyCode == 115) {
333 return 'Toggle full screen';
334 } else if (keyCode >= 48 && keyCode <= 90) {
335 return String.fromCharCode(keyCode);
340 * Get the platform specific sticky key keycode.
342 * @return {number} The platform specific sticky key keycode.
344 cvox.KeyUtil.getStickyKeyCode = function() {
345 // TODO (rshearer): This should not be hard-coded here.
346 var stickyKeyCode = 45; // Insert for Linux and Windows
347 if (cvox.ChromeVox.isChromeOS || cvox.ChromeVox.isMac) {
348 stickyKeyCode = 91; // GUI key (Search/Cmd) for ChromeOs and Mac
350 return stickyKeyCode;
355 * Get readable string description for an internal string representation of a
356 * key or a keyboard shortcut.
358 * @param {string} keyStr The internal string repsentation of a key or
359 * a keyboard shortcut.
360 * @return {?string} Readable string representation of the input.
362 cvox.KeyUtil.getReadableNameForStr = function(keyStr) {
363 // TODO (clchen): Refactor this function away since it is no longer used.
364 return null;
369 * Creates a string representation of a KeySequence.
370 * A KeySequence with a keyCode of 76 ('L') and the control and alt keys down
371 * would return the string 'Ctrl+Alt+L', for example. A key code that doesn't
372 * correspond to a letter or number will typically return a string with a
373 * pound and then its keyCode, like '#39' for Right Arrow. However,
374 * if the opt_readableKeyCode option is specified, the key code will return a
375 * readable string description like 'Right Arrow' instead of '#39'.
377 * The modifiers always come in this order:
379 * Ctrl
380 * Alt
381 * Shift
382 * Meta
384 * @param {cvox.KeySequence} keySequence The KeySequence object.
385 * @param {boolean=} opt_readableKeyCode Whether or not to return a readable
386 * string description instead of a string with a pound symbol and a keycode.
387 * Default is false.
388 * @param {boolean=} opt_modifiers Restrict printout to only modifiers. Defaults
389 * to false.
390 * @return {string} Readable string representation of the KeySequence object.
392 cvox.KeyUtil.keySequenceToString = function(
393 keySequence, opt_readableKeyCode, opt_modifiers) {
394 // TODO(rshearer): Move this method and the getReadableNameForKeyCode and the
395 // method to KeySequence after we refactor isModifierActive (when the modifie
396 // key becomes customizable and isn't stored as a string). We can't do it
397 // earlier because isModifierActive uses KeyUtil.getReadableNameForKeyCode,
398 // and I don't want KeySequence to depend on KeyUtil.
399 var str = '';
401 var numKeys = keySequence.length();
403 for (var index = 0; index < numKeys; index++) {
404 if (str != '' && !opt_modifiers) {
405 str += '>';
406 } else if (str != '') {
407 str += '+';
410 // This iterates through the sequence. Either we're on the first key
411 // pressed or the second
412 var tempStr = '';
413 for (var keyPressed in keySequence.keys) {
414 // This iterates through the actual key, taking into account any
415 // modifiers.
416 if (!keySequence.keys[keyPressed][index]) {
417 continue;
419 var modifier = '';
420 switch (keyPressed) {
421 case 'ctrlKey':
422 // TODO(rshearer): This is a hack to work around the special casing
423 // of the Ctrl key that used to happen in keyEventToString. We won't
424 // need it once we move away from strings completely.
425 modifier = 'Ctrl';
426 break;
427 case 'searchKeyHeld':
428 var searchKey = cvox.KeyUtil.getReadableNameForKeyCode(91);
429 modifier = searchKey;
430 break;
431 case 'altKey':
432 modifier = 'Alt';
433 break;
434 case 'altGraphKey':
435 modifier = 'AltGraph';
436 break;
437 case 'shiftKey':
438 modifier = 'Shift';
439 break;
440 case 'metaKey':
441 var metaKey = cvox.KeyUtil.getReadableNameForKeyCode(91);
442 modifier = metaKey;
443 break;
444 case 'keyCode':
445 var keyCode = keySequence.keys[keyPressed][index];
446 // We make sure the keyCode isn't for a modifier key. If it is, then
447 // we've already added that into the string above.
448 if (!keySequence.isModifierKey(keyCode) && !opt_modifiers) {
449 if (opt_readableKeyCode) {
450 tempStr += cvox.KeyUtil.getReadableNameForKeyCode(keyCode);
451 } else {
452 tempStr += cvox.KeyUtil.keyCodeToString(keyCode);
456 if (str.indexOf(modifier) == -1) {
457 tempStr += modifier + '+';
460 str += tempStr;
462 // Strip trailing +.
463 if (str[str.length - 1] == '+') {
464 str = str.slice(0, -1);
468 if (keySequence.cvoxModifier || keySequence.prefixKey) {
469 if (str != '') {
470 str = 'Cvox+' + str;
471 } else {
472 str = 'Cvox';
474 } else if (keySequence.stickyMode) {
475 if (str[str.length - 1] == '>') {
476 str = str.slice(0, -1);
478 str = str + '+' + str;
480 return str;
484 * Looks up if the given key sequence is triggered via double tap.
485 * @param {cvox.KeySequence} key The key.
486 * @return {boolean} True if key is triggered via double tap.
488 cvox.KeyUtil.isDoubleTapKey = function(key) {
489 var isSet = false;
490 var originalState = key.doubleTap;
491 key.doubleTap = true;
492 for (var i = 0, keySeq; keySeq = cvox.KeySequence.doubleTapCache[i]; i++) {
493 if (keySeq.equals(key)) {
494 isSet = true;
495 break;
498 key.doubleTap = originalState;
499 return isSet;