| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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 <include src="keyboard_overlay_data.js"/> | |
| 6 <include src="keyboard_overlay_accessibility_helper.js"/> | |
| 7 | |
| 8 var BASE_KEYBOARD = { | |
| 9 top: 0, | |
| 10 left: 0, | |
| 11 width: 1237, | |
| 12 height: 514 | |
| 13 }; | |
| 14 | |
| 15 var BASE_INSTRUCTIONS = { | |
| 16 top: 194, | |
| 17 left: 370, | |
| 18 width: 498, | |
| 19 height: 112 | |
| 20 }; | |
| 21 | |
| 22 var MODIFIER_TO_CLASS = { | |
| 23 'SHIFT': 'modifier-shift', | |
| 24 'CTRL': 'modifier-ctrl', | |
| 25 'ALT': 'modifier-alt' | |
| 26 }; | |
| 27 | |
| 28 var IDENTIFIER_TO_CLASS = { | |
| 29 '2A': 'is-shift', | |
| 30 '1D': 'is-ctrl', | |
| 31 '38': 'is-alt' | |
| 32 }; | |
| 33 | |
| 34 var LABEL_TO_IDENTIFIER = { | |
| 35 'search': 'E0 5B', | |
| 36 'ctrl': '1D', | |
| 37 'alt': '38', | |
| 38 'caps lock': '3A', | |
| 39 'disabled': 'DISABLED' | |
| 40 } | |
| 41 | |
| 42 var keyboardOverlayId = 'en_US'; | |
| 43 var identifierMap = {}; | |
| 44 | |
| 45 /** | |
| 46 * Returns layouts data. | |
| 47 */ | |
| 48 function getLayouts() { | |
| 49 return keyboardOverlayData['layouts']; | |
| 50 } | |
| 51 | |
| 52 /** | |
| 53 * Returns shortcut data. | |
| 54 */ | |
| 55 function getShortcutData() { | |
| 56 return keyboardOverlayData['shortcut']; | |
| 57 } | |
| 58 | |
| 59 /** | |
| 60 * Returns the keyboard overlay ID. | |
| 61 */ | |
| 62 function getKeyboardOverlayId() { | |
| 63 return keyboardOverlayId; | |
| 64 } | |
| 65 | |
| 66 /** | |
| 67 * Returns keyboard glyph data. | |
| 68 */ | |
| 69 function getKeyboardGlyphData() { | |
| 70 return keyboardOverlayData['keyboardGlyph'][getKeyboardOverlayId()]; | |
| 71 } | |
| 72 | |
| 73 /** | |
| 74 * Converts a single hex number to a character. | |
| 75 */ | |
| 76 function hex2char(hex) { | |
| 77 if (!hex) { | |
| 78 return ''; | |
| 79 } | |
| 80 var result = ''; | |
| 81 var n = parseInt(hex, 16); | |
| 82 if (n <= 0xFFFF) { | |
| 83 result += String.fromCharCode(n); | |
| 84 } else if (n <= 0x10FFFF) { | |
| 85 n -= 0x10000; | |
| 86 result += (String.fromCharCode(0xD800 | (n >> 10)) + | |
| 87 String.fromCharCode(0xDC00 | (n & 0x3FF))); | |
| 88 } else { | |
| 89 console.error('hex2Char error: Code point out of range :' + hex); | |
| 90 } | |
| 91 return result; | |
| 92 } | |
| 93 | |
| 94 /** | |
| 95 * Returns a list of modifiers from the key event. | |
| 96 */ | |
| 97 function getModifiers(e) { | |
| 98 if (!e) { | |
| 99 return []; | |
| 100 } | |
| 101 var isKeyDown = (e.type == 'keydown'); | |
| 102 var keyCodeToModifier = { | |
| 103 16: 'SHIFT', | |
| 104 17: 'CTRL', | |
| 105 18: 'ALT', | |
| 106 91: 'ALT', // left ALT pressed with SHIFT | |
| 107 92: 'ALT', // right ALT pressed with SHIFT | |
| 108 }; | |
| 109 var modifierWithKeyCode = keyCodeToModifier[e.keyCode]; | |
| 110 var isPressed = {'SHIFT': e.shiftKey, 'CTRL': e.ctrlKey, 'ALT': e.altKey}; | |
| 111 // if e.keyCode is one of Shift, Ctrl and Alt, isPressed should | |
| 112 // be changed because the key currently pressed | |
| 113 // does not affect the values of e.shiftKey, e.ctrlKey and e.altKey | |
| 114 if(modifierWithKeyCode){ | |
| 115 isPressed[modifierWithKeyCode] = isKeyDown; | |
| 116 } | |
| 117 // make the result array | |
| 118 return ['SHIFT', 'CTRL', 'ALT'].filter( | |
| 119 function(modifier) { | |
| 120 return isPressed[modifier]; | |
| 121 }).sort(); | |
| 122 } | |
| 123 | |
| 124 /** | |
| 125 * Returns an ID of the key. | |
| 126 */ | |
| 127 function keyId(identifier, i) { | |
| 128 return identifier + '-key-' + i; | |
| 129 } | |
| 130 | |
| 131 /** | |
| 132 * Returns an ID of the text on the key. | |
| 133 */ | |
| 134 function keyTextId(identifier, i) { | |
| 135 return identifier + '-key-text-' + i; | |
| 136 } | |
| 137 | |
| 138 /** | |
| 139 * Returns an ID of the shortcut text. | |
| 140 */ | |
| 141 function shortcutTextId(identifier, i) { | |
| 142 return identifier + '-shortcut-text-' + i; | |
| 143 } | |
| 144 | |
| 145 /** | |
| 146 * Returns true if |list| contains |e|. | |
| 147 */ | |
| 148 function contains(list, e) { | |
| 149 return list.indexOf(e) != -1; | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * Returns a list of the class names corresponding to the identifier and | |
| 154 * modifiers. | |
| 155 */ | |
| 156 function getKeyClasses(identifier, modifiers) { | |
| 157 var classes = ['keyboard-overlay-key']; | |
| 158 for (var i = 0; i < modifiers.length; ++i) { | |
| 159 classes.push(MODIFIER_TO_CLASS[modifiers[i]]); | |
| 160 } | |
| 161 | |
| 162 if ((identifier == '2A' && contains(modifiers, 'SHIFT')) || | |
| 163 (identifier == '1D' && contains(modifiers, 'CTRL')) || | |
| 164 (identifier == '38' && contains(modifiers, 'ALT'))) { | |
| 165 classes.push('pressed'); | |
| 166 classes.push(IDENTIFIER_TO_CLASS[identifier]); | |
| 167 } | |
| 168 return classes; | |
| 169 } | |
| 170 | |
| 171 /** | |
| 172 * Returns true if a character is a ASCII character. | |
| 173 */ | |
| 174 function isAscii(c) { | |
| 175 var charCode = c.charCodeAt(0); | |
| 176 return 0x00 <= charCode && charCode <= 0x7F; | |
| 177 } | |
| 178 | |
| 179 /** | |
| 180 * Returns a remapped identiifer based on the preference. | |
| 181 */ | |
| 182 function remapIdentifier(identifier) { | |
| 183 return identifierMap[identifier] || identifier; | |
| 184 } | |
| 185 | |
| 186 /** | |
| 187 * Returns a label of the key. | |
| 188 */ | |
| 189 function getKeyLabel(keyData, modifiers) { | |
| 190 if (!keyData) { | |
| 191 return ''; | |
| 192 } | |
| 193 if (keyData.label) { | |
| 194 return keyData.label; | |
| 195 } | |
| 196 var keyLabel = ''; | |
| 197 for (var j = 1; j <= 9; j++) { | |
| 198 var pos = keyData['p' + j]; | |
| 199 if (!pos) { | |
| 200 continue; | |
| 201 } | |
| 202 keyLabel = hex2char(pos); | |
| 203 if (!keyLabel) { | |
| 204 continue; | |
| 205 } | |
| 206 if (isAscii(keyLabel) && | |
| 207 getShortcutData()[getAction(keyLabel, modifiers)]) { | |
| 208 break; | |
| 209 } | |
| 210 } | |
| 211 return keyLabel; | |
| 212 } | |
| 213 | |
| 214 /** | |
| 215 * Returns a normalized string used for a key of shortcutData. | |
| 216 * | |
| 217 * Examples: | |
| 218 * keycode: 'd', modifiers: ['CTRL', 'SHIFT'] => 'd<>CTRL<>SHIFT' | |
| 219 * keycode: 'alt', modifiers: ['ALT', 'SHIFT'] => 'ALT<>SHIFT' | |
| 220 */ | |
| 221 function getAction(keycode, modifiers) { | |
| 222 const SEPARATOR = '<>'; | |
| 223 if (keycode.toUpperCase() in MODIFIER_TO_CLASS) { | |
| 224 keycode = keycode.toUpperCase(); | |
| 225 if (keycode in modifiers) { | |
| 226 return modifiers.join(SEPARATOR); | |
| 227 } else { | |
| 228 var action = [keycode].concat(modifiers) | |
| 229 action.sort(); | |
| 230 return action.join(SEPARATOR); | |
| 231 } | |
| 232 } | |
| 233 return [keycode].concat(modifiers).join(SEPARATOR); | |
| 234 } | |
| 235 | |
| 236 /** | |
| 237 * Returns a text which displayed on a key. | |
| 238 */ | |
| 239 function getKeyTextValue(keyData) { | |
| 240 if (keyData.label) { | |
| 241 // Do not show text on the space key. | |
| 242 if (keyData.label == 'space') { | |
| 243 return ''; | |
| 244 } | |
| 245 return keyData.label; | |
| 246 } | |
| 247 | |
| 248 var chars = []; | |
| 249 for (var j = 1; j <= 9; ++j) { | |
| 250 var pos = keyData['p' + j]; | |
| 251 if (pos && pos.length > 0) { | |
| 252 chars.push(hex2char(pos)); | |
| 253 } | |
| 254 } | |
| 255 return chars.join(' '); | |
| 256 } | |
| 257 | |
| 258 /** | |
| 259 * Updates the whole keyboard. | |
| 260 */ | |
| 261 function update(modifiers) { | |
| 262 var instructions = document.getElementById('instructions'); | |
| 263 if (modifiers.length == 0) { | |
| 264 instructions.style.visibility = 'visible'; | |
| 265 } else { | |
| 266 instructions.style.visibility = 'hidden'; | |
| 267 } | |
| 268 | |
| 269 var keyboardGlyphData = getKeyboardGlyphData(); | |
| 270 var shortcutData = getShortcutData(); | |
| 271 var layout = getLayouts()[keyboardGlyphData.layoutName]; | |
| 272 for (var i = 0; i < layout.length; ++i) { | |
| 273 var identifier = remapIdentifier(layout[i][0]); | |
| 274 var keyData = keyboardGlyphData.keys[identifier]; | |
| 275 var classes = getKeyClasses(identifier, modifiers, keyData); | |
| 276 var keyLabel = getKeyLabel(keyData, modifiers); | |
| 277 var shortcutId = shortcutData[getAction(keyLabel, modifiers)]; | |
| 278 if (modifiers.length == 1 && modifiers[0] == 'SHIFT' && | |
| 279 identifier == '2A') { | |
| 280 // Currently there is no way to identify whether the left shift or the | |
| 281 // right shift is preesed from the key event, so I assume the left shift | |
| 282 // key is pressed here and do not show keyboard shortcut description for | |
| 283 // 'Shift - Shift' (Toggle caps lock) on the left shift key, the | |
| 284 // identifier of which is '2A'. | |
| 285 // TODO(mazda): Remove this workaround (http://crosbug.com/18047) | |
| 286 shortcutId = null; | |
| 287 } | |
| 288 if (shortcutId) { | |
| 289 classes.push('is-shortcut'); | |
| 290 } | |
| 291 | |
| 292 var key = document.getElementById(keyId(identifier, i)); | |
| 293 key.className = classes.join(' '); | |
| 294 | |
| 295 if (!keyData) { | |
| 296 continue; | |
| 297 } | |
| 298 | |
| 299 var keyText = document.getElementById(keyTextId(identifier, i)); | |
| 300 var keyTextValue = getKeyTextValue(keyData); | |
| 301 if (keyTextValue) { | |
| 302 keyText.style.visibility = 'visible'; | |
| 303 } else { | |
| 304 keyText.style.visibility = 'hidden'; | |
| 305 } | |
| 306 keyText.textContent = keyTextValue; | |
| 307 | |
| 308 var shortcutText = document.getElementById(shortcutTextId(identifier, i)); | |
| 309 if (shortcutId) { | |
| 310 shortcutText.style.visibility = 'visible'; | |
| 311 shortcutText.textContent = templateData[shortcutId]; | |
| 312 } else { | |
| 313 shortcutText.style.visibility = 'hidden'; | |
| 314 } | |
| 315 | |
| 316 if (keyData.format) { | |
| 317 var format = keyData.format; | |
| 318 if (format == 'left' || format == 'right') { | |
| 319 shortcutText.style.textAlign = format; | |
| 320 keyText.style.textAlign = format; | |
| 321 } | |
| 322 } | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * A callback function for onkeydown and onkeyup events. | |
| 328 */ | |
| 329 function handleKeyEvent(e){ | |
| 330 var modifiers = getModifiers(e); | |
| 331 if (!getKeyboardOverlayId()) { | |
| 332 return; | |
| 333 } | |
| 334 update(modifiers); | |
| 335 KeyboardOverlayAccessibilityHelper.maybeSpeakAllShortcuts(modifiers); | |
| 336 } | |
| 337 | |
| 338 /** | |
| 339 * Initializes the layout of the keys. | |
| 340 */ | |
| 341 function initLayout() { | |
| 342 // Add data for the caps lock key | |
| 343 var keys = getKeyboardGlyphData().keys; | |
| 344 if (!('3A' in keys)) { | |
| 345 keys['3A'] = {label: 'caps lock', format: 'left'}; | |
| 346 } | |
| 347 // Add data for the special key representing a disabled key | |
| 348 keys['DISABLED'] = {label: 'disabled', format: 'left'}; | |
| 349 | |
| 350 var layout = getLayouts()[getKeyboardGlyphData().layoutName]; | |
| 351 var keyboard = document.body; | |
| 352 var minX = window.innerWidth; | |
| 353 var maxX = 0; | |
| 354 var minY = window.innerHeight; | |
| 355 var maxY = 0; | |
| 356 var multiplier = 1.38 * window.innerWidth / BASE_KEYBOARD.width; | |
| 357 var keyMargin = 7; | |
| 358 var offsetX = 10; | |
| 359 var offsetY = 7; | |
| 360 for (var i = 0; i < layout.length; i++) { | |
| 361 var array = layout[i]; | |
| 362 var identifier = remapIdentifier(array[0]); | |
| 363 var x = Math.round((array[1] + offsetX) * multiplier); | |
| 364 var y = Math.round((array[2] + offsetY) * multiplier); | |
| 365 var w = Math.round((array[3] - keyMargin) * multiplier); | |
| 366 var h = Math.round((array[4] - keyMargin) * multiplier); | |
| 367 | |
| 368 var key = document.createElement('div'); | |
| 369 key.id = keyId(identifier, i); | |
| 370 key.className = 'keyboard-overlay-key'; | |
| 371 key.style.left = x + 'px'; | |
| 372 key.style.top = y + 'px'; | |
| 373 key.style.width = w + 'px'; | |
| 374 key.style.height = h + 'px'; | |
| 375 | |
| 376 var keyText = document.createElement('div'); | |
| 377 keyText.id = keyTextId(identifier, i); | |
| 378 keyText.className = 'keyboard-overlay-key-text'; | |
| 379 keyText.style.visibility = 'hidden'; | |
| 380 key.appendChild(keyText); | |
| 381 | |
| 382 var shortcutText = document.createElement('div'); | |
| 383 shortcutText.id = shortcutTextId(identifier, i); | |
| 384 shortcutText.className = 'keyboard-overlay-shortcut-text'; | |
| 385 shortcutText.style.visilibity = 'hidden'; | |
| 386 key.appendChild(shortcutText); | |
| 387 keyboard.appendChild(key); | |
| 388 | |
| 389 minX = Math.min(minX, x); | |
| 390 maxX = Math.max(maxX, x + w); | |
| 391 minY = Math.min(minY, y); | |
| 392 maxY = Math.max(maxY, y + h); | |
| 393 } | |
| 394 | |
| 395 var width = maxX - minX + 1; | |
| 396 var height = maxY - minY + 1; | |
| 397 keyboard.style.width = (width + 2 * (minX + 1)) + 'px'; | |
| 398 keyboard.style.height = (height + 2 * (minY + 1)) + 'px'; | |
| 399 | |
| 400 var instructions = document.createElement('div'); | |
| 401 instructions.id = 'instructions'; | |
| 402 instructions.className = 'keyboard-overlay-instructions'; | |
| 403 instructions.style.left = ((BASE_INSTRUCTIONS.left - BASE_KEYBOARD.left) * | |
| 404 width / BASE_KEYBOARD.width + minX) + 'px'; | |
| 405 instructions.style.top = ((BASE_INSTRUCTIONS.top - BASE_KEYBOARD.top) * | |
| 406 height / BASE_KEYBOARD.height + minY) + 'px'; | |
| 407 instructions.style.width = (width * BASE_INSTRUCTIONS.width / | |
| 408 BASE_KEYBOARD.width) + 'px'; | |
| 409 instructions.style.height = (height * BASE_INSTRUCTIONS.height / | |
| 410 BASE_KEYBOARD.height) + 'px'; | |
| 411 | |
| 412 var instructionsText = document.createElement('div'); | |
| 413 instructionsText.id = 'instructions-text'; | |
| 414 instructionsText.className = 'keyboard-overlay-instructions-text'; | |
| 415 instructionsText.innerHTML = templateData.keyboardOverlayInstructions; | |
| 416 instructions.appendChild(instructionsText); | |
| 417 var instructionsHideText = document.createElement('div'); | |
| 418 instructionsHideText.id = 'instructions-hide-text'; | |
| 419 instructionsHideText.className = 'keyboard-overlay-instructions-hide-text'; | |
| 420 instructionsHideText.innerHTML = templateData.keyboardOverlayInstructionsHide; | |
| 421 instructions.appendChild(instructionsHideText); | |
| 422 keyboard.appendChild(instructions); | |
| 423 } | |
| 424 | |
| 425 /** | |
| 426 * A callback function for the onload event of the body element. | |
| 427 */ | |
| 428 function init() { | |
| 429 document.addEventListener('keydown', handleKeyEvent); | |
| 430 document.addEventListener('keyup', handleKeyEvent); | |
| 431 chrome.send('getLabelMap'); | |
| 432 } | |
| 433 | |
| 434 /** | |
| 435 * Initializes the global map for remapping identifiers of modifier keys based | |
| 436 * on the preference. | |
| 437 * Called after sending the 'getLabelMap' message. | |
| 438 */ | |
| 439 function initIdentifierMap(remap) { | |
| 440 for (var key in remap) { | |
| 441 var val = remap[key]; | |
| 442 if ((key in LABEL_TO_IDENTIFIER) && | |
| 443 (val in LABEL_TO_IDENTIFIER)) { | |
| 444 identifierMap[LABEL_TO_IDENTIFIER[key]] = | |
| 445 LABEL_TO_IDENTIFIER[val]; | |
| 446 } else { | |
| 447 console.error('Invalid label map element: ' + key + ', ' + val); | |
| 448 } | |
| 449 } | |
| 450 chrome.send('getInputMethodId'); | |
| 451 } | |
| 452 | |
| 453 /** | |
| 454 * Initializes the global keyboad overlay ID and the layout of keys. | |
| 455 * Called after sending the 'getInputMethodId' message. | |
| 456 */ | |
| 457 function initKeyboardOverlayId(inputMethodId) { | |
| 458 // Libcros returns an empty string when it cannot find the keyboard overlay ID | |
| 459 // corresponding to the current input method. | |
| 460 // In such a case, fallback to the default ID (en_US). | |
| 461 var inputMethodIdToOverlayId = keyboardOverlayData['inputMethodIdToOverlayId'] | |
| 462 if (inputMethodId) { | |
| 463 keyboardOverlayId = inputMethodIdToOverlayId[inputMethodId]; | |
| 464 } | |
| 465 if (!keyboardOverlayId) { | |
| 466 console.error('No keyboard overlay ID for ' + inputMethodId); | |
| 467 keyboardOverlayId = 'en_US'; | |
| 468 } | |
| 469 while(document.body.firstChild) { | |
| 470 document.body.removeChild(document.body.firstChild); | |
| 471 } | |
| 472 initLayout(); | |
| 473 update([]); | |
| 474 } | |
| 475 | |
| 476 document.addEventListener('DOMContentLoaded', init); | |
| OLD | NEW |