blob: 0ba4efbb42ec8fdb2bf2755f3e6a01ae88e1823d [file]
// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
// limitations under the License.
// See the License for the specific language governing permissions and
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// distributed under the License is distributed on an "AS-IS" BASIS,
// Unless required by applicable law or agreed to in writing, software
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// You may obtain a copy of the License at
// you may not use this file except in compliance with the License.
// Licensed under the Apache License, Version 2.0 (the "License");
//
goog.provide('i18n.input.chrome.inputview.Controller');
goog.require('goog.Disposable');
goog.require('goog.array');
goog.require('goog.async.Delay');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.i18n.bidi');
goog.require('goog.object');
goog.require('i18n.input.chrome.DataSource');
goog.require('i18n.input.chrome.Statistics');
goog.require('i18n.input.chrome.inputview.Adapter');
goog.require('i18n.input.chrome.inputview.CandidatesInfo');
goog.require('i18n.input.chrome.inputview.ConditionName');
goog.require('i18n.input.chrome.inputview.Css');
goog.require('i18n.input.chrome.inputview.KeyboardContainer');
goog.require('i18n.input.chrome.inputview.M17nModel');
goog.require('i18n.input.chrome.inputview.Model');
goog.require('i18n.input.chrome.inputview.PerfTracker');
goog.require('i18n.input.chrome.inputview.ReadyState');
goog.require('i18n.input.chrome.inputview.Settings');
goog.require('i18n.input.chrome.inputview.SizeSpec');
goog.require('i18n.input.chrome.inputview.SoundController');
goog.require('i18n.input.chrome.inputview.SpecNodeName');
goog.require('i18n.input.chrome.inputview.StateType');
goog.require('i18n.input.chrome.inputview.SwipeDirection');
goog.require('i18n.input.chrome.inputview.elements.ElementType');
goog.require('i18n.input.chrome.inputview.elements.content.Candidate');
goog.require('i18n.input.chrome.inputview.elements.content.CandidateView');
goog.require('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView');
goog.require('i18n.input.chrome.inputview.elements.content.MenuView');
goog.require('i18n.input.chrome.inputview.events.EventType');
goog.require('i18n.input.chrome.inputview.events.KeyCodes');
goog.require('i18n.input.chrome.inputview.handler.PointerHandler');
goog.require('i18n.input.chrome.inputview.util');
goog.require('i18n.input.chrome.message.ContextType');
goog.require('i18n.input.chrome.message.Name');
goog.require('i18n.input.chrome.message.Type');
goog.require('i18n.input.lang.InputToolCode');
goog.scope(function() {
var CandidateType = i18n.input.chrome.inputview.elements.content.Candidate.Type;
var Candidate = i18n.input.chrome.inputview.elements.content.Candidate;
var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView;
var ConditionName = i18n.input.chrome.inputview.ConditionName;
var ContextType = i18n.input.chrome.message.ContextType;
var Css = i18n.input.chrome.inputview.Css;
var ElementType = i18n.input.chrome.inputview.elements.ElementType;
var EventType = i18n.input.chrome.inputview.events.EventType;
var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content.
ExpandedCandidateView;
var InputToolCode = i18n.input.lang.InputToolCode;
var KeyCodes = i18n.input.chrome.inputview.events.KeyCodes;
var MenuView = i18n.input.chrome.inputview.elements.content.MenuView;
var Name = i18n.input.chrome.message.Name;
var PerfTracker = i18n.input.chrome.inputview.PerfTracker;
var SizeSpec = i18n.input.chrome.inputview.SizeSpec;
var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
var StateType = i18n.input.chrome.inputview.StateType;
var content = i18n.input.chrome.inputview.elements.content;
var SoundController = i18n.input.chrome.inputview.SoundController;
var Sounds = i18n.input.chrome.inputview.Sounds;
var util = i18n.input.chrome.inputview.util;
/**
* The controller of the input view keyboard.
*
* @param {string} keyset The keyboard keyset.
* @param {string} languageCode The language code for this keyboard.
* @param {string} passwordLayout The layout for password box.
* @param {string} name The input tool name.
* @constructor
* @extends {goog.Disposable}
*/
i18n.input.chrome.inputview.Controller = function(keyset, languageCode,
passwordLayout, name) {
/**
* The model.
*
* @type {!i18n.input.chrome.inputview.Model}
* @private
*/
this.model_ = new i18n.input.chrome.inputview.Model();
/** @private {!i18n.input.chrome.inputview.PerfTracker} */
this.perfTracker_ = new i18n.input.chrome.inputview.PerfTracker(
PerfTracker.TickName.HTML_LOADED);
/**
* The layout map.
*
* @type {!Object.<string, !Object>}
* @private
*/
this.layoutDataMap_ = {};
/**
* The keyset data map.
*
* @type {!Object.<string, !Object>}
* @private
*/
this.keysetDataMap_ = {};
/**
* The event handler.
*
* @type {!goog.events.EventHandler}
* @private
*/
this.handler_ = new goog.events.EventHandler(this);
/**
* The m17n model.
*
* @type {!i18n.input.chrome.inputview.M17nModel}
* @private
*/
this.m17nModel_ = new i18n.input.chrome.inputview.M17nModel();
/**
* The pointer handler.
*
* @type {!i18n.input.chrome.inputview.handler.PointerHandler}
* @private
*/
this.pointerHandler_ = new i18n.input.chrome.inputview.handler.
PointerHandler();
/**
* The statistics object for recording metrics values.
*
* @type {!i18n.input.chrome.Statistics}
* @private
*/
this.statistics_ = i18n.input.chrome.Statistics.getInstance();
/** @private {!i18n.input.chrome.inputview.ReadyState} */
this.readyState_ = new i18n.input.chrome.inputview.ReadyState();
/** @private {!i18n.input.chrome.inputview.Adapter} */
this.adapter_ = new i18n.input.chrome.inputview.Adapter(this.readyState_);
/** @private {!i18n.input.chrome.inputview.KeyboardContainer} */
this.container_ = new i18n.input.chrome.inputview.KeyboardContainer(
this.adapter_);
this.container_.render();
/** @private {!i18n.input.chrome.inputview.SoundController} */
this.soundController_ = new SoundController(false);
/**
* The context type and keyset mapping group by input method id.
* key: input method id.
* value: Object
* key: context type string.
* value: keyset string.
*
* @private {!Object.<string, !Object.<string, string>>}
*/
this.contextTypeToKeysetMap_ = {};
/**
* The stats map for input view closing.
*
* @type {!Object.<string, !Array.<number>>}
* @private
*/
this.statsForClosing_ = {};
/**
* The last height sent to window.resizeTo to avoid multiple equivalent calls.
*
* @private {number}
*/
this.lastResizeHeight_ = -1;
/**
* The activate (show) time stamp for statistics.
*
* @type {Date}
* @private
*/
this.showTimeStamp_ = new Date();
this.initialize(keyset, languageCode, passwordLayout, name);
/**
* The suggestions.
* Note: sets a default empty result to avoid null check.
*
* @private {!i18n.input.chrome.inputview.CandidatesInfo}
*/
this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty();
this.registerEventHandler_();
};
goog.inherits(i18n.input.chrome.inputview.Controller,
goog.Disposable);
var Controller = i18n.input.chrome.inputview.Controller;
/**
* @define {boolean} Flag to disable handwriting.
*/
Controller.DISABLE_HWT = false;
/**
* A flag to indicate whether the shift is for auto capital.
*
* @private {boolean}
*/
Controller.prototype.shiftForAutoCapital_ = false;
/**
* @define {boolean} Flag to indicate whether it is debugging.
*/
Controller.DEV = false;
/**
* The handwriting view code, use the code can switch handwriting panel view.
*
* @const {string}
* @private
*/
Controller.HANDWRITING_VIEW_CODE_ = 'hwt';
/**
* The emoji view code, use the code can switch to emoji.
*
* @const {string}
* @private
*/
Controller.EMOJI_VIEW_CODE_ = 'emoji';
/**
* The limitation for backspace repeat time to avoid unexpected
* problem that backspace is held all the time.
*
* @private {number}
*/
Controller.BACKSPACE_REPEAT_LIMIT_ = 255;
/**
* The repeated times of the backspace.
*
* @private {number}
*/
Controller.prototype.backspaceRepeated_ = 0;
/**
* The handwriting input tool code suffix.
*
* @const {string}
* @private
*/
Controller.HANDWRITING_CODE_SUFFIX_ = '-t-i0-handwrit';
/**
* True if the settings is loaded.
*
* @type {boolean}
*/
Controller.prototype.isSettingReady = false;
/**
* True if the keyboard is set up.
* Note: This flag is only used for automation testing.
*
* @type {boolean}
*/
Controller.prototype.isKeyboardReady = false;
/**
* The auto repeat timer for backspace hold.
*
* @type {goog.async.Delay}
* @private
*/
Controller.prototype.backspaceAutoRepeat_;
/**
* The active keyboard code.
*
* @type {string}
* @private
*/
Controller.prototype.currentKeyset_ = '';
/**
* The current input method id.
*
* @private {string}
*/
Controller.prototype.currentInputmethod_ = '';
/**
* The operations on candidates.
*
* @enum {number}
*/
Controller.CandidatesOperation = {
NONE: 0,
EXPAND: 1,
SHRINK: 2
};
/**
* The active language code.
*
* @type {string}
* @private
*/
Controller.prototype.lang_;
/**
* The password keyset.
*
* @private {string}
*/
Controller.prototype.passwordKeyset_ = '';
/**
* The soft key map, because key configuration is loaded before layout,
* controller needs this varaible to save it and hook into keyboard view.
*
* @type {!Array.<!content.SoftKey>}
* @private
*/
Controller.prototype.softKeyList_;
/**
* The mapping from soft key id to soft key view id.
*
* @type {!Object.<string, string>}
* @private
*/
Controller.prototype.mapping_;
/**
* The dead key.
*
* @type {string}
* @private
*/
Controller.prototype.deadKey_ = '';
/**
* The input tool name.
*
* @type {string}
* @private
*/
Controller.prototype.title_;
/**
* Registers event handlers.
* @private
*/
Controller.prototype.registerEventHandler_ = function() {
this.handler_.
listen(this.model_,
EventType.LAYOUT_LOADED,
this.onLayoutLoaded_).
listen(this.model_,
EventType.CONFIG_LOADED,
this.onConfigLoaded_).
listen(this.m17nModel_,
EventType.CONFIG_LOADED,
this.onConfigLoaded_).
listen(this.pointerHandler_, [
EventType.LONG_PRESS,
EventType.CLICK,
EventType.DOUBLE_CLICK,
EventType.DOUBLE_CLICK_END,
EventType.POINTER_UP,
EventType.POINTER_DOWN,
EventType.POINTER_OVER,
EventType.POINTER_OUT,
EventType.SWIPE
], this.onPointerEvent_).
listen(window, goog.events.EventType.RESIZE, this.resize).
listen(this.adapter_,
i18n.input.chrome.inputview.events.EventType.
SURROUNDING_TEXT_CHANGED,
this.onSurroundingTextChanged_).
listen(this.adapter_,
i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK,
this.onCandidatesBack_).
listen(this.adapter_,
i18n.input.chrome.inputview.events.EventType.CONTEXT_FOCUS,
this.onContextFocus_).
listen(this.adapter_,
i18n.input.chrome.inputview.events.EventType.CONTEXT_BLUR,
this.onContextBlur_).
listen(this.adapter_,
i18n.input.chrome.inputview.events.EventType.VISIBILITY_CHANGE,
this.onVisibilityChange_).
listen(this.adapter_,
i18n.input.chrome.inputview.events.EventType.SETTINGS_READY,
this.onSettingsReady_).
listen(this.adapter_,
i18n.input.chrome.message.Type.UPDATE_SETTINGS,
this.onUpdateSettings_);
};
/**
* Callback for updating settings.
*
* @param {!i18n.input.chrome.message.Event} e .
* @private
*/
Controller.prototype.onUpdateSettings_ = function(e) {
var settings = this.model_.settings;
if (goog.isDef(e.msg['autoSpace'])) {
settings.autoSpace = e.msg['autoSpace'];
}
if (goog.isDef(e.msg['autoCapital'])) {
settings.autoCapital = e.msg['autoCapital'];
}
if (goog.isDef(e.msg['candidatesNavigation'])) {
settings.candidatesNavigation = e.msg['candidatesNavigation'];
}
if (goog.isDef(e.msg[Name.KEYSET])) {
this.contextTypeToKeysetMap_[this.currentInputMethod_][
ContextType.DEFAULT] = e.msg[Name.KEYSET];
}
if (goog.isDef(e.msg['enableLongPress'])) {
settings.enableLongPress = e.msg['enableLongPress'];
}
if (goog.isDef(e.msg['doubleSpacePeriod'])) {
settings.doubleSpacePeriod = e.msg['doubleSpacePeriod'];
}
if (goog.isDef(e.msg['soundOnKeypress'])) {
settings.soundOnKeypress = e.msg['soundOnKeypress'];
this.soundController_.setEnabled(settings.soundOnKeypress);
}
this.perfTracker_.tick(PerfTracker.TickName.BACKGROUND_SETTINGS_FETCHED);
this.model_.stateManager.contextType = this.adapter_.getContextType();
var isPassword = this.adapter_.isPasswordBox();
this.switchToKeySet(this.getActiveKeyset_());
};
/**
* Callback for setting ready.
*
* @private
*/
Controller.prototype.onSettingsReady_ = function() {
if (this.isSettingReady) {
return;
}
this.isSettingReady = true;
var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
if (this.adapter_.isA11yMode) {
keysetMap[ContextType.PASSWORD] = keysetMap[ContextType.DEFAULT] =
util.getConfigName(keysetMap[ContextType.DEFAULT]);
} else {
var preferredKeyset = /** @type {string} */ (this.model_.settings.
getPreference(util.getConfigName(
keysetMap[ContextType.DEFAULT])));
if (preferredKeyset) {
keysetMap[ContextType.PASSWORD] = keysetMap[ContextType.DEFAULT] =
preferredKeyset;
}
}
if (!this.adapter_.isExperimental && keysetMap[ContextType.DEFAULT] ==
'zhuyin.compact.qwerty') {
keysetMap[ContextType.DEFAULT] = 'zhuyin';
}
this.maybeCreateViews_();
this.switchToKeySet(this.getActiveKeyset_());
};
/**
* Gets the data for spatial module.
*
* @param {!content.SoftKey} key .
* @param {number} x The x-offset of the touch point.
* @param {number} y The y-offset of the touch point.
* @return {!Object} .
* @private
*/
Controller.prototype.getSpatialData_ = function(key, x, y) {
var items = [];
items.push([this.getKeyContent_(key), key.estimator.estimateInLogSpace(x, y)
]);
for (var i = 0; i < key.nearbyKeys.length; i++) {
var nearByKey = key.nearbyKeys[i];
var content = this.getKeyContent_(nearByKey);
if (content && util.REGEX_LANGUAGE_MODEL_CHARACTERS.test(content)) {
items.push([content, nearByKey.estimator.estimateInLogSpace(x, y)]);
}
}
goog.array.sort(items, function(item1, item2) {
return item1[1] - item2[1];
});
var sources = items.map(function(item) {
return item[0].toLowerCase();
});
var possibilities = items.map(function(item) {
return item[1];
});
return {
'sources': sources,
'possibilities': possibilities
};
};
/**
* Gets the key content.
*
* @param {!content.SoftKey} key .
* @return {string} .
* @private
*/
Controller.prototype.getKeyContent_ = function(key) {
if (key.type == i18n.input.chrome.inputview.elements.ElementType.
CHARACTER_KEY) {
key = /** @type {!content.CharacterKey} */ (key);
return key.getActiveCharacter();
}
if (key.type == i18n.input.chrome.inputview.elements.ElementType.
COMPACT_KEY) {
key = /** @type {!content.FunctionalKey} */ (key);
return key.text;
}
return '';
};
/**
* Callback for pointer event.
*
* @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
* @private
*/
Controller.prototype.onPointerEvent_ = function(e) {
if ((this.adapter_.isChromeVoxOn || !this.model_.settings.enableLongPress) &&
e.type == EventType.LONG_PRESS) {
return;
}
if (e.type == EventType.POINTER_UP) {
this.stopBackspaceAutoRepeat_();
}
if (e.view) {
this.handlePointerAction_(e.view, e);
} else {
var tabbableKeysets = [
Controller.HANDWRITING_VIEW_CODE_,
Controller.EMOJI_VIEW_CODE_];
if (goog.array.contains(tabbableKeysets, this.currentKeyset_)) {
this.resetAll_();
this.switchToKeySet(this.container_.currentKeysetView.fromKeyset);
}
}
};
/**
* Handles the swipe action.
*
* @param {!i18n.input.chrome.inputview.elements.Element} view The view, for
* swipe event, the view would be the soft key which starts the swipe.
* @param {!i18n.input.chrome.inputview.events.SwipeEvent} e The swipe event.
* @private
*/
Controller.prototype.handleSwipeAction_ = function(view, e) {
var direction = e.direction;
if (this.container_.altDataView.isVisible()) {
this.container_.altDataView.highlightItem(e.x, e.y);
return;
}
if (view.type == ElementType.CHARACTER_KEY) {
view = /** @type {!content.CharacterKey} */ (view);
if (direction & i18n.input.chrome.inputview.SwipeDirection.UP ||
direction & i18n.input.chrome.inputview.SwipeDirection.DOWN) {
var ch = view.getCharacterByGesture(!!(direction &
i18n.input.chrome.inputview.SwipeDirection.UP));
if (ch) {
view.flickerredCharacter = ch;
}
}
}
if (view.type == ElementType.COMPACT_KEY) {
view = /** @type {!content.CompactKey} */ (view);
if ((direction & i18n.input.chrome.inputview.SwipeDirection.UP) &&
view.hintText) {
view.flickerredCharacter = view.hintText;
}
}
};
/**
* Execute a command.
*
* @param {!i18n.input.chrome.inputview.elements.content.MenuView.Command}
* command The command that about to be executed.
* @param {string=} opt_arg The optional command argument.
* @private
*/
Controller.prototype.executeCommand_ = function(command, opt_arg) {
var CommandEnum = MenuView.Command;
switch (command) {
case CommandEnum.SWITCH_IME:
var inputMethodId = opt_arg;
if (inputMethodId) {
this.adapter_.switchToInputMethod(inputMethodId);
}
break;
case CommandEnum.SWITCH_KEYSET:
var keyset = opt_arg;
if (keyset) {
this.recordStatsForClosing_(
'InputMethod.VirtualKeyboard.LayoutSwitch', 1, 25, 25);
this.switchToKeySet(keyset);
}
break;
case CommandEnum.OPEN_EMOJI:
this.switchToKeySet(Controller.EMOJI_VIEW_CODE_);
break;
case CommandEnum.OPEN_HANDWRITING:
// TODO: remember handwriting keyset.
this.switchToKeySet(Controller.HANDWRITING_VIEW_CODE_);
break;
case CommandEnum.OPEN_SETTING:
if (window.inputview) {
inputview.openSettings();
}
break;
}
};
/**
* Handles the pointer action.
*
* @param {!i18n.input.chrome.inputview.elements.Element} view The view.
* @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
* @private
*/
Controller.prototype.handlePointerAction_ = function(view, e) {
if (e.type == i18n.input.chrome.inputview.events.EventType.SWIPE) {
e = /** @type {!i18n.input.chrome.inputview.events.SwipeEvent} */ (e);
this.handleSwipeAction_(view, e);
}
switch (view.type) {
case ElementType.BACK_BUTTON:
if (e.type == EventType.POINTER_OUT || e.type == EventType.POINTER_UP) {
view.setHighlighted(false);
} else if (e.type == EventType.POINTER_DOWN ||
e.type == EventType.POINTER_OVER) {
view.setHighlighted(true);
}
if (e.type == EventType.POINTER_UP) {
this.switchToKeySet(this.container_.currentKeysetView.fromKeyset);
this.clearCandidates_();
}
return;
case ElementType.EXPAND_CANDIDATES:
if (e.type == EventType.POINTER_UP) {
this.showCandidates_(this.candidatesInfo_.source,
this.candidatesInfo_.candidates,
Controller.CandidatesOperation.EXPAND);
}
return;
case ElementType.SHRINK_CANDIDATES:
if (e.type == EventType.POINTER_UP) {
this.showCandidates_(this.candidatesInfo_.source,
this.candidatesInfo_.candidates,
Controller.CandidatesOperation.SHRINK);
}
return;
case ElementType.CANDIDATE:
view = /** @type {!Candidate} */ (view);
if (e.type == EventType.POINTER_UP) {
if (view.candidateType == CandidateType.CANDIDATE) {
this.adapter_.selectCandidate(view.candidate);
} else if (view.candidateType == CandidateType.NUMBER) {
this.adapter_.commitText(view.candidate[Name.CANDIDATE]);
}
this.container_.cleanStroke();
this.soundController_.onKeyUp(ElementType.CANDIDATE);
}
if (e.type == EventType.POINTER_OUT || e.type == EventType.POINTER_UP) {
view.setHighlighted(false);
} else if (e.type == EventType.POINTER_DOWN ||
e.type == EventType.POINTER_OVER) {
view.setHighlighted(true);
}
return;
case ElementType.ALTDATA_VIEW:
view = /** @type {!content.AltDataView} */ (view);
if (e.type == EventType.POINTER_DOWN &&
e.target == view.getCoverElement()) {
view.hide();
} else if (e.type == EventType.POINTER_UP) {
var ch = view.getHighlightedCharacter();
this.adapter_.sendKeyDownAndUpEvent(ch, view.triggeredBy.id,
view.triggeredBy.keyCode,
{'sources': [ch.toLowerCase()], 'possibilities': [1]});
view.hide();
this.clearUnstickyState_();
}
return;
case ElementType.MENU_ITEM:
view = /** @type {!content.MenuItem} */ (view);
if (e.type == EventType.CLICK) {
this.resetAll_();
this.executeCommand_.apply(this, view.getCommand());
this.container_.menuView.hide();
}
view.setHighlighted(e.type == EventType.POINTER_DOWN ||
e.type == EventType.POINTER_OVER);
// TODO: Add chrome vox support.
return;
case ElementType.MENU_VIEW:
view = /** @type {!MenuView} */ (view);
if (e.type == EventType.POINTER_DOWN &&
e.target == view.getCoverElement()) {
view.hide();
}
return;
case ElementType.EMOJI_KEY:
if (e.type == EventType.POINTER_UP) {
if (!this.container_.currentKeysetView.isDragging && view.text != '') {
this.adapter_.commitText(view.text);
}
}
return;
case ElementType.HWT_PRIVACY_GOT_IT:
this.adapter_.sendHwtPrivacyConfirmMessage();
return;
case ElementType.SOFT_KEY_VIEW:
// Delegates the events on the soft key view to its soft key.
view = /** @type {!i18n.input.chrome.inputview.elements.layout.
SoftKeyView} */ (view);
if (!view.softKey) {
return;
}
view = view.softKey;
}
if (view.type != ElementType.MODIFIER_KEY &&
!this.container_.altDataView.isVisible() &&
!this.container_.menuView.isVisible()) {
// The highlight of the modifier key is depending on the state instead
// of the key down or up.
if (e.type == EventType.POINTER_OVER || e.type == EventType.POINTER_DOWN ||
e.type == EventType.DOUBLE_CLICK) {
view.setHighlighted(true);
} else if (e.type == EventType.POINTER_OUT ||
e.type == EventType.POINTER_UP ||
e.type == EventType.DOUBLE_CLICK_END) {
view.setHighlighted(false);
}
}
this.handlePointerEventForSoftKey_(
/** @type {!content.SoftKey} */ (view), e);
this.updateContextModifierState_();
};
/**
* Handles softkey of the pointer action.
*
* @param {!content.SoftKey} softKey .
* @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
* @private
*/
Controller.prototype.handlePointerEventForSoftKey_ = function(softKey, e) {
var key;
switch (softKey.type) {
case ElementType.CANDIDATES_PAGE_UP:
if (e.type == EventType.POINTER_UP) {
this.container_.expandedCandidateView.pageUp();
}
break;
case ElementType.CANDIDATES_PAGE_DOWN:
if (e.type == EventType.POINTER_UP) {
this.container_.expandedCandidateView.pageDown();
}
break;
case ElementType.CHARACTER_KEY:
key = /** @type {!content.CharacterKey} */ (softKey);
if (e.type == EventType.LONG_PRESS) {
this.container_.altDataView.show(
key, goog.i18n.bidi.isRtlLanguage(this.languageCode_),
this.adapter_.isExperimental);
} else if (e.type == EventType.POINTER_UP) {
this.model_.stateManager.triggerChording();
var ch = key.getActiveCharacter();
this.adapter_.sendKeyDownAndUpEvent(ch, key.id, key.keyCode,
this.getSpatialData_(key, e.x, e.y));
this.clearUnstickyState_();
key.flickerredCharacter = '';
}
break;
case ElementType.MODIFIER_KEY:
key = /** @type {!content.ModifierKey} */ (softKey);
var isStateEnabled = this.model_.stateManager.hasState(key.toState);
var isChording = this.model_.stateManager.isChording(key.toState);
if (e.type == EventType.POINTER_DOWN) {
this.changeState_(key.toState, !isStateEnabled, true, false);
this.model_.stateManager.setKeyDown(key.toState, true);
} else if (e.type == EventType.POINTER_UP || e.type == EventType.
POINTER_OUT) {
if (isChording) {
this.changeState_(key.toState, false, false);
} else if (key.toState == StateType.CAPSLOCK) {
this.changeState_(key.toState, isStateEnabled, true, true);
} else if (this.model_.stateManager.isKeyDown(key.toState)) {
this.changeState_(key.toState, isStateEnabled, false);
}
this.model_.stateManager.setKeyDown(key.toState, false);
} else if (e.type == EventType.DOUBLE_CLICK) {
this.changeState_(key.toState, isStateEnabled, true, true);
} else if (e.type == EventType.LONG_PRESS) {
if (!isChording) {
this.changeState_(key.toState, true, true, true);
this.model_.stateManager.setKeyDown(key.toState, false);
}
}
break;
case ElementType.BACKSPACE_KEY:
key = /** @type {!content.FunctionalKey} */ (softKey);
if (e.type == EventType.POINTER_DOWN) {
this.backspaceTick_();
} else if (e.type == EventType.POINTER_UP || e.type == EventType.
POINTER_OUT) {
this.stopBackspaceAutoRepeat_();
this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE);
}
break;
case ElementType.TAB_KEY:
key = /** @type {!content.FunctionalKey} */ (softKey);
if (e.type == EventType.POINTER_DOWN) {
this.adapter_.sendKeyDownEvent('\u0009', KeyCodes.TAB);
} else if (e.type == EventType.POINTER_UP) {
this.adapter_.sendKeyUpEvent('\u0009', KeyCodes.TAB);
}
break;
case ElementType.ENTER_KEY:
key = /** @type {!content.FunctionalKey} */ (softKey);
if (e.type == EventType.POINTER_UP) {
this.adapter_.sendKeyDownAndUpEvent('\u000D', KeyCodes.ENTER);
}
break;
case ElementType.ARROW_UP:
if (e.type == EventType.POINTER_DOWN) {
this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_UP);
} else if (e.type == EventType.POINTER_UP) {
this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_UP);
}
break;
case ElementType.ARROW_DOWN:
if (e.type == EventType.POINTER_DOWN) {
this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_DOWN);
} else if (e.type == EventType.POINTER_UP) {
this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_DOWN);
}
break;
case ElementType.ARROW_LEFT:
if (e.type == EventType.POINTER_DOWN) {
this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_LEFT);
} else if (e.type == EventType.POINTER_UP) {
this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_LEFT);
}
break;
case ElementType.ARROW_RIGHT:
if (e.type == EventType.POINTER_DOWN) {
this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_RIGHT);
} else if (e.type == EventType.POINTER_UP) {
this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_RIGHT);
}
break;
case ElementType.EN_SWITCHER:
if (e.type == EventType.POINTER_UP) {
key = /** @type {!content.EnSwitcherKey} */ (softKey);
this.adapter_.toggleLanguageState(this.model_.stateManager.isEnMode);
this.model_.stateManager.isEnMode = !this.model_.stateManager.isEnMode;
key.update();
}
break;
case ElementType.SPACE_KEY:
key = /** @type {!content.SpaceKey} */ (softKey);
var doubleSpacePeriod = this.model_.settings.doubleSpacePeriod;
if (e.type == EventType.POINTER_UP || (!doubleSpacePeriod && e.type ==
EventType.DOUBLE_CLICK_END)) {
this.adapter_.sendKeyDownAndUpEvent(key.getCharacter(),
KeyCodes.SPACE);
this.clearUnstickyState_();
} else if (e.type == EventType.DOUBLE_CLICK && doubleSpacePeriod) {
this.adapter_.doubleClickOnSpaceKey();
}
break;
case ElementType.SWITCHER_KEY:
key = /** @type {!content.SwitcherKey} */ (softKey);
if (e.type == EventType.POINTER_UP) {
this.recordStatsForClosing_(
'InputMethod.VirtualKeyboard.LayoutSwitch', 1, 25, 25);
if (this.isSubKeyset_(key.toKeyset, this.currentKeyset_)) {
this.model_.stateManager.reset();
this.container_.update();
this.updateContextModifierState_();
this.container_.menuView.hide();
} else {
this.resetAll_();
}
// Switch to the specific keyboard.
this.switchToKeySet(key.toKeyset);
if (key.record) {
this.model_.settings.savePreference(
util.getConfigName(key.toKeyset),
key.toKeyset);
}
}
break;
case ElementType.COMPACT_KEY:
key = /** @type {!content.CompactKey} */ (softKey);
if (e.type == EventType.LONG_PRESS) {
this.container_.altDataView.show(
key, goog.i18n.bidi.isRtlLanguage(this.languageCode_),
this.adapter_.isExperimental);
} else if (e.type == EventType.POINTER_UP) {
this.model_.stateManager.triggerChording();
this.adapter_.sendKeyDownAndUpEvent(key.getActiveCharacter(), '', 0,
this.getSpatialData_(key, e.x, e.y));
this.clearUnstickyState_();
key.flickerredCharacter = '';
}
break;
case ElementType.HIDE_KEYBOARD_KEY:
var defaultKeyset = this.getActiveKeyset_();
if (e.type == EventType.POINTER_UP) {
this.adapter_.hideKeyboard();
}
if (this.currentKeyset_ != defaultKeyset) {
this.switchToKeySet(defaultKeyset);
}
break;
case ElementType.MENU_KEY:
key = /** @type {!content.MenuKey} */ (softKey);
if (e.type == EventType.POINTER_DOWN) {
var isCompact = this.currentKeyset_.indexOf('compact') != -1;
var defaultKeyset = this.contextTypeToKeysetMap_[
this.currentInputMethod_][ContextType.DEFAULT];
var enableCompact = !this.adapter_.isA11yMode && goog.array.contains(
util.KEYSETS_HAVE_COMPACT, defaultKeyset.split(/\./)[0]);
if (defaultKeyset == 'zhuyin' && !this.adapter_.isExperimental ||
this.languageCode_ == 'ko') {
// Hides 'switch to compact' for zhuyin when not in experimental env.
enableCompact = false;
}
var self = this;
var hasHwt = !this.adapter_.isPasswordBox() &&
!Controller.DISABLE_HWT && goog.object.contains(
InputToolCode, this.getHwtInputToolCode_()) &&
this.languageCode_ != 'ko';
var enableSettings = this.shouldEnableSettings() &&
!!window.inputview && !!inputview.openSettings;
this.adapter_.getInputMethods(function(inputMethods) {
this.container_.menuView.show(key, defaultKeyset, isCompact,
enableCompact, this.currentInputMethod_, inputMethods, hasHwt,
enableSettings, this.adapter_.isExperimental);
}.bind(this));
}
break;
case ElementType.GLOBE_KEY:
if (e.type == EventType.POINTER_UP) {
this.adapter_.clearModifierStates();
this.adapter_.setModifierState(
i18n.input.chrome.inputview.StateType.CTRL, true);
this.adapter_.sendKeyDownAndUpEvent(' ', KeyCodes.SPACE, 0x20);
this.adapter_.setModifierState(
i18n.input.chrome.inputview.StateType.CTRL, false);
}
break;
case ElementType.IME_SWITCH:
key = /** @type {!content.FunctionalKey} */ (softKey);
this.adapter_.sendKeyDownAndUpEvent('', key.id);
break;
}
// Play key sound on pointer up.
if (e.type == EventType.POINTER_UP)
this.soundController_.onKeyUp(softKey.type);
};
/**
* Clears unsticky state.
*
* @private
*/
Controller.prototype.clearUnstickyState_ = function() {
if (this.model_.stateManager.hasUnStickyState()) {
for (var key in StateType) {
var value = StateType[key];
if (this.model_.stateManager.hasState(value) &&
!this.model_.stateManager.isSticky(value)) {
this.changeState_(value, false, false);
}
}
}
this.container_.update();
};
/**
* Stops the auto-repeat for backspace.
*
* @private
*/
Controller.prototype.stopBackspaceAutoRepeat_ = function() {
if (this.backspaceAutoRepeat_) {
goog.dispose(this.backspaceAutoRepeat_);
this.backspaceAutoRepeat_ = null;
this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE);
this.backspaceRepeated_ = 0;
}
};
/**
* The tick for the backspace key.
*
* @private
*/
Controller.prototype.backspaceTick_ = function() {
if (this.backspaceRepeated_ >= Controller.BACKSPACE_REPEAT_LIMIT_) {
this.stopBackspaceAutoRepeat_();
return;
}
this.backspaceRepeated_++;
this.backspaceDown_();
this.soundController_.onKeyRepeat(ElementType.BACKSPACE_KEY);
if (this.backspaceAutoRepeat_) {
this.backspaceAutoRepeat_.start(75);
} else {
this.backspaceAutoRepeat_ = new goog.async.Delay(
goog.bind(this.backspaceTick_, this), 300);
this.backspaceAutoRepeat_.start();
}
};
/**
* Callback for VISIBILITY_CHANGE.
*
* @private
*/
Controller.prototype.onVisibilityChange_ = function() {
if (!this.adapter_.isVisible) {
for (var name in this.statsForClosing_) {
var stat = this.statsForClosing_[name];
this.statistics_.recordValue(name, stat[0], stat[1], stat[2]);
}
this.statistics_.recordValue('InputMethod.VirtualKeyboard.Duration',
Math.floor((new Date() - this.showTimeStamp_) / 1000), 4096, 50);
this.statsForClosing_ = {};
this.showTimeStamp_ = new Date();
this.resetAll_();
}
};
/**
* Resets the whole keyboard include clearing candidates,
* reset modifier state, etc.
*
* @private
*/
Controller.prototype.resetAll_ = function() {
this.clearCandidates_();
this.container_.cleanStroke();
this.container_.candidateView.hideNumberRow();
this.model_.stateManager.reset();
this.container_.update();
this.updateContextModifierState_();
this.deadKey_ = '';
this.resize();
this.container_.expandedCandidateView.close();
this.container_.menuView.hide();
};
/**
* Callback when the context is changed.
*
* @private
*/
Controller.prototype.onContextFocus_ = function() {
this.resetAll_();
this.model_.stateManager.contextType = this.adapter_.getContextType();
this.switchToKeySet(this.getActiveKeyset_());
};
/**
* Callback when surrounding text is changed.
*
* @param {!i18n.input.chrome.inputview.events.SurroundingTextChangedEvent} e .
* @private
*/
Controller.prototype.onSurroundingTextChanged_ = function(e) {
if (!this.model_.settings.autoCapital || !e.text) {
return;
}
var isShiftEnabled = this.model_.stateManager.hasState(StateType.SHIFT);
var needAutoCap = this.model_.settings.autoCapital &&
util.needAutoCap(e.text);
if (needAutoCap && !isShiftEnabled) {
this.changeState_(StateType.SHIFT, true, false);
this.shiftForAutoCapital_ = true;
} else if (!needAutoCap && this.shiftForAutoCapital_) {
this.changeState_(StateType.SHIFT, false, false);
}
};
/**
* Callback for Context blurs.
*
* @private
*/
Controller.prototype.onContextBlur_ = function() {
this.clearCandidates_();
this.container_.cleanStroke();
this.deadKey_ = '';
this.container_.menuView.hide();
};
/**
* Backspace key is down.
*
* @private
*/
Controller.prototype.backspaceDown_ = function() {
if (this.container_.hasStrokesOnCanvas()) {
this.clearCandidates_();
this.container_.cleanStroke();
} else {
this.adapter_.sendKeyDownEvent('\u0008', KeyCodes.BACKSPACE);
}
this.recordStatsForClosing_(
'InputMethod.VirtualKeyboard.BackspaceCount', 1, 4095, 4096);
this.statistics_.recordEnum('InputMethod.VirtualKeyboard.BackspaceOnLayout',
this.statistics_.getLayoutType(this.currentKeyset_,
this.adapter_.isA11yMode),
i18n.input.chrome.Statistics.LayoutTypes.MAX);
};
/**
* Callback for state change.
*
* @param {StateType} stateType The state type.
* @param {boolean} enable True to enable the state.
* @param {boolean} isSticky True to make the state sticky.
* @param {boolean=} opt_isFinalSticky .
* @private
*/
Controller.prototype.changeState_ = function(stateType, enable, isSticky,
opt_isFinalSticky) {
if (stateType == StateType.ALTGR) {
var code = KeyCodes.ALT_RIGHT;
if (enable) {
this.adapter_.sendKeyDownEvent('', code);
} else {
this.adapter_.sendKeyUpEvent('', code);
}
}
if (stateType == StateType.SHIFT) {
this.shiftForAutoCapital_ = false;
}
var isEnabledBefore = this.model_.stateManager.hasState(stateType);
var isStickyBefore = this.model_.stateManager.isSticky(stateType);
this.model_.stateManager.setState(stateType, enable);
this.model_.stateManager.setSticky(stateType, isSticky);
var isFinalSticky = goog.isDef(opt_isFinalSticky) ? opt_isFinalSticky :
false;
var isFinalStikcyBefore = this.model_.stateManager.isFinalSticky(stateType);
this.model_.stateManager.setFinalSticky(stateType, isFinalSticky);
if (isEnabledBefore != enable || isStickyBefore != isSticky ||
isFinalStikcyBefore != isFinalSticky) {
this.container_.update();
}
};
/**
* Updates the modifier state for context.
*
* @private
*/
Controller.prototype.updateContextModifierState_ = function() {
var stateManager = this.model_.stateManager;
this.adapter_.setModifierState(StateType.ALT,
stateManager.hasState(StateType.ALT));
this.adapter_.setModifierState(StateType.CTRL,
stateManager.hasState(StateType.CTRL));
this.adapter_.setModifierState(StateType.CAPSLOCK,
stateManager.hasState(StateType.CAPSLOCK));
if (!this.shiftForAutoCapital_) {
// If shift key is automatically on because of feature - autoCapital,
// Don't set modifier state to adapter.
this.adapter_.setModifierState(StateType.SHIFT,
stateManager.hasState(StateType.SHIFT));
}
};
/**
* Callback for AUTO-COMPLETE event.
*
* @param {!i18n.input.chrome.DataSource.CandidatesBackEvent} e .
* @private
*/
Controller.prototype.onCandidatesBack_ = function(e) {
this.candidatesInfo_ = new i18n.input.chrome.inputview.CandidatesInfo(
e.source, e.candidates);
this.showCandidates_(e.source, e.candidates, Controller.CandidatesOperation.
NONE);
};
/**
* Shows the candidates to the candidate view.
*
* @param {string} source The source text.
* @param {!Array.<!Object>} candidates The candidate text list.
* @param {Controller.CandidatesOperation} operation .
* @private
*/
Controller.prototype.showCandidates_ = function(source, candidates,
operation) {
var state = !!source ? ExpandedCandidateView.State.COMPLETION_CORRECTION :
ExpandedCandidateView.State.PREDICTION;
var expandView = this.container_.expandedCandidateView;
var expand = false;
if (operation == Controller.CandidatesOperation.NONE) {
expand = expandView.state == state;
} else {
expand = operation == Controller.CandidatesOperation.EXPAND;
}
if (candidates.length == 0) {
this.clearCandidates_();
expandView.state = ExpandedCandidateView.State.NONE;
return;
}
// The compact pinyin needs full candidates instead of three candidates.
var isThreeCandidates = this.currentKeyset_.indexOf('compact') != -1 &&
this.currentKeyset_.indexOf('pinyin-zh-CN') == -1;
if (isThreeCandidates) {
if (candidates.length > 1) {
// Swap the first candidate and the second candidate.
var tmp = candidates[0];
candidates[0] = candidates[1];
candidates[1] = tmp;
}
}
var isHwt = Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_;
this.container_.candidateView.showCandidates(candidates, isThreeCandidates,
this.model_.settings.candidatesNavigation && !isHwt);
if (expand) {
expandView.state = state;
this.container_.currentKeysetView.setVisible(false);
expandView.showCandidates(candidates,
this.container_.candidateView.candidateCount);
this.container_.candidateView.switchToIcon(CandidateView.IconType.
SHRINK_CANDIDATES, true);
} else {
expandView.state = ExpandedCandidateView.State.NONE;
expandView.setVisible(false);
this.container_.currentKeysetView.setVisible(true);
}
};
/**
* Clears candidates.
*
* @private
*/
Controller.prototype.clearCandidates_ = function() {
this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty();
this.container_.candidateView.clearCandidates();
this.container_.expandedCandidateView.close();
this.container_.expandedCandidateView.state = ExpandedCandidateView.State.
NONE;
if (this.container_.currentKeysetView) {
this.container_.currentKeysetView.setVisible(true);
}
this.container_.candidateView.switchToIcon(CandidateView.IconType.BACK,
Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_);
};
/**
* Callback when the layout is loaded.
*
* @param {!i18n.input.chrome.inputview.events.LayoutLoadedEvent} e The event.
* @private
*/
Controller.prototype.onLayoutLoaded_ = function(e) {
var layoutID = e.data['layoutID'];
this.layoutDataMap_[layoutID] = e.data;
this.perfTracker_.tick(PerfTracker.TickName.LAYOUT_LOADED);
this.maybeCreateViews_();
};
/**
* Creates the whole view.
*
* @private
*/
Controller.prototype.maybeCreateViews_ = function() {
if (!this.isSettingReady) {
return;
}
for (var keyset in this.keysetDataMap_) {
var keysetData = this.keysetDataMap_[keyset];
var layoutId = keysetData[SpecNodeName.LAYOUT];
var layoutData = this.layoutDataMap_[layoutId];
if (!this.container_.keysetViewMap[keyset] && layoutData) {
var conditions = {};
conditions[ConditionName.SHOW_ALTGR] =
keysetData[SpecNodeName.HAS_ALTGR_KEY];
conditions[ConditionName.SHOW_MENU] =
keysetData[SpecNodeName.SHOW_MENU_KEY];
// In symbol and more keysets, we want to show a symbol key in the globe
// SoftKeyView. So this view should alway visible in the two keysets.
// Currently, SHOW_MENU_KEY is false for the two keysets, so we use
// !keysetData[SpecNodeName.SHOW_MENU_KEY] here.
conditions[ConditionName.SHOW_GLOBE_OR_SYMBOL] =
!keysetData[SpecNodeName.SHOW_MENU_KEY] ||
this.adapter_.showGlobeKey;
conditions[ConditionName.SHOW_EN_SWITCHER_KEY] = false;
// If the view for the keyboard code doesn't exist, and the layout
// data is ready, then creates the view.
this.container_.addKeysetView(keysetData, layoutData, keyset,
this.languageCode_, this.model_, this.title_, conditions);
this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_CREATED);
}
}
this.switchToKeySet(this.getActiveKeyset_());
};
/**
* Switch to a specific keyboard.
*
* @param {string} keyset The keyset name.
*/
Controller.prototype.switchToKeySet = function(keyset) {
if (!this.isSettingReady) {
return;
}
var lastKeysetView = this.container_.currentKeysetView;
var ret = this.container_.switchToKeyset(this.getRemappedKeyset_(keyset),
this.title_, this.adapter_.isPasswordBox(), this.adapter_.isA11yMode,
keyset, this.currentKeyset_, this.languageCode_);
if (!this.isSubKeyset_(this.currentKeyset_, keyset)) {
// If it is the sub keyset switching, don't record it.
// Update the keyset of current context type.
this.contextTypeToKeysetMap_[this.currentInputMethod_][
this.adapter_.getContextType()] = keyset;
}
if (ret) {
this.updateLanguageState_(this.currentKeyset_, keyset);
// Deactivate the last keyset view instance.
if (lastKeysetView) {
lastKeysetView.deactivate(this.currentKeyset_);
}
this.currentKeyset_ = keyset;
this.resize(Controller.DEV);
this.statistics_.recordLayout(keyset, this.adapter_.isA11yMode);
// Activate the current key set view instance.
this.container_.currentKeysetView.activate(keyset);
this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_SHOWN);
this.perfTracker_.stop();
} else {
this.loadResource_(keyset);
}
};
/**
* Callback when the configuration is loaded.
*
* @param {!i18n.input.chrome.inputview.events.ConfigLoadedEvent} e The event.
* @private
*/
Controller.prototype.onConfigLoaded_ = function(e) {
var data = e.data;
var keyboardCode = data[i18n.input.chrome.inputview.SpecNodeName.ID];
this.keysetDataMap_[keyboardCode] = data;
this.perfTracker_.tick(PerfTracker.TickName.KEYSET_LOADED);
var context = data[i18n.input.chrome.inputview.SpecNodeName.ON_CONTEXT];
if (context && !this.adapter_.isA11yMode) {
var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
if (!keySetMap) {
keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {};
}
keySetMap[context] = keyboardCode;
}
var layoutId = data[i18n.input.chrome.inputview.SpecNodeName.LAYOUT];
var layoutData = this.layoutDataMap_[layoutId];
if (layoutData) {
this.maybeCreateViews_();
} else {
this.model_.loadLayout(data[i18n.input.chrome.inputview.SpecNodeName.
LAYOUT]);
}
};
/**
* Resizes the whole UI.
*
* @param {boolean=} opt_ignoreWindowResize .
*/
Controller.prototype.resize = function(opt_ignoreWindowResize) {
var height;
var widthPercent;
var candidateViewHeight;
var isHorizontal = screen.width > screen.height;
var isWideScreen = (Math.min(screen.width, screen.height) / Math.max(
screen.width, screen.height)) < 0.6;
this.model_.stateManager.covariance.update(isWideScreen, isHorizontal,
this.adapter_.isA11yMode);
if (this.adapter_.isA11yMode) {
height = SizeSpec.A11Y_HEIGHT;
widthPercent = screen.width > screen.height ? SizeSpec.A11Y_WIDTH_PERCENT.
HORIZONTAL : SizeSpec.A11Y_WIDTH_PERCENT.VERTICAL;
candidateViewHeight = SizeSpec.A11Y_CANDIDATE_VIEW_HEIGHT;
} else {
var keyset = this.keysetDataMap_[this.currentKeyset_];
var layout = keyset && keyset[SpecNodeName.LAYOUT];
var data = layout && this.layoutDataMap_[layout];
var spec = data && data[SpecNodeName.WIDTH_PERCENT] ||
SizeSpec.NON_A11Y_WIDTH_PERCENT;
height = SizeSpec.NON_A11Y_HEIGHT;
if (isHorizontal) {
if (isWideScreen) {
widthPercent = spec.HORIZONTAL_WIDE_SCREEN;
} else {
widthPercent = spec.HORIZONTAL;
}
} else {
widthPercent = spec.VERTICAL;
}
candidateViewHeight = SizeSpec.NON_A11Y_CANDIDATE_VIEW_HEIGHT;
}
var viewportSize = goog.dom.getViewportSize();
if (viewportSize.height != height && !opt_ignoreWindowResize) {
if (this.lastResizeHeight_ != height) {
this.lastResizeHeight_ = height;
window.resizeTo(screen.width, height);
}
return;
}
this.container_.resize(screen.width, height, widthPercent,
candidateViewHeight);
if (this.container_.currentKeysetView) {
this.isKeyboardReady = true;
}
};
/**
* Loads the resources, for currentKeyset, passwdKeyset, handwriting,
* emoji, etc.
*
* @private
*/
Controller.prototype.loadAllResources_ = function() {
var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
goog.array.forEach([keysetMap[ContextType.DEFAULT],
Controller.HANDWRITING_VIEW_CODE_,
Controller.EMOJI_VIEW_CODE_,
keysetMap[ContextType.PASSWORD]], function(keyset) {
this.loadResource_(keyset);
}, this);
};
/**
* Gets the remapped keyset.
*
* @param {string} keyset .
* @return {string} The remapped keyset.
* @private
*/
Controller.prototype.getRemappedKeyset_ = function(keyset) {
if (goog.array.contains(util.KEYSETS_USE_US, keyset)) {
return 'us';
}
return keyset;
};
/**
* Loads a single resource.
*
* @param {string} keyset .
* loaded.
* @private
*/
Controller.prototype.loadResource_ = function(keyset) {
var remapped = this.getRemappedKeyset_(keyset);
if (!this.keysetDataMap_[remapped]) {
if (/^m17n:/.test(remapped)) {
this.m17nModel_.loadConfig(remapped);
} else {
this.model_.loadConfig(remapped);
}
return;
}
var layoutId = this.keysetDataMap_[remapped][SpecNodeName.LAYOUT];
if (!this.layoutDataMap_[layoutId]) {
this.model_.loadLayout(layoutId);
return;
}
};
/**
* Sets the keyboard.
*
* @param {string} keyset The keyboard keyset.
* @param {string} languageCode The language code for this keyboard.
* @param {string} passwordLayout The layout for password box.
* @param {string} title The title for this keyboard.
*/
Controller.prototype.initialize = function(keyset, languageCode, passwordLayout,
title) {
this.perfTracker_.restart();
this.adapter_.getCurrentInputMethod(function(currentInputMethod) {
// TODO: remove this hack as soon as the manifest is fixed in chromium.
if (languageCode == 'ko') {
if (currentInputMethod.indexOf('hangul_2set') > 0) {
keyset = 'm17n:ko_2set';
}
}
this.languageCode_ = languageCode;
this.currentInputMethod_ = currentInputMethod;
var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
if (!keySetMap) {
keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {};
}
keySetMap[ContextType.PASSWORD] = passwordLayout;
keySetMap[ContextType.DEFAULT] = keyset;
this.title_ = title;
this.isSettingReady = false;
this.model_.settings = new i18n.input.chrome.inputview.Settings();
this.adapter_.initialize(languageCode ? languageCode.split('-')[0] : '');
this.loadAllResources_();
this.switchToKeySet(this.getActiveKeyset_());
// Set language attribute and font of body.
document.body.setAttribute('lang', this.languageCode_);
goog.dom.classlist.add(document.body, Css.FONT);
}.bind(this));
};
/** @override */
Controller.prototype.disposeInternal = function() {
goog.dispose(this.container_);
goog.dispose(this.adapter_);
goog.dispose(this.handler_);
goog.dispose(this.soundController_);
goog.base(this, 'disposeInternal');
};
/**
* Gets the handwriting Input Tool code of current language code.
*
* @return {string} The handwriting Input Tool code.
* @private
*/
Controller.prototype.getHwtInputToolCode_ = function() {
return this.languageCode_.split(/_|-/)[0] +
Controller.HANDWRITING_CODE_SUFFIX_;
};
/**
* True to enable settings link.
*
* @return {boolean} .
*/
Controller.prototype.shouldEnableSettings = function() {
return !this.adapter_.screen || this.adapter_.screen == 'normal';
};
/**
* Gets the active keyset, if there is a keyset to switch, return it.
* otherwise if it's a password box, return the password keyset,
* otherwise return the current keyset.
*
* @return {string} .
* @private
*/
Controller.prototype.getActiveKeyset_ = function() {
var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
return keySetMap[this.adapter_.getContextType()] ||
keySetMap[ContextType.DEFAULT];
};
/**
* True if keysetB is the sub keyset of keysetA.
*
* @param {string} keysetA .
* @param {string} keysetB .
* @return {boolean} .
* @private
*/
Controller.prototype.isSubKeyset_ = function(keysetA, keysetB) {
var segmentsA = keysetA.split('.');
var segmentsB = keysetB.split('.');
return segmentsA.length >= 2 && segmentsB.length >= 2 &&
segmentsA[0] == segmentsB[0] && segmentsA[1] == segmentsB[1];
};
/**
* Updates the compact pinyin to set the inputcode for english and pinyin.
*
* @param {string} fromRawKeyset .
* @param {string} toRawKeyset .
* @private
*/
Controller.prototype.updateLanguageState_ =
function(fromRawKeyset, toRawKeyset) {
var toggle = false;
var toggleState = false;
// Deal with the switch logic to/from English within the compact layout.
if (fromRawKeyset.indexOf('en.compact') *
toRawKeyset.indexOf('en.compact') < 0) { // Switches between non-en/en.
toggle = true;
toggleState = toRawKeyset.indexOf('en.compact') == -1;
} else if (fromRawKeyset.indexOf(toRawKeyset) == 0 &&
fromRawKeyset.indexOf('.compact') > 0 ||
fromRawKeyset && toRawKeyset.indexOf(fromRawKeyset) == 0 &&
toRawKeyset.indexOf('.compact') > 0) {
// Switch between full/compact layouts, reset the default button and
// language.
toggle = true;
toggleState = true;
}
if (toggle) {
this.adapter_.toggleLanguageState(toggleState);
this.model_.stateManager.isEnMode = !toggleState;
this.container_.currentKeysetView.update();
}
};
/**
* Records the stats which will be reported when input view is closing.
*
* @param {string} name The metrics name.
* @param {number} count The count value for histogram.
* @param {number} max .
* @param {number} bucketCount .
* @private
*/
Controller.prototype.recordStatsForClosing_ = function(
name, count, max, bucketCount) {
if (!this.statsForClosing_[name]) {
this.statsForClosing_[name] = [count, max, bucketCount];
} else {
this.statsForClosing_[name][0] += count;
this.statsForClosing_[name][1] = max;
this.statsForClosing_[name][2] = bucketCount;
}
};
}); // goog.scope