diff options
Diffstat (limited to 'chromium/ui/keyboard/resources/elements/kb-keyboard.js')
-rw-r--r-- | chromium/ui/keyboard/resources/elements/kb-keyboard.js | 977 |
1 files changed, 977 insertions, 0 deletions
diff --git a/chromium/ui/keyboard/resources/elements/kb-keyboard.js b/chromium/ui/keyboard/resources/elements/kb-keyboard.js new file mode 100644 index 00000000000..5933271ef26 --- /dev/null +++ b/chromium/ui/keyboard/resources/elements/kb-keyboard.js @@ -0,0 +1,977 @@ +// 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. + +/** + * The repeat delay in milliseconds before a key starts repeating. Use the + * same rate as Chromebook. + * (See chrome/browser/chromeos/language_preferences.cc) + * @const + * @type {number} + */ +var REPEAT_DELAY_MSEC = 500; + +/** + * The repeat interval or number of milliseconds between subsequent + * keypresses. Use the same rate as Chromebook. + * @const + * @type {number} + */ +var REPEAT_INTERVAL_MSEC = 50; + +/** + * The double click/tap interval. + * @const + * @type {number} + */ +var DBL_INTERVAL_MSEC = 300; + +/** + * The index of the name of the keyset when searching for all keysets. + * @const + * @type {number} + */ +var REGEX_KEYSET_INDEX = 1; + +/** + * The integer number of matches when searching for keysets. + * @const + * @type {number} + */ +var REGEX_MATCH_COUNT = 2; + +/** + * The boolean to decide if keyboard should transit to upper case keyset + * when spacebar is pressed. If a closing punctuation is followed by a + * spacebar, keyboard should automatically transit to upper case. + * @type {boolean} + */ +var enterUpperOnSpace = false; + +/** + * A structure to track the currently repeating key on the keyboard. + */ +var repeatKey = { + + /** + * The timer for the delay before repeating behaviour begins. + * @type {number|undefined} + */ + timer: undefined, + + /** + * The interval timer for issuing keypresses of a repeating key. + * @type {number|undefined} + */ + interval: undefined, + + /** + * The key which is currently repeating. + * @type {BaseKey|undefined} + */ + key: undefined, + + /** + * Cancel the repeat timers of the currently active key. + */ + cancel: function() { + clearTimeout(this.timer); + clearInterval(this.interval); + this.timer = undefined; + this.interval = undefined; + this.key = undefined; + } +}; + +/** + * The minimum movement interval needed to trigger cursor move on + * horizontal and vertical way. + * @const + * @type {number} + */ +var MIN_SWIPE_DIST_X = 50; +var MIN_SWIPE_DIST_Y = 20; + +/** + * The maximum swipe distance that will trigger hintText of a key + * to be typed. + * @const + * @type {number} + */ +var MAX_SWIPE_FLICK_DIST = 60; + +/** + * The boolean to decide if it is swipe in process or finished. + * @type {boolean} + */ +var swipeInProgress = false; + +// Flag values for ctrl, alt and shift as defined by EventFlags +// in "event_constants.h". +// @enum {number} +var Modifier = { + NONE: 0, + ALT: 8, + CONTROL: 4, + SHIFT: 2 +}; + +/** + * A structure to track the current swipe status. + */ +var swipeTracker = { + /** + * The latest PointerMove event in the swipe. + * @type {Object} + */ + currentEvent: undefined, + + /** + * Whether or not a swipe changes direction. + * @type {false} + */ + isComplex: false, + + /** + * The count of horizontal and vertical movement. + * @type {number} + */ + offset_x : 0, + offset_y : 0, + + /** + * Last touch coordinate. + * @type {number} + */ + pre_x : 0, + pre_y : 0, + + /** + * The PointerMove event which triggered the swipe. + * @type {Object} + */ + startEvent: undefined, + + /** + * The flag of current modifier key. + * @type {number} + */ + swipeFlags : 0, + + /** + * Current swipe direction. + * @type {number} + */ + swipeDirection : 0, + + /** + * The number of times we've swiped within a single swipe. + * @type {number} + */ + swipeIndex: 0, + + /** + * Returns the combined direction of the x and y offsets. + * @return {number} The latest direction. + */ + getOffsetDirection: function() { + // TODO (rsadam): Use angles to figure out the direction. + var direction = 0; + // Checks for horizontal swipe. + if (Math.abs(this.offset_x) > MIN_SWIPE_DIST_X) { + if (this.offset_x > 0) { + direction |= SwipeDirection.RIGHT; + } else { + direction |= SwipeDirection.LEFT; + } + } + // Checks for vertical swipe. + if (Math.abs(this.offset_y) > MIN_SWIPE_DIST_Y) { + if (this.offset_y < 0) { + direction |= SwipeDirection.UP; + } else { + direction |= SwipeDirection.DOWN; + } + } + return direction; + }, + + /** + * Populates the swipe update details. + * @param {boolean} endSwipe Whether this is the final event for this + * swipe. + * @return {Object} The current state of the swipeTracker. + */ + populateDetails: function(endSwipe) { + var detail = {}; + detail.direction = this.swipeDirection; + detail.index = this.swipeIndex; + detail.status = this.swipeStatus; + detail.endSwipe = endSwipe; + detail.startEvent = this.startEvent; + detail.currentEvent = this.currentEvent; + detail.isComplex = this.isComplex; + return detail; + }, + + /** + * Reset all the values when swipe finished. + */ + resetAll: function() { + this.offset_x = 0; + this.offset_y = 0; + this.pre_x = 0; + this.pre_y = 0; + this.swipeFlags = 0; + this.swipeDirection = 0; + this.swipeIndex = 0; + this.startEvent = undefined; + this.currentEvent = undefined; + this.isComplex = false; + }, + + /** + * Updates the swipe path with the current event. + * @param {Object} event The PointerEvent that triggered this update. + * @return {boolean} Whether or not to notify swipe observers. + */ + update: function(event) { + if(!event.isPrimary) + return false; + // Update priors. + this.offset_x += event.screenX - this.pre_x; + this.offset_y += event.screenY - this.pre_y; + this.pre_x = event.screenX; + this.pre_y = event.screenY; + + // Check if movement crosses minimum thresholds in each direction. + var direction = this.getOffsetDirection(); + if (direction == 0) + return false; + // If swipeIndex is zero the current event is triggering the swipe. + if (this.swipeIndex == 0) { + this.startEvent = event; + } else if (direction != this.swipeDirection) { + // Toggle the isComplex flag. + this.isComplex = true; + } + // Update the swipe tracker. + this.swipeDirection = direction; + this.offset_x = 0; + this.offset_y = 0; + this.currentEvent = event; + this.swipeIndex++; + return true; + }, + +}; + +Polymer('kb-keyboard', { + alt: null, + config: null, + control: null, + dblDetail_: null, + dblTimer_: null, + inputType: null, + lastPressedKey: null, + shift: null, + sounds: {}, + stale: true, + swipeHandler: null, + voiceInput_: null, + //TODO(rsadam@): Add a control to let users change this. + volume: DEFAULT_VOLUME, + + /** + * The default input type to keyboard layout map. The key must be one of + * the input box type values. + * @type {object} + */ + inputTypeToLayoutMap: { + number: "numeric", + text: "qwerty", + password: "qwerty" + }, + + /** + * Caches the specified sound on the keyboard. + * @param {string} soundId The name of the .wav file in the "sounds" + directory. + */ + addSound: function(soundId) { + // Check if already loaded. + if (soundId == Sound.NONE || this.sounds[soundId]) + return; + var pool = []; + for (var i = 0; i < SOUND_POOL_SIZE; i++) { + var audio = document.createElement('audio'); + audio.preload = "auto"; + audio.id = soundId; + audio.src = "../sounds/" + soundId + ".wav"; + audio.volume = this.volume; + pool.push(audio); + } + this.sounds[soundId] = pool; + }, + + /** + * Changes the current keyset. + * @param {Object} detail The detail of the event that called this + * function. + */ + changeKeyset: function(detail) { + if (detail.relegateToShift && this.shift) { + this.keyset = this.shift.textKeyset; + this.activeKeyset.nextKeyset = undefined; + return true; + } + var toKeyset = detail.toKeyset; + if (toKeyset) { + this.keyset = toKeyset; + this.activeKeyset.nextKeyset = detail.nextKeyset; + return true; + } + return false; + }, + + keysetChanged: function() { + var keyset = this.activeKeyset; + // Show the keyset if it has been initialized. + if (keyset) + keyset.show(); + }, + + configChanged: function() { + this.layout = this.config.layout; + }, + + ready: function() { + this.voiceInput_ = new VoiceInput(this); + this.swipeHandler = this.move.bind(this); + var self = this; + getKeyboardConfig(function(config) { + self.config = config; + }); + }, + + /** + * Registers a callback for state change events. + * @param{!Function} callback Callback function to register. + */ + addKeysetChangedObserver: function(callback) { + this.addEventListener('stateChange', callback); + }, + + /** + * Called when the type of focused input box changes. If a keyboard layout + * is defined for the current input type, that layout will be loaded. + * Otherwise, the keyboard layout for 'text' type will be loaded. + */ + inputTypeChanged: function() { + // Disable layout switching at accessbility mode. + if (this.config && this.config.a11ymode) + return; + + // TODO(bshe): Toggle visibility of some keys in a keyboard layout + // according to the input type. + var layout = this.inputTypeToLayoutMap[this.inputType]; + if (!layout) + layout = this.inputTypeToLayoutMap.text; + this.layout = layout; + }, + + /** + * When double click/tap event is enabled, the second key-down and key-up + * events on the same key should be skipped. Return true when the event + * with |detail| should be skipped. + * @param {Object} detail The detail of key-up or key-down event. + */ + skipEvent: function(detail) { + if (this.dblDetail_) { + if (this.dblDetail_.char != detail.char) { + // The second key down is not on the same key. Double click/tap + // should be ignored. + this.dblDetail_ = null; + clearTimeout(this.dblTimer_); + } else if (this.dblDetail_.clickCount == 1) { + return true; + } + } + return false; + }, + + /** + * Handles a swipe update. + * param {Object} detail The swipe update details. + */ + onSwipeUpdate: function(detail) { + var direction = detail.direction; + if (!direction) + console.error("Swipe direction cannot be: " + direction); + // Triggers swipe editting if it's a purely horizontal swipe. + if (!(direction & (SwipeDirection.UP | SwipeDirection.DOWN))) { + // Nothing to do if the swipe has ended. + if (detail.endSwipe) + return; + var modifiers = 0; + // TODO (rsadam): This doesn't take into account index shifts caused + // by vertical swipes. + if (detail.index % 2 != 0) { + modifiers |= Modifier.SHIFT; + modifiers |= Modifier.CONTROL; + } + MoveCursor(direction, modifiers); + return; + } + // Triggers swipe hintText if it's a purely vertical swipe. + if (this.activeKeyset.flick && + !(direction & (SwipeDirection.LEFT | SwipeDirection.RIGHT))) { + // Check if event is relevant to us. + if ((!detail.endSwipe) || (detail.isComplex)) + return; + // Too long a swipe. + var distance = Math.abs(detail.startEvent.screenY - + detail.currentEvent.screenY); + if (distance > MAX_SWIPE_FLICK_DIST) + return; + var triggerKey = detail.startEvent.target; + if (triggerKey && triggerKey.onFlick) + triggerKey.onFlick(detail); + } + }, + + /** + * This function is bound to swipeHandler. Updates the current swipe + * status so that PointerEvents can be converted to Swipe events. + * @param {PointerEvent} event. + */ + move: function(event) { + if (!swipeTracker.update(event)) + return; + // Conversion was successful, swipe is now in progress. + swipeInProgress = true; + if (this.lastPressedKey) { + this.lastPressedKey.classList.remove('active'); + this.lastPressedKey = null; + } + this.onSwipeUpdate(swipeTracker.populateDetails(false)); + }, + + /** + * Handles key-down event that is sent by kb-key-base. + * @param {CustomEvent} event The key-down event dispatched by + * kb-key-base. + * @param {Object} detail The detail of pressed kb-key. + */ + keyDown: function(event, detail) { + if (this.skipEvent(detail)) + return; + + if (this.lastPressedKey) { + this.lastPressedKey.classList.remove('active'); + this.lastPressedKey.autoRelease(); + } + this.lastPressedKey = event.target; + this.lastPressedKey.classList.add('active'); + repeatKey.cancel(); + this.playSound(detail.sound); + + var char = detail.char; + switch(char) { + case 'Shift': + this.classList.remove('caps-locked'); + break; + case 'Alt': + case 'Ctrl': + var modifier = char.toLowerCase() + "-active"; + // Removes modifier if already active. + if (this.classList.contains(modifier)) + this.classList.remove(modifier); + break; + case 'Invalid': + // Not all Invalid keys are transition keys. Reset control keys if + // we pressed a transition key. + if (event.target.toKeyset || detail.relegateToShift) + this.onNonControlKeyTyped(); + break; + default: + // Notify shift key. + if (this.shift) + this.shift.onNonControlKeyDown(); + if (this.ctrl) + this.ctrl.onNonControlKeyDown(); + if (this.alt) + this.alt.onNonControlKeyDown(); + break; + } + if(this.changeKeyset(detail)) + return; + if (detail.repeat) { + this.keyTyped(detail); + this.onNonControlKeyTyped(); + repeatKey.key = this.lastPressedKey; + var self = this; + repeatKey.timer = setTimeout(function() { + repeatKey.timer = undefined; + repeatKey.interval = setInterval(function() { + self.playSound(detail.sound); + self.keyTyped(detail); + }, REPEAT_INTERVAL_MSEC); + }, Math.max(0, REPEAT_DELAY_MSEC - REPEAT_INTERVAL_MSEC)); + } + }, + + /** + * Handles key-out event that is sent by kb-shift-key. + * @param {CustomEvent} event The key-out event dispatched by + * kb-shift-key. + * @param {Object} detail The detail of pressed kb-shift-key. + */ + keyOut: function(event, detail) { + this.changeKeyset(detail); + }, + + /** + * Enable/start double click/tap event recognition. + * @param {CustomEvent} event The enable-dbl event dispatched by + * kb-shift-key. + * @param {Object} detail The detail of pressed kb-shift-key. + */ + enableDbl: function(event, detail) { + if (!this.dblDetail_) { + this.dblDetail_ = detail; + this.dblDetail_.clickCount = 0; + var self = this; + this.dblTimer_ = setTimeout(function() { + self.dblDetail_.callback = null; + self.dblDetail_ = null; + }, DBL_INTERVAL_MSEC); + } + }, + + /** + * Enable the selection while swipe. + * @param {CustomEvent} event The enable-dbl event dispatched by + * kb-shift-key. + */ + enableSel: function(event) { + // TODO(rsadam): Disabled for now. May come back if we revert swipe + // selection to not do word selection. + }, + + /** + * Handles pointerdown event. This is used for swipe selection process. + * to get the start pre_x and pre_y. And also add a pointermove handler + * to start handling the swipe selection event. + * @param {PointerEvent} event The pointerup event that received by + * kb-keyboard. + */ + down: function(event) { + var layout = getKeysetLayout(this.activeKeysetId); + var key = layout.findClosestKey(event.clientX, event.clientY); + if (key) + key.down(event); + if (event.isPrimary) { + swipeTracker.pre_x = event.screenX; + swipeTracker.pre_y = event.screenY; + this.addEventListener("pointermove", this.swipeHandler, false); + } + }, + + /** + * Handles pointerup event. This is used for double tap/click events. + * @param {PointerEvent} event The pointerup event that bubbled to + * kb-keyboard. + */ + up: function(event) { + var layout = getKeysetLayout(this.activeKeysetId); + var key = layout.findClosestKey(event.clientX, event.clientY); + if (key) + key.up(event); + // When touch typing, it is very possible that finger moves slightly out + // of the key area before releases. The key should not be dropped in + // this case. + // TODO(rsadam@) Change behaviour such that the key drops and the second + // key gets pressed. + if (this.lastPressedKey && + this.lastPressedKey.pointerId == event.pointerId) { + this.lastPressedKey.autoRelease(); + } + + if (this.dblDetail_) { + this.dblDetail_.clickCount++; + if (this.dblDetail_.clickCount == 2) { + this.dblDetail_.callback(); + this.changeKeyset(this.dblDetail_); + clearTimeout(this.dblTimer_); + + this.classList.add('caps-locked'); + + this.dblDetail_ = null; + } + } + + // TODO(zyaozhujun): There are some edge cases to deal with later. + // (for instance, what if a second finger trigger a down and up + // event sequence while swiping). + // When pointer up from the screen, a swipe selection session finished, + // all the data should be reset to prepare for the next session. + if (event.isPrimary && swipeInProgress) { + swipeInProgress = false; + this.onSwipeUpdate(swipeTracker.populateDetails(true)) + swipeTracker.resetAll(); + } + this.removeEventListener('pointermove', this.swipeHandler, false); + }, + + /** + * Handles PointerOut event. This is used for when a swipe gesture goes + * outside of the keyboard window. + * @param {Object} event The pointerout event that bubbled to the + * kb-keyboard. + */ + out: function(event) { + repeatKey.cancel(); + // Ignore if triggered from one of the keys. + if (this.compareDocumentPosition(event.relatedTarget) & + Node.DOCUMENT_POSITION_CONTAINED_BY) + return; + if (swipeInProgress) + this.onSwipeUpdate(swipeTracker.populateDetails(true)) + // Touched outside of the keyboard area, so disables swipe. + swipeInProgress = false; + swipeTracker.resetAll(); + this.removeEventListener('pointermove', this.swipeHandler, false); + }, + + /** + * Handles a TypeKey event. This is used for when we programmatically + * want to type a specific key. + * @param {CustomEvent} event The TypeKey event that bubbled to the + * kb-keyboard. + */ + type: function(event) { + this.keyTyped(event.detail); + }, + + /** + * Handles key-up event that is sent by kb-key-base. + * @param {CustomEvent} event The key-up event dispatched by kb-key-base. + * @param {Object} detail The detail of pressed kb-key. + */ + keyUp: function(event, detail) { + if (this.skipEvent(detail)) + return; + if (swipeInProgress) + return; + if (detail.activeModifier) { + var modifier = detail.activeModifier.toLowerCase() + "-active"; + this.classList.add(modifier); + } + // Adds the current keyboard modifiers to the detail. + if (this.ctrl) + detail.controlModifier = this.ctrl.isActive(); + if (this.alt) + detail.altModifier = this.alt.isActive(); + if (this.lastPressedKey) + this.lastPressedKey.classList.remove('active'); + // Keyset transition key. This is needed to transition from upper + // to lower case when we are not in caps mode, as well as when + // we're ending chording. + this.changeKeyset(detail); + + if (this.lastPressedKey && + this.lastPressedKey.charValue != event.target.charValue) { + return; + } + if (repeatKey.key == event.target) { + repeatKey.cancel(); + this.lastPressedKey = null; + return; + } + var toLayoutId = detail.toLayout; + // Layout transition key. + if (toLayoutId) + this.layout = toLayoutId; + var char = detail.char; + this.lastPressedKey = null; + // Characters that should not be typed. + switch(char) { + case 'Invalid': + case 'Shift': + case 'Ctrl': + case 'Alt': + enterUpperOnSpace = false; + swipeTracker.swipeFlags = 0; + return; + case 'Microphone': + this.voiceInput_.onDown(); + return; + default: + break; + } + // Tries to type the character. Resorts to insertText if that fails. + if(!this.keyTyped(detail)) + insertText(char); + // Post-typing logic. + switch(char) { + case '\n': + case ' ': + if(enterUpperOnSpace) { + enterUpperOnSpace = false; + if (this.shift) { + var shiftDetail = this.shift.onSpaceAfterPunctuation(); + // Check if transition defined. + this.changeKeyset(shiftDetail); + } else { + console.error('Capitalization on space after punctuation \ + enabled, but cannot find target keyset.'); + } + // Immediately return to maintain shift-state. Space is a + // non-control key and would otherwise trigger a reset of the + // shift key, causing a transition to lower case. + return; + } + break; + case '.': + case '?': + case '!': + enterUpperOnSpace = this.shouldUpperOnSpace(); + break; + default: + enterUpperOnSpace = false; + break; + } + // Reset control keys. + this.onNonControlKeyTyped(); + }, + + /* + * Handles key-longpress event that is sent by kb-key-base. + * @param {CustomEvent} event The key-longpress event dispatched by + * kb-key-base. + * @param {Object} detail The detail of pressed key. + */ + keyLongpress: function(event, detail) { + // If the gesture is long press, remove the pointermove listener. + this.removeEventListener('pointermove', this.swipeHandler, false); + // Keyset transtion key. + if (this.changeKeyset(detail)) { + // Locks the keyset before removing active to prevent flicker. + this.classList.add('caps-locked'); + // Makes last pressed key inactive if transit to a new keyset on long + // press. + if (this.lastPressedKey) + this.lastPressedKey.classList.remove('active'); + } + }, + + /** + * Plays the specified sound. + * @param {Sound} sound The id of the audio tag. + */ + playSound: function(sound) { + if (!SOUND_ENABLED || !sound || sound == Sound.NONE) + return; + var pool = this.sounds[sound]; + if (!pool) { + console.error("Cannot find audio tag: " + sound); + return; + } + // Search the sound pool for a free resource. + for (var i = 0; i < pool.length; i++) { + if (pool[i].paused) { + pool[i].play(); + return; + } + } + }, + + /** + * Whether we should transit to upper case when seeing a space after + * punctuation. + * @return {boolean} + */ + shouldUpperOnSpace: function() { + // TODO(rsadam): Add other input types in which we should not + // transition to upper after a space. + return this.inputTypeValue != 'password'; + }, + + /** + * Handler for the 'set-layout' event. + * @param {!Event} event The triggering event. + * @param {{layout: string}} details Details of the event, which contains + * the name of the layout to activate. + */ + setLayout: function(event, details) { + this.layout = details.layout; + }, + + /** + * Handles a change in the keyboard layout. Auto-selects the default + * keyset for the new layout. + */ + layoutChanged: function() { + this.stale = true; + if (!this.selectDefaultKeyset()) { + console.error('No default keyset found for layout: ' + this.layout); + return; + } + this.activeKeyset.show(); + }, + + /** + * Notifies the modifier keys that a non-control key was typed. This + * lets them reset sticky behaviour. A non-control key is defined as + * any key that is not Control, Alt, or Shift. + */ + onNonControlKeyTyped: function() { + if (this.shift) + this.shift.onNonControlKeyTyped(); + if (this.ctrl) + this.ctrl.onNonControlKeyTyped(); + if (this.alt) + this.alt.onNonControlKeyTyped(); + this.classList.remove('ctrl-active'); + this.classList.remove('alt-active'); + }, + + /** + * Callback function for when volume is changed. + */ + volumeChanged: function() { + var toChange = Object.keys(this.sounds); + for (var i = 0; i < toChange.length; i++) { + var pool = this.sounds[toChange[i]]; + for (var j = 0; j < pool.length; j++) { + pool[j].volume = this.volume; + } + } + }, + + /** + * Id for the active keyset. + * @type {string} + */ + get activeKeysetId() { + return this.layout + '-' + this.keyset; + }, + + /** + * The active keyset DOM object. + * @type {kb-keyset} + */ + get activeKeyset() { + return this.querySelector('#' + this.activeKeysetId); + }, + + /** + * The current input type. + * @type {string} + */ + get inputTypeValue() { + return this.inputType; + }, + + /** + * Changes the input type if it's different from the current + * type, else resets the keyset to the default keyset. + * @type {string} + */ + set inputTypeValue(value) { + if (value == this.inputType) + this.selectDefaultKeyset(); + else + this.inputType = value; + }, + + /** + * The keyboard is ready for input once the target keyset appears + * in the distributed nodes for the keyboard. + * @return {boolean} Indicates if the keyboard is ready for input. + */ + isReady: function() { + var keyset = this.activeKeyset; + if (!keyset) + return false; + var nodes = this.$.content.getDistributedNodes(); + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].id && nodes[i].id == keyset.id) + return true; + } + return false; + }, + + /** + * Generates fabricated key events to simulate typing on a + * physical keyboard. + * @param {Object} detail Attributes of the key being typed. + * @return {boolean} Whether the key type succeeded. + */ + keyTyped: function(detail) { + var builder = this.$.keyCodeMetadata; + if (this.ctrl) + detail.controlModifier = this.ctrl.isActive(); + if (this.alt) + detail.altModifier = this.alt.isActive(); + var downEvent = builder.createVirtualKeyEvent(detail, "keydown"); + if (downEvent) { + sendKeyEvent(downEvent); + sendKeyEvent(builder.createVirtualKeyEvent(detail, "keyup")); + return true; + } + return false; + }, + + /** + * Selects the default keyset for a layout. + * @return {boolean} True if successful. This method can fail if the + * keysets corresponding to the layout have not been injected. + */ + selectDefaultKeyset: function() { + var keysets = this.querySelectorAll('kb-keyset'); + // Full name of the keyset is of the form 'layout-keyset'. + var regex = new RegExp('^' + this.layout + '-(.+)'); + var keysetsLoaded = false; + for (var i = 0; i < keysets.length; i++) { + var matches = keysets[i].id.match(regex); + if (matches && matches.length == REGEX_MATCH_COUNT) { + keysetsLoaded = true; + // Without both tests for a default keyset, it is possible to get + // into a state where multiple layouts are displayed. A + // reproducable test case is do the following set of keyset + // transitions: qwerty -> system -> dvorak -> qwerty. + // TODO(kevers): Investigate why this is the case. + if (keysets[i].isDefault || + keysets[i].getAttribute('isDefault') == 'true') { + this.keyset = matches[REGEX_KEYSET_INDEX]; + this.classList.remove('caps-locked'); + this.classList.remove('alt-active'); + this.classList.remove('ctrl-active'); + // Caches shift key. + this.shift = this.querySelector('kb-shift-key'); + if (this.shift) + this.shift.reset(); + // Caches control key. + this.ctrl = this.querySelector('kb-modifier-key[char=Ctrl]'); + if (this.ctrl) + this.ctrl.reset(); + // Caches alt key. + this.alt = this.querySelector('kb-modifier-key[char=Alt]'); + if (this.alt) + this.alt.reset(); + this.fire('stateChange', { + state: 'keysetLoaded', + value: this.keyset, + }); + keyboardLoaded(); + return true; + } + } + } + if (keysetsLoaded) + console.error('No default keyset found for ' + this.layout); + return false; + } +}); |