Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / keyboard_overlay.js
blob018aa6f9a00a4155a358846728afe246a675f813
1 // Copyright (c) 2012 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 <include src="keyboard_overlay_data.js"/>
6 <include src="keyboard_overlay_accessibility_helper.js"/>
8 var BASE_KEYBOARD = {
9   top: 0,
10   left: 0,
11   width: 1237,
12   height: 514
15 var BASE_INSTRUCTIONS = {
16   top: 194,
17   left: 370,
18   width: 498,
19   height: 142
22 var MODIFIER_TO_CLASS = {
23   'SHIFT': 'modifier-shift',
24   'CTRL': 'modifier-ctrl',
25   'ALT': 'modifier-alt',
26   'SEARCH': 'modifier-search'
29 var IDENTIFIER_TO_CLASS = {
30   '2A': 'is-shift',
31   '1D': 'is-ctrl',
32   '38': 'is-alt',
33   'E0 5B': 'is-search'
36 var LABEL_TO_IDENTIFIER = {
37   'search': 'E0 5B',
38   'ctrl': '1D',
39   'alt': '38',
40   'caps lock': '3A',
41   'esc': '01',
42   'disabled': 'DISABLED'
45 var KEYCODE_TO_LABEL = {
46   8: 'backspace',
47   9: 'tab',
48   13: 'enter',
49   27: 'esc',
50   32: 'space',
51   33: 'pageup',
52   34: 'pagedown',
53   35: 'end',
54   36: 'home',
55   37: 'left',
56   38: 'up',
57   39: 'right',
58   40: 'down',
59   46: 'delete',
60   91: 'search',
61   92: 'search',
62   96: '0',
63   97: '1',
64   98: '2',
65   99: '3',
66   100: '4',
67   101: '5',
68   102: '6',
69   103: '7',
70   104: '8',
71   105: '9',
72   106: '*',
73   107: '+',
74   109: '-',
75   110: '.',
76   111: '/',
77   112: 'back',
78   113: 'forward',
79   114: 'reload',
80   115: 'full screen',
81   116: 'switch window',
82   117: 'bright down',
83   118: 'bright up',
84   119: 'mute',
85   120: 'vol. down',
86   121: 'vol. up',
87   186: ';',
88   187: '+',
89   188: ',',
90   189: '-',
91   190: '.',
92   191: '/',
93   192: '`',
94   219: '[',
95   220: '\\',
96   221: ']',
97   222: '\'',
100 var keyboardOverlayId = 'en_US';
101 var identifierMap = {};
104  * Returns the layout name.
105  * @return {string} layout name.
106  */
107 function getLayoutName() {
108   return getKeyboardGlyphData().layoutName;
112  * Returns layout data.
113  * @return {Array} Keyboard layout data.
114  */
115 function getLayout() {
116   return keyboardOverlayData['layouts'][getLayoutName()];
119 // Cache the shortcut data after it is constructed.
120 var shortcutDataCache;
123  * Returns shortcut data.
124  * @return {Object} Keyboard shortcut data.
125  */
126 function getShortcutData() {
127   if (shortcutDataCache)
128     return shortcutDataCache;
130   shortcutDataCache = keyboardOverlayData['shortcut'];
132   if (!isDisplayUIScalingEnabled()) {
133     // Zoom screen in
134     delete shortcutDataCache['+<>CTRL<>SHIFT'];
135     // Zoom screen out
136     delete shortcutDataCache['-<>CTRL<>SHIFT'];
137     // Reset screen zoom
138     delete shortcutDataCache['0<>CTRL<>SHIFT'];
139   }
141   return shortcutDataCache;
145  * Returns the keyboard overlay ID.
146  * @return {string} Keyboard overlay ID.
147  */
148 function getKeyboardOverlayId() {
149   return keyboardOverlayId;
153  * Returns keyboard glyph data.
154  * @return {Object} Keyboard glyph data.
155  */
156 function getKeyboardGlyphData() {
157   return keyboardOverlayData['keyboardGlyph'][getKeyboardOverlayId()];
161  * Converts a single hex number to a character.
162  * @param {string} hex Hexadecimal string.
163  * @return {string} Unicode values of hexadecimal string.
164  */
165 function hex2char(hex) {
166   if (!hex) {
167     return '';
168   }
169   var result = '';
170   var n = parseInt(hex, 16);
171   if (n <= 0xFFFF) {
172     result += String.fromCharCode(n);
173   } else if (n <= 0x10FFFF) {
174     n -= 0x10000;
175     result += (String.fromCharCode(0xD800 | (n >> 10)) +
176                String.fromCharCode(0xDC00 | (n & 0x3FF)));
177   } else {
178     console.error('hex2Char error: Code point out of range :' + hex);
179   }
180   return result;
183 var searchIsPressed = false;
186  * Returns a list of modifiers from the key event.
187  * @param {Event} e The key event.
188  * @return {Array} List of modifiers based on key event.
189  */
190 function getModifiers(e) {
191   if (!e)
192     return [];
194   var isKeyDown = (e.type == 'keydown');
195   var keyCodeToModifier = {
196     16: 'SHIFT',
197     17: 'CTRL',
198     18: 'ALT',
199     91: 'SEARCH',
200   };
201   var modifierWithKeyCode = keyCodeToModifier[e.keyCode];
202   var isPressed = {
203       'SHIFT': e.shiftKey,
204       'CTRL': e.ctrlKey,
205       'ALT': e.altKey,
206       'SEARCH': searchIsPressed
207   };
208   if (modifierWithKeyCode)
209     isPressed[modifierWithKeyCode] = isKeyDown;
211   searchIsPressed = isPressed['SEARCH'];
213   // make the result array
214   return ['SHIFT', 'CTRL', 'ALT', 'SEARCH'].filter(
215       function(modifier) {
216         return isPressed[modifier];
217       }).sort();
221  * Returns an ID of the key.
222  * @param {string} identifier Key identifier.
223  * @param {number} i Key number.
224  * @return {string} Key ID.
225  */
226 function keyId(identifier, i) {
227   return identifier + '-key-' + i;
231  * Returns an ID of the text on the key.
232  * @param {string} identifier Key identifier.
233  * @param {number} i Key number.
234  * @return {string} Key text ID.
235  */
236 function keyTextId(identifier, i) {
237   return identifier + '-key-text-' + i;
241  * Returns an ID of the shortcut text.
242  * @param {string} identifier Key identifier.
243  * @param {number} i Key number.
244  * @return {string} Key shortcut text ID.
245  */
246 function shortcutTextId(identifier, i) {
247   return identifier + '-shortcut-text-' + i;
251  * Returns true if |list| contains |e|.
252  * @param {Array} list Container list.
253  * @param {string} e Element string.
254  * @return {boolean} Returns true if the list contains the element.
255  */
256 function contains(list, e) {
257   return list.indexOf(e) != -1;
261  * Returns a list of the class names corresponding to the identifier and
262  * modifiers.
263  * @param {string} identifier Key identifier.
264  * @param {Array} modifiers List of key modifiers.
265  * @return {Array} List of class names corresponding to specified params.
266  */
267 function getKeyClasses(identifier, modifiers) {
268   var classes = ['keyboard-overlay-key'];
269   for (var i = 0; i < modifiers.length; ++i) {
270     classes.push(MODIFIER_TO_CLASS[modifiers[i]]);
271   }
273   if ((identifier == '2A' && contains(modifiers, 'SHIFT')) ||
274       (identifier == '1D' && contains(modifiers, 'CTRL')) ||
275       (identifier == '38' && contains(modifiers, 'ALT')) ||
276       (identifier == 'E0 5B' && contains(modifiers, 'SEARCH'))) {
277     classes.push('pressed');
278     classes.push(IDENTIFIER_TO_CLASS[identifier]);
279   }
280   return classes;
284  * Returns true if a character is a ASCII character.
285  * @param {string} c A character to be checked.
286  * @return {boolean} True if the character is an ASCII character.
287  */
288 function isAscii(c) {
289   var charCode = c.charCodeAt(0);
290   return 0x00 <= charCode && charCode <= 0x7F;
294  * Returns a remapped identiifer based on the preference.
295  * @param {string} identifier Key identifier.
296  * @return {string} Remapped identifier.
297  */
298 function remapIdentifier(identifier) {
299   return identifierMap[identifier] || identifier;
303  * Returns a label of the key.
304  * @param {string} keyData Key glyph data.
305  * @param {Array} modifiers Key Modifier list.
306  * @return {string} Label of the key.
307  */
308 function getKeyLabel(keyData, modifiers) {
309   if (!keyData) {
310     return '';
311   }
312   if (keyData.label) {
313     return keyData.label;
314   }
315   var keyLabel = '';
316   for (var j = 1; j <= 9; j++) {
317     var pos = keyData['p' + j];
318     if (!pos) {
319       continue;
320     }
321     keyLabel = hex2char(pos);
322     if (!keyLabel) {
323       continue;
324      }
325     if (isAscii(keyLabel) &&
326         getShortcutData()[getAction(keyLabel, modifiers)]) {
327       break;
328     }
329   }
330   return keyLabel;
334  * Returns a normalized string used for a key of shortcutData.
336  * Examples:
337  *   keyCode: 'd', modifiers: ['CTRL', 'SHIFT'] => 'd<>CTRL<>SHIFT'
338  *   keyCode: 'alt', modifiers: ['ALT', 'SHIFT'] => 'ALT<>SHIFT'
340  * @param {string} keyCode Key code.
341  * @param {Array} modifiers Key Modifier list.
342  * @return {string} Normalized key shortcut data string.
343  */
344 function getAction(keyCode, modifiers) {
345   /** @const */ var separatorStr = '<>';
346   if (keyCode.toUpperCase() in MODIFIER_TO_CLASS) {
347     keyCode = keyCode.toUpperCase();
348     if (keyCode in modifiers) {
349       return modifiers.join(separatorStr);
350     } else {
351       var action = [keyCode].concat(modifiers);
352       action.sort();
353       return action.join(separatorStr);
354     }
355   }
356   return [keyCode].concat(modifiers).join(separatorStr);
360  * Returns a text which displayed on a key.
361  * @param {string} keyData Key glyph data.
362  * @return {string} Key text value.
363  */
364 function getKeyTextValue(keyData) {
365   if (keyData.label) {
366     // Do not show text on the space key.
367     if (keyData.label == 'space') {
368       return '';
369     }
370     return keyData.label;
371   }
373   var chars = [];
374   for (var j = 1; j <= 9; ++j) {
375     var pos = keyData['p' + j];
376     if (pos && pos.length > 0) {
377       chars.push(hex2char(pos));
378     }
379   }
380   return chars.join(' ');
384  * Updates the whole keyboard.
385  * @param {Array} modifiers Key Modifier list.
386  */
387 function update(modifiers) {
388   var instructions = $('instructions');
389   if (modifiers.length == 0) {
390     instructions.style.visibility = 'visible';
391   } else {
392     instructions.style.visibility = 'hidden';
393   }
395   var keyboardGlyphData = getKeyboardGlyphData();
396   var shortcutData = getShortcutData();
397   var layout = getLayout();
398   for (var i = 0; i < layout.length; ++i) {
399     var identifier = remapIdentifier(layout[i][0]);
400     var keyData = keyboardGlyphData.keys[identifier];
401     var classes = getKeyClasses(identifier, modifiers, keyData);
402     var keyLabel = getKeyLabel(keyData, modifiers);
403     var shortcutId = shortcutData[getAction(keyLabel, modifiers)];
404     if (modifiers.length == 1 && modifiers[0] == 'SHIFT' &&
405         identifier == '2A') {
406       // Currently there is no way to identify whether the left shift or the
407       // right shift is preesed from the key event, so I assume the left shift
408       // key is pressed here and do not show keyboard shortcut description for
409       // 'Shift - Shift' (Toggle caps lock) on the left shift key, the
410       // identifier of which is '2A'.
411       // TODO(mazda): Remove this workaround (http://crosbug.com/18047)
412       shortcutId = null;
413     }
414     if (shortcutId) {
415       classes.push('is-shortcut');
416     }
418     var key = $(keyId(identifier, i));
419     key.className = classes.join(' ');
421     if (!keyData) {
422       continue;
423     }
425     var keyText = $(keyTextId(identifier, i));
426     var keyTextValue = getKeyTextValue(keyData);
427     if (keyTextValue) {
428        keyText.style.visibility = 'visible';
429     } else {
430        keyText.style.visibility = 'hidden';
431     }
432     keyText.textContent = keyTextValue;
434     var shortcutText = $(shortcutTextId(identifier, i));
435     if (shortcutId) {
436       shortcutText.style.visibility = 'visible';
437       shortcutText.textContent = loadTimeData.getString(shortcutId);
438     } else {
439       shortcutText.style.visibility = 'hidden';
440     }
442     var format = keyboardGlyphData.keys[layout[i][0]].format;
443     if (format) {
444       if (format == 'left' || format == 'right') {
445         shortcutText.style.textAlign = format;
446         keyText.style.textAlign = format;
447       }
448     }
449   }
453  * A callback function for onkeydown and onkeyup events.
454  * @param {Event} e Key event.
455  */
456 function handleKeyEvent(e) {
457   if (!getKeyboardOverlayId()) {
458     return;
459   }
460   var modifiers = getModifiers(e);
461   update(modifiers);
462   KeyboardOverlayAccessibilityHelper.maybeSpeakAllShortcuts(modifiers);
463   e.preventDefault();
467  * Initializes the layout of the keys.
468  */
469 function initLayout() {
470   // Add data for the caps lock key
471   var keys = getKeyboardGlyphData().keys;
472   if (!('3A' in keys)) {
473     keys['3A'] = {label: 'caps lock', format: 'left'};
474   }
475   // Add data for the special key representing a disabled key
476   keys['DISABLED'] = {label: 'disabled', format: 'left'};
478   var layout = getLayout();
479   var keyboard = document.body;
480   var minX = window.innerWidth;
481   var maxX = 0;
482   var minY = window.innerHeight;
483   var maxY = 0;
484   var multiplier = 1.38 * window.innerWidth / BASE_KEYBOARD.width;
485   var keyMargin = 7;
486   var offsetX = 10;
487   var offsetY = 7;
488   for (var i = 0; i < layout.length; i++) {
489     var array = layout[i];
490     var identifier = remapIdentifier(array[0]);
491     var x = Math.round((array[1] + offsetX) * multiplier);
492     var y = Math.round((array[2] + offsetY) * multiplier);
493     var w = Math.round((array[3] - keyMargin) * multiplier);
494     var h = Math.round((array[4] - keyMargin) * multiplier);
496     var key = document.createElement('div');
497     key.id = keyId(identifier, i);
498     key.className = 'keyboard-overlay-key';
499     key.style.left = x + 'px';
500     key.style.top = y + 'px';
501     key.style.width = w + 'px';
502     key.style.height = h + 'px';
504     var keyText = document.createElement('div');
505     keyText.id = keyTextId(identifier, i);
506     keyText.className = 'keyboard-overlay-key-text';
507     keyText.style.visibility = 'hidden';
508     key.appendChild(keyText);
510     var shortcutText = document.createElement('div');
511     shortcutText.id = shortcutTextId(identifier, i);
512     shortcutText.className = 'keyboard-overlay-shortcut-text';
513     shortcutText.style.visilibity = 'hidden';
514     key.appendChild(shortcutText);
515     keyboard.appendChild(key);
517     minX = Math.min(minX, x);
518     maxX = Math.max(maxX, x + w);
519     minY = Math.min(minY, y);
520     maxY = Math.max(maxY, y + h);
521   }
523   var width = maxX - minX + 1;
524   var height = maxY - minY + 1;
525   keyboard.style.width = (width + 2 * (minX + 1)) + 'px';
526   keyboard.style.height = (height + 2 * (minY + 1)) + 'px';
528   var instructions = document.createElement('div');
529   instructions.id = 'instructions';
530   instructions.className = 'keyboard-overlay-instructions';
531   instructions.style.left = ((BASE_INSTRUCTIONS.left - BASE_KEYBOARD.left) *
532                              width / BASE_KEYBOARD.width + minX) + 'px';
533   instructions.style.top = ((BASE_INSTRUCTIONS.top - BASE_KEYBOARD.top) *
534                             height / BASE_KEYBOARD.height + minY) + 'px';
535   instructions.style.width = (width * BASE_INSTRUCTIONS.width /
536                               BASE_KEYBOARD.width) + 'px';
537   instructions.style.height = (height * BASE_INSTRUCTIONS.height /
538                                BASE_KEYBOARD.height) + 'px';
540   var instructionsText = document.createElement('div');
541   instructionsText.id = 'instructions-text';
542   instructionsText.className = 'keyboard-overlay-instructions-text';
543   instructionsText.innerHTML =
544       loadTimeData.getString('keyboardOverlayInstructions');
545   instructions.appendChild(instructionsText);
546   var instructionsHideText = document.createElement('div');
547   instructionsHideText.id = 'instructions-hide-text';
548   instructionsHideText.className = 'keyboard-overlay-instructions-hide-text';
549   instructionsHideText.innerHTML =
550       loadTimeData.getString('keyboardOverlayInstructionsHide');
551   instructions.appendChild(instructionsHideText);
552   var learnMoreLinkText = document.createElement('div');
553   learnMoreLinkText.id = 'learn-more-text';
554   learnMoreLinkText.className = 'keyboard-overlay-learn-more-text';
555   learnMoreLinkText.addEventListener('click', learnMoreClicked);
556   var learnMoreLinkAnchor = document.createElement('a');
557   learnMoreLinkAnchor.href =
558       loadTimeData.getString('keyboardOverlayLearnMoreURL');
559   learnMoreLinkAnchor.textContent =
560       loadTimeData.getString('keyboardOverlayLearnMore');
561   learnMoreLinkText.appendChild(learnMoreLinkAnchor);
562   instructions.appendChild(learnMoreLinkText);
563   keyboard.appendChild(instructions);
567  * Returns true if the device has a diamond key.
568  * @return {boolean} Returns true if the device has a diamond key.
569  */
570 function hasDiamondKey() {
571   return loadTimeData.getBoolean('keyboardOverlayHasChromeOSDiamondKey');
575  * Returns true if display scaling feature is enabled.
576  * @return {boolean} True if display scaling feature is enabled.
577  */
578 function isDisplayUIScalingEnabled() {
579   return loadTimeData.getBoolean('keyboardOverlayIsDisplayUIScalingEnabled');
583  * Initializes the layout and the key labels for the keyboard that has a diamond
584  * key.
585  */
586 function initDiamondKey() {
587   var newLayoutData = {
588     '1D': [65.0, 287.0, 60.0, 60.0],  // left Ctrl
589     '38': [185.0, 287.0, 60.0, 60.0],  // left Alt
590     'E0 5B': [125.0, 287.0, 60.0, 60.0],  // search
591     '3A': [5.0, 167.0, 105.0, 60.0],  // caps lock
592     '5B': [803.0, 6.0, 72.0, 35.0],  // lock key
593     '5D': [5.0, 287.0, 60.0, 60.0]  // diamond key
594   };
596   var layout = getLayout();
597   var powerKeyIndex = -1;
598   var powerKeyId = '00';
599   for (var i = 0; i < layout.length; i++) {
600     var keyId = layout[i][0];
601     if (keyId in newLayoutData) {
602       layout[i] = [keyId].concat(newLayoutData[keyId]);
603       delete newLayoutData[keyId];
604     }
605     if (keyId == powerKeyId)
606       powerKeyIndex = i;
607   }
608   for (var keyId in newLayoutData)
609     layout.push([keyId].concat(newLayoutData[keyId]));
611   // Remove the power key.
612   if (powerKeyIndex != -1)
613     layout.splice(powerKeyIndex, 1);
615   var keyData = getKeyboardGlyphData()['keys'];
616   var newKeyData = {
617     '3A': {'label': 'caps lock', 'format': 'left'},
618     '5B': {'label': 'lock'},
619     '5D': {'label': 'diamond', 'format': 'left'}
620   };
621   for (var keyId in newKeyData)
622     keyData[keyId] = newKeyData[keyId];
626  * A callback function for the onload event of the body element.
627  */
628 function init() {
629   document.addEventListener('keydown', handleKeyEvent);
630   document.addEventListener('keyup', handleKeyEvent);
631   chrome.send('getLabelMap');
635  * Initializes the global map for remapping identifiers of modifier keys based
636  * on the preference.
637  * Called after sending the 'getLabelMap' message.
638  * @param {Object} remap Identifier map.
639  */
640 function initIdentifierMap(remap) {
641   for (var key in remap) {
642     var val = remap[key];
643     if ((key in LABEL_TO_IDENTIFIER) &&
644         (val in LABEL_TO_IDENTIFIER)) {
645       identifierMap[LABEL_TO_IDENTIFIER[key]] =
646           LABEL_TO_IDENTIFIER[val];
647     } else {
648       console.error('Invalid label map element: ' + key + ', ' + val);
649     }
650   }
651   chrome.send('getInputMethodId');
655  * Initializes the global keyboad overlay ID and the layout of keys.
656  * Called after sending the 'getInputMethodId' message.
657  * @param {inputMethodId} inputMethodId Input Method Identifier.
658  */
659 function initKeyboardOverlayId(inputMethodId) {
660   // Libcros returns an empty string when it cannot find the keyboard overlay ID
661   // corresponding to the current input method.
662   // In such a case, fallback to the default ID (en_US).
663   var inputMethodIdToOverlayId =
664       keyboardOverlayData['inputMethodIdToOverlayId'];
665   if (inputMethodId) {
666     keyboardOverlayId = inputMethodIdToOverlayId[inputMethodId];
667   }
668   if (!keyboardOverlayId) {
669     console.error('No keyboard overlay ID for ' + inputMethodId);
670     keyboardOverlayId = 'en_US';
671   }
672   while (document.body.firstChild) {
673     document.body.removeChild(document.body.firstChild);
674   }
675   // We show Japanese layout as-is because the user has chosen the layout
676   // that is quite diffrent from the physical layout that has a diamond key.
677   if (hasDiamondKey() && getLayoutName() != 'J')
678     initDiamondKey();
679   initLayout();
680   update([]);
681   window.webkitRequestAnimationFrame(function() {
682     chrome.send('didPaint');
683   });
687  * Handles click events of the learn more link.
688  * @param {Event} e Mouse click event.
689  */
690 function learnMoreClicked(e) {
691   chrome.send('openLearnMorePage');
692   chrome.send('DialogClose');
693   e.preventDefault();
696 document.addEventListener('DOMContentLoaded', init);