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