1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
2 // limitations under the License.
3 // See the License for the specific language governing permissions and
4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5 // distributed under the License is distributed on an "AS-IS" BASIS,
6 // Unless required by applicable law or agreed to in writing, software
8 // http://www.apache.org/licenses/LICENSE-2.0
10 // You may obtain a copy of the License at
11 // you may not use this file except in compliance with the License.
12 // Licensed under the Apache License, Version 2.0 (the "License");
14 goog.provide('i18n.input.chrome.inputview.Controller');
16 goog.require('goog.Disposable');
17 goog.require('goog.Timer');
18 goog.require('goog.array');
19 goog.require('goog.async.Delay');
20 goog.require('goog.dom.classlist');
21 goog.require('goog.events.Event');
22 goog.require('goog.events.EventHandler');
23 goog.require('goog.events.EventType');
24 goog.require('goog.events.KeyCodes');
25 goog.require('goog.i18n.bidi');
26 goog.require('goog.object');
27 goog.require('i18n.input.chrome.DataSource');
28 goog.require('i18n.input.chrome.Statistics');
29 goog.require('i18n.input.chrome.inputview.Adapter');
30 goog.require('i18n.input.chrome.inputview.CandidatesInfo');
31 goog.require('i18n.input.chrome.inputview.ConditionName');
32 goog.require('i18n.input.chrome.inputview.Css');
33 goog.require('i18n.input.chrome.inputview.FeatureName');
34 goog.require('i18n.input.chrome.inputview.KeyboardContainer');
35 goog.require('i18n.input.chrome.inputview.M17nModel');
36 goog.require('i18n.input.chrome.inputview.Model');
37 goog.require('i18n.input.chrome.inputview.PerfTracker');
38 goog.require('i18n.input.chrome.inputview.ReadyState');
39 goog.require('i18n.input.chrome.inputview.Settings');
40 goog.require('i18n.input.chrome.inputview.SizeSpec');
41 goog.require('i18n.input.chrome.inputview.SpecNodeName');
42 goog.require('i18n.input.chrome.inputview.StateType');
43 goog.require('i18n.input.chrome.inputview.SwipeDirection');
44 goog.require('i18n.input.chrome.inputview.elements.ElementType');
45 goog.require('i18n.input.chrome.inputview.elements.content.Candidate');
46 goog.require('i18n.input.chrome.inputview.elements.content.CandidateView');
47 goog.require('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView');
48 goog.require('i18n.input.chrome.inputview.elements.content.MenuView');
49 goog.require('i18n.input.chrome.inputview.events.EventType');
50 goog.require('i18n.input.chrome.inputview.events.KeyCodes');
51 goog.require('i18n.input.chrome.inputview.handler.PointerHandler');
52 goog.require('i18n.input.chrome.inputview.util');
53 goog.require('i18n.input.chrome.message.ContextType');
54 goog.require('i18n.input.chrome.message.Name');
55 goog.require('i18n.input.chrome.message.Type');
56 goog.require('i18n.input.chrome.sounds.SoundController');
57 goog.require('i18n.input.lang.InputToolCode');
61 goog.scope(function() {
62 var CandidateType = i18n.input.chrome.inputview.elements.content.Candidate.Type;
63 var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView;
64 var ConditionName = i18n.input.chrome.inputview.ConditionName;
65 var ContextType = i18n.input.chrome.message.ContextType;
66 var Css = i18n.input.chrome.inputview.Css;
67 var ElementType = i18n.input.chrome.inputview.elements.ElementType;
68 var EventType = i18n.input.chrome.inputview.events.EventType;
69 var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content.
70 ExpandedCandidateView;
71 var FeatureName = i18n.input.chrome.inputview.FeatureName;
72 var InputToolCode = i18n.input.lang.InputToolCode;
73 var KeyCodes = i18n.input.chrome.inputview.events.KeyCodes;
74 var MenuView = i18n.input.chrome.inputview.elements.content.MenuView;
75 var Name = i18n.input.chrome.message.Name;
76 var PerfTracker = i18n.input.chrome.inputview.PerfTracker;
77 var SizeSpec = i18n.input.chrome.inputview.SizeSpec;
78 var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
79 var StateType = i18n.input.chrome.inputview.StateType;
80 var SoundController = i18n.input.chrome.sounds.SoundController;
81 var Type = i18n.input.chrome.message.Type;
82 var events = i18n.input.chrome.inputview.events;
83 var util = i18n.input.chrome.inputview.util;
88 * The controller of the input view keyboard.
90 * @param {string} keyset The keyboard keyset.
91 * @param {string} languageCode The language code for this keyboard.
92 * @param {string} passwordLayout The layout for password box.
93 * @param {string} name The input tool name.
95 * @extends {goog.Disposable}
97 i18n.input.chrome.inputview.Controller = function(keyset, languageCode,
98 passwordLayout, name) {
102 * @type {!i18n.input.chrome.inputview.Model}
105 this.model_ = new i18n.input.chrome.inputview.Model();
107 /** @private {!i18n.input.chrome.inputview.PerfTracker} */
108 this.perfTracker_ = new i18n.input.chrome.inputview.PerfTracker(
109 PerfTracker.TickName.HTML_LOADED);
114 * @type {!Object.<string, !Object>}
117 this.layoutDataMap_ = {};
122 * @private {!Object.<ElementType, !KeyCodes>}
124 this.elementTypeToKeyCode_ = goog.object.create(
125 ElementType.BOLD, KeyCodes.KEY_B,
126 ElementType.ITALICS, KeyCodes.KEY_I,
127 ElementType.UNDERLINE, KeyCodes.KEY_U,
128 ElementType.COPY, KeyCodes.KEY_C,
129 ElementType.PASTE, KeyCodes.KEY_V,
130 ElementType.CUT, KeyCodes.KEY_X,
131 ElementType.SELECT_ALL, KeyCodes.KEY_A,
132 ElementType.REDO, KeyCodes.KEY_Y,
133 ElementType.UNDO, KeyCodes.KEY_Z
137 * The keyset data map.
139 * @type {!Object.<string, !Object>}
142 this.keysetDataMap_ = {};
147 * @type {!goog.events.EventHandler}
150 this.handler_ = new goog.events.EventHandler(this);
155 * @type {!i18n.input.chrome.inputview.M17nModel}
158 this.m17nModel_ = new i18n.input.chrome.inputview.M17nModel();
161 * The pointer handler.
163 * @type {!i18n.input.chrome.inputview.handler.PointerHandler}
166 this.pointerHandler_ = new i18n.input.chrome.inputview.handler.
170 * The statistics object for recording metrics values.
172 * @type {!i18n.input.chrome.Statistics}
175 this.statistics_ = i18n.input.chrome.Statistics.getInstance();
177 /** @private {!i18n.input.chrome.inputview.ReadyState} */
178 this.readyState_ = new i18n.input.chrome.inputview.ReadyState();
180 /** @private {!i18n.input.chrome.inputview.Adapter} */
181 this.adapter_ = new i18n.input.chrome.inputview.Adapter(this.readyState_);
183 /** @private {!SoundController} */
184 this.soundController_ = new SoundController(false);
187 * Whether or not to commit the next gesture result.
191 this.commitNextGestureResult_ = false;
193 /** @private {!i18n.input.chrome.inputview.KeyboardContainer} */
194 this.container_ = new i18n.input.chrome.inputview.KeyboardContainer(
195 this.adapter_, this.soundController_);
198 * The context type and keyset mapping group by input method id.
199 * key: input method id.
201 * key: context type string.
202 * value: keyset string.
204 * @private {!Object.<string, !Object.<string, string>>}
206 this.contextTypeToKeysetMap_ = {};
210 * The previous raw keyset code before switched to hwt or emoji layout.
211 * key: context type string.
212 * value: keyset string.
214 * @private {!Object.<string, string>}
216 this.contextTypeToLastKeysetMap_ = {};
219 * The stats map for input view closing.
221 * @type {!Object.<string, !Array.<number>>}
224 this.statsForClosing_ = {};
227 * The time the last point was accepted.
231 this.lastPointTime_ = 0;
234 * The last height sent to window.resizeTo to avoid multiple equivalent calls.
238 this.lastResizeHeight_ = -1;
241 * The activate (show) time stamp for statistics.
246 this.showTimeStamp_ = new Date();
248 this.initialize(keyset, languageCode, passwordLayout, name);
251 * Note: sets a default empty result to avoid null check.
253 * @private {!i18n.input.chrome.inputview.CandidatesInfo}
255 this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty();
257 this.registerEventHandler_();
259 goog.inherits(i18n.input.chrome.inputview.Controller,
261 var Controller = i18n.input.chrome.inputview.Controller;
265 * @define {boolean} Flag to disable handwriting.
267 Controller.DISABLE_HWT = false;
271 * A flag to indicate whether the shift is for auto capital.
275 Controller.prototype.shiftForAutoCapital_ = false;
279 * @define {boolean} Flag to indicate whether it is debugging.
281 Controller.DEV = false;
285 * The handwriting view code, use the code can switch handwriting panel view.
290 Controller.HANDWRITING_VIEW_CODE_ = 'hwt';
294 * The emoji view code, use the code can switch to emoji.
299 Controller.EMOJI_VIEW_CODE_ = 'emoji';
303 * The limitation for backspace repeat time to avoid unexpected
304 * problem that backspace is held all the time.
308 Controller.BACKSPACE_REPEAT_LIMIT_ = 255;
312 * The repeated times of the backspace.
316 Controller.prototype.backspaceRepeated_ = 0;
320 * The handwriting input tool code suffix.
325 Controller.HANDWRITING_CODE_SUFFIX_ = '-t-i0-handwrit';
329 * The US English compact layout prefix.
334 Controller.US_COMPACT_PREFIX_ = 'us.compact';
338 * Time threshold between samples sent to the back end.
342 Controller.SUBSAMPLING_TIME_THRESHOLD = 100;
346 * True if the settings is loaded.
350 Controller.prototype.isSettingReady = false;
354 * True if the keyboard is set up.
355 * Note: This flag is only used for automation testing.
359 Controller.prototype.isKeyboardReady = false;
363 * True if the current keyset is the US english compact layout.
368 Controller.prototype.isKeysetUSCompact_ = false;
372 * The auto repeat timer for backspace hold.
374 * @type {goog.async.Delay}
377 Controller.prototype.backspaceAutoRepeat_;
381 * The initial keyset determined by inputview url and/or settings.
386 Controller.prototype.initialKeyset_ = '';
390 * The current raw keyset code.
395 Controller.prototype.currentKeyset_ = '';
399 * The current input method id.
403 Controller.prototype.currentInputmethod_ = '';
407 * The operations on candidates.
411 Controller.CandidatesOperation = {
419 * A temporary list to track keysets have customized in material design.
421 * @private {!Array.<string>}
423 Controller.MATERIAL_KEYSETS_ = [
430 * The active language code.
435 Controller.prototype.lang_;
439 * The password keyset.
443 Controller.prototype.passwordKeyset_ = '';
447 * The soft key map, because key configuration is loaded before layout,
448 * controller needs this varaible to save it and hook into keyboard view.
450 * @type {!Array.<!i18n.input.chrome.inputview.elements.content.SoftKey>}
453 Controller.prototype.softKeyList_;
457 * The mapping from soft key id to soft key view id.
459 * @type {!Object.<string, string>}
462 Controller.prototype.mapping_;
466 * The input tool name.
471 Controller.prototype.title_;
475 * Whether to return to the standard character keyset after space is touched.
480 Controller.prototype.returnToLetterKeysetOnSpace_ = false;
484 * A cache of the previous gesture results.
486 * @private {!Array.<string>}
488 Controller.prototype.gestureResultsCache_;
492 * Registers event handlers.
495 Controller.prototype.registerEventHandler_ = function() {
498 EventType.LAYOUT_LOADED,
499 this.onLayoutLoaded_).
501 EventType.CONFIG_LOADED,
502 this.onConfigLoaded_).
503 listen(this.m17nModel_,
504 EventType.CONFIG_LOADED,
505 this.onConfigLoaded_).
506 listen(this.pointerHandler_, [
507 EventType.LONG_PRESS,
509 EventType.DOUBLE_CLICK,
510 EventType.DOUBLE_CLICK_END,
511 EventType.POINTER_UP,
512 EventType.POINTER_DOWN,
513 EventType.POINTER_OVER,
514 EventType.POINTER_OUT,
516 ], this.onPointerEvent_).
517 listen(this.pointerHandler_,
520 listen(window, goog.events.EventType.RESIZE, this.resize).
521 listen(this.adapter_,
522 EventType.SURROUNDING_TEXT_CHANGED, this.onSurroundingTextChanged_).
523 listen(this.adapter_,
524 i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK,
525 this.onCandidatesBack_).
526 listen(this.adapter_,
527 i18n.input.chrome.DataSource.EventType.GESTURES_BACK,
528 this.onGesturesBack_).
529 listen(this.adapter_, EventType.URL_CHANGED, this.onURLChanged_).
530 listen(this.adapter_, EventType.CONTEXT_FOCUS, this.onContextFocus_).
531 listen(this.adapter_, EventType.CONTEXT_BLUR, this.onContextBlur_).
532 listen(this.adapter_, EventType.VISIBILITY_CHANGE,
533 this.onVisibilityChange_).
534 listen(this.adapter_, EventType.SETTINGS_READY, this.onSettingsReady_).
535 listen(this.adapter_, EventType.UPDATE_SETTINGS, this.onUpdateSettings_).
536 listen(this.adapter_, EventType.FRONT_TOGGLE_LANGUAGE_STATE,
537 this.onUpdateToggleLanguageState_).
538 listen(this.adapter_, EventType.VOICE_STATE_CHANGE,
539 this.onVoiceStateChange_).
540 listen(this.adapter_, EventType.REFRESH, this.onRefresh_);
545 * Handler for voice module state change.
547 * @param {!events.MessageEvent} e .
550 Controller.prototype.onVoiceStateChange_ = function(e) {
551 if (!e.msg[Name.VOICE_STATE]) {
552 this.container_.candidateView.switchToIcon(
553 CandidateView.IconType.VOICE, true);
554 this.container_.voiceView.stop();
560 * Handles the refresh event from adapter.
564 Controller.prototype.onRefresh_ = function() {
565 window.location.reload();
570 * Sets the default keyset for context types.
572 * @param {string} newKeyset .
575 Controller.prototype.setDefaultKeyset_ = function(newKeyset) {
576 var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
577 for (var context in keysetMap) {
578 if (context != ContextType.DEFAULT &&
579 keysetMap[context] == keysetMap[ContextType.DEFAULT]) {
580 keysetMap[context] = newKeyset;
583 keysetMap[ContextType.DEFAULT] = this.initialKeyset_ = newKeyset;
588 * Callback for updating settings.
590 * @param {!events.MessageEvent} e .
593 Controller.prototype.onUpdateSettings_ = function(e) {
594 var settings = this.model_.settings;
595 if (goog.isDef(e.msg['autoSpace'])) {
596 settings.autoSpace = e.msg['autoSpace'];
598 if (goog.isDef(e.msg['autoCapital'])) {
599 settings.autoCapital = e.msg['autoCapital'];
601 if (goog.isDef(e.msg['candidatesNavigation'])) {
602 settings.candidatesNavigation = e.msg['candidatesNavigation'];
603 this.container_.candidateView.setNavigation(settings.candidatesNavigation);
605 if (goog.isDef(e.msg[Name.KEYSET])) {
606 this.setDefaultKeyset_(e.msg[Name.KEYSET]);
608 if (goog.isDef(e.msg['enableLongPress'])) {
609 settings.enableLongPress = e.msg['enableLongPress'];
611 if (goog.isDef(e.msg['doubleSpacePeriod'])) {
612 settings.doubleSpacePeriod = e.msg['doubleSpacePeriod'];
614 if (goog.isDef(e.msg['soundOnKeypress'])) {
615 settings.soundOnKeypress = e.msg['soundOnKeypress'];
616 this.soundController_.setEnabled(settings.soundOnKeypress);
618 if (goog.isDef(e.msg['gestureEditing'])) {
619 settings.gestureEditing = e.msg['gestureEditing'];
620 var enabled = settings.gestureEditing && !this.adapter_.isA11yMode;
621 this.container_.swipeView.enabled = enabled;
622 this.container_.selectView.setSettingsEnabled(enabled);
624 if (goog.isDef(e.msg['gestureTyping'])) {
625 settings.gestureTyping = e.msg['gestureTyping'];
627 settings.gestureTyping = false;
629 this.perfTracker_.tick(PerfTracker.TickName.BACKGROUND_SETTINGS_FETCHED);
630 this.model_.stateManager.contextType = this.adapter_.contextType;
631 this.maybeCreateViews_();
636 * Callback for url changed.
640 Controller.prototype.onURLChanged_ = function() {
641 this.container_.candidateView.setToolbarVisible(this.shouldShowToolBar_());
646 * Callback for setting ready.
650 Controller.prototype.onSettingsReady_ = function() {
651 if (this.isSettingReady) {
655 this.isSettingReady = true;
656 // Don't render container twice.
657 if (!this.container_.isInDocument()) {
658 this.container_.render();
660 var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
662 if (this.adapter_.isA11yMode) {
663 newKeyset = util.getConfigName(keysetMap[ContextType.DEFAULT]);
665 newKeyset = /** @type {string} */ (this.model_.settings.
666 getPreference(util.getConfigName(keysetMap[ContextType.DEFAULT])));
668 if (!this.adapter_.features.isEnabled(FeatureName.EXPERIMENTAL) &&
669 keysetMap[ContextType.DEFAULT] ==
670 'zhuyin.compact.qwerty') {
671 newKeyset = 'zhuyin';
674 this.setDefaultKeyset_(newKeyset);
676 this.container_.selectView.setSettingsEnabled(
677 this.model_.settings.gestureEditing && !this.adapter_.isA11yMode);
678 // Loads resources in case the default keyset is changed.
679 this.loadAllResources_();
680 this.maybeCreateViews_();
685 * Returns whether or not the gesture typing feature is enabled.
690 Controller.prototype.gestureTypingEnabled_ = function() {
691 return this.isKeysetUSCompact_ && this.model_.settings.gestureTyping &&
692 !this.adapter_.isA11yMode && !this.adapter_.isChromeVoxOn;
697 * Returns the time threshold for subsampling, in ms.
702 Controller.prototype.subsamplingThreshold_ = function() {
703 return Controller.SUBSAMPLING_TIME_THRESHOLD;
708 * Gets the data for spatial module.
710 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key .
711 * @param {number} x The x-offset of the touch point.
712 * @param {number} y The y-offset of the touch point.
713 * @return {!Object} .
716 Controller.prototype.getSpatialData_ = function(key, x, y) {
718 items.push([this.getKeyContent_(key), key.estimator.estimateInLogSpace(x, y)
720 for (var i = 0; i < key.nearbyKeys.length; i++) {
721 var nearByKey = key.nearbyKeys[i];
722 var content = this.getKeyContent_(nearByKey);
723 if (content && util.REGEX_LANGUAGE_MODEL_CHARACTERS.test(content)) {
724 items.push([content, nearByKey.estimator.estimateInLogSpace(x, y)]);
727 goog.array.sort(items, function(item1, item2) {
728 return item1[1] - item2[1];
730 var sources = items.map(function(item) {
731 return item[0].toLowerCase();
733 var possibilities = items.map(function(item) {
738 'possibilities': possibilities
744 * Gets the key content.
746 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key .
750 Controller.prototype.getKeyContent_ = function(key) {
751 if (key.type == i18n.input.chrome.inputview.elements.ElementType.
753 key = /** @type {!i18n.input.chrome.inputview.elements.content.
754 CharacterKey} */ (key);
755 return key.getActiveCharacter();
757 if (key.type == i18n.input.chrome.inputview.elements.ElementType.
759 key = /** @type {!i18n.input.chrome.inputview.elements.content.
760 FunctionalKey} */ (key);
768 * Callback for pointer event.
770 * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
773 Controller.prototype.onPointerEvent_ = function(e) {
774 if (e.type == EventType.LONG_PRESS) {
775 if (this.adapter_.isChromeVoxOn || !this.model_.settings.enableLongPress) {
778 var keyset = this.keysetDataMap_[this.currentKeyset_];
779 var layout = keyset && keyset[SpecNodeName.LAYOUT];
780 var data = layout && this.layoutDataMap_[layout];
781 if (data && data[SpecNodeName.DISABLE_LONGPRESS]) {
786 // POINTER_UP event may be dispatched without a view. This is the case when
787 // user selected an accent character which is displayed outside of the
788 // keyboard window bounds. For other cases, we expect a view associated with a
790 if (e.type == EventType.POINTER_UP && !e.view) {
791 if (this.container_.altDataView.isVisible() &&
792 e.identifier == this.container_.altDataView.identifier) {
793 var altDataView = this.container_.altDataView;
794 var ch = altDataView.getHighlightedCharacter();
796 this.adapter_.sendKeyDownAndUpEvent(ch, altDataView.triggeredBy.id,
797 altDataView.triggeredBy.keyCode,
798 {'sources': [ch.toLowerCase()], 'possibilities': [1]});
801 this.clearUnstickyState_();
806 if (e.type == EventType.POINTER_UP) {
807 this.stopBackspaceAutoRepeat_();
811 this.handlePointerAction_(e.view, e);
817 * Sends the last stroke from the gesture canvas view to the gesture decoder, if
818 * the last point was added past a time threshold.
820 * @param {boolean=} opt_force Whether or not to force send the gesture event.
823 Controller.prototype.maybeSendLastStroke_ = function(opt_force) {
824 // Subsample by returning early if the previous point was added too recently.
825 var currentTime = Date.now();
826 if (!opt_force && currentTime - this.lastPointTime_ <
827 this.subsamplingThreshold_()) {
830 this.lastPointTime_ = currentTime;
832 var lastStroke = this.container_.gestureCanvasView.getLastStroke();
834 // This call will set up the necessary callbacks the decoder will use to
835 // communicate back to this class.
836 this.adapter_.sendGestureEvent(lastStroke.points);
842 * Handles the drag events. Generally, this will forward the event details to
843 * the components that handle drawing, decoding, etc.
845 * @param {!i18n.input.chrome.inputview.events.DragEvent} e .
848 Controller.prototype.onDragEvent_ = function(e) {
849 if (this.gestureTypingEnabled_() && e.type == EventType.DRAG &&
850 !this.container_.swipeView.isVisible()) {
851 this.container_.gestureCanvasView.addPoint(e);
852 if (e.view && this.container_.gestureCanvasView.isGesturing) {
853 // Ensure the last touched key is not highlighted.
854 e.view.setHighlighted(false);
855 this.maybeSendLastStroke_();
863 * Handles the swipe action.
865 * @param {!i18n.input.chrome.inputview.elements.Element} view The view, for
866 * swipe event, the view would be the soft key which starts the swipe.
867 * @param {!i18n.input.chrome.inputview.events.SwipeEvent} e The swipe event.
870 Controller.prototype.handleSwipeAction_ = function(view, e) {
871 var direction = e.direction;
872 if (this.container_.altDataView.isVisible()) {
873 this.container_.altDataView.highlightItem(e.x, e.y, e.identifier);
876 if (view.type == ElementType.BACKSPACE_KEY) {
877 if (this.container_.swipeView.isVisible() ||
878 this.container_.swipeView.isArmed()) {
879 this.stopBackspaceAutoRepeat_();
884 if (view.type == ElementType.CHARACTER_KEY) {
885 view = /** @type {!i18n.input.chrome.inputview.elements.content.
886 CharacterKey} */ (view);
887 if (direction & i18n.input.chrome.inputview.SwipeDirection.UP ||
888 direction & i18n.input.chrome.inputview.SwipeDirection.DOWN) {
889 var ch = view.getCharacterByGesture(!!(direction &
890 i18n.input.chrome.inputview.SwipeDirection.UP));
892 view.flickerredCharacter = ch;
897 if (view.type == ElementType.COMPACT_KEY) {
898 view = /** @type {!i18n.input.chrome.inputview.elements.content.
899 CompactKey} */ (view);
900 if ((direction & i18n.input.chrome.inputview.SwipeDirection.UP) &&
902 view.flickerredCharacter = view.hintText;
911 * @param {!i18n.input.chrome.inputview.elements.content.MenuView.Command}
912 * command The command that about to be executed.
913 * @param {string=} opt_arg The optional command argument.
916 Controller.prototype.executeCommand_ = function(command, opt_arg) {
917 var CommandEnum = MenuView.Command;
919 case CommandEnum.SWITCH_IME:
920 var inputMethodId = opt_arg;
922 this.adapter_.switchToInputMethod(inputMethodId);
926 case CommandEnum.SWITCH_KEYSET:
927 var keyset = opt_arg;
929 this.recordStatsForClosing_(
930 'InputMethod.VirtualKeyboard.LayoutSwitch', 1, 25, 25);
931 this.switchToKeyset(keyset);
934 case CommandEnum.OPEN_EMOJI:
935 this.contextTypeToLastKeysetMap_[this.adapter_.contextType] =
937 this.switchToKeyset(Controller.EMOJI_VIEW_CODE_);
940 case CommandEnum.OPEN_HANDWRITING:
941 this.contextTypeToLastKeysetMap_[this.adapter_.contextType] =
943 // TODO: remember handwriting keyset.
944 this.switchToKeyset(Controller.HANDWRITING_VIEW_CODE_);
947 case CommandEnum.OPEN_SETTING:
948 if (window.inputview) {
949 inputview.openSettings();
953 case CommandEnum.FLOATING:
954 if (inputview.setMode) {
955 inputview.setMode('FLOATING');
956 this.adapter_.isFloating = true;
957 this.container_.candidateView.setFloatingVKButtonsVisible(true);
959 setTimeout(function() {
960 var x = Math.floor((screen.width - window.innerWidth) / 2);
961 window.moveTo(x, window.screenY);
966 case CommandEnum.DOCKING:
967 if (inputview.setMode) {
968 inputview.setMode('FULL_WIDTH');
969 this.adapter_.isFloating = false;
970 this.container_.candidateView.setFloatingVKButtonsVisible(false);
979 * Handles the pointer action.
981 * @param {!i18n.input.chrome.inputview.elements.Element} view The view.
982 * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
985 Controller.prototype.handlePointerAction_ = function(view, e) {
986 if (this.gestureTypingEnabled_() && !this.container_.swipeView.isVisible()) {
987 if (e.type == EventType.POINTER_DOWN) {
988 this.container_.gestureCanvasView.startStroke(e);
989 view.setHighlighted(false);
992 // Determine if the gestureCanvasView was handling a gesture before calling
993 // endStroke, as it ends the current gesture.
994 var wasGesturing = this.container_.gestureCanvasView.isGesturing;
995 if (e.type == EventType.POINTER_UP && wasGesturing) {
996 this.container_.gestureCanvasView.endStroke(e);
997 this.maybeSendLastStroke_(true);
998 this.commitNextGestureResult_ = true;
1001 // Do not trigger other activities when gesturing.
1003 if (e.type == EventType.POINTER_OVER) {
1004 view.setHighlighted(false);
1010 // Listen for DOUBLE_CLICK as well to capture secondary taps on the spacebar.
1011 if (e.type == EventType.POINTER_UP || e.type == EventType.DOUBLE_CLICK) {
1012 this.recordStatsForClosing_(
1013 'InputMethod.VirtualKeyboard.TapCount', 1, 4095, 4096);
1016 if (e.type == EventType.SWIPE) {
1017 e = /** @type {!i18n.input.chrome.inputview.events.SwipeEvent} */ (e);
1018 this.handleSwipeAction_(view, e);
1020 switch (view.type) {
1021 case ElementType.KEYBOARD_CONTAINER_VIEW:
1022 if (e.type == EventType.POINTER_DOWN) {
1023 var tabbableKeysets = [
1024 Controller.HANDWRITING_VIEW_CODE_,
1025 Controller.EMOJI_VIEW_CODE_];
1026 if (goog.array.contains(tabbableKeysets, this.currentKeyset_)) {
1028 this.switchToKeyset(this.container_.currentKeysetView.fromKeyset);
1032 case ElementType.BACK_BUTTON:
1033 case ElementType.BACK_TO_KEYBOARD:
1034 if (e.type == EventType.POINTER_OUT || e.type == EventType.POINTER_UP) {
1035 view.setHighlighted(false);
1036 } else if (e.type == EventType.POINTER_DOWN ||
1037 e.type == EventType.POINTER_OVER) {
1038 view.setHighlighted(true);
1040 if (e.type == EventType.POINTER_UP) {
1041 this.switchToKeyset(this.container_.currentKeysetView.fromKeyset);
1042 this.clearCandidates_();
1043 this.soundController_.onKeyUp(view.type);
1046 case ElementType.EXPAND_CANDIDATES:
1047 if (e.type == EventType.POINTER_UP) {
1048 this.showCandidates_(this.candidatesInfo_.source,
1049 this.candidatesInfo_.candidates,
1050 Controller.CandidatesOperation.EXPAND);
1051 this.soundController_.onKeyUp(view.type);
1054 case ElementType.SHRINK_CANDIDATES:
1055 if (e.type == EventType.POINTER_UP) {
1056 this.showCandidates_(this.candidatesInfo_.source,
1057 this.candidatesInfo_.candidates,
1058 Controller.CandidatesOperation.SHRINK);
1059 this.soundController_.onKeyUp(view.type);
1062 case ElementType.CANDIDATE:
1063 view = /** @type {!i18n.input.chrome.inputview.elements.content.
1064 Candidate} */ (view);
1065 if (view.candidateType == CandidateType.TOOLTIP)
1067 if (e.type == EventType.POINTER_UP) {
1068 if (view.candidateType == CandidateType.CANDIDATE) {
1069 this.adapter_.selectCandidate(view.candidate);
1070 } else if (view.candidateType == CandidateType.NUMBER) {
1071 this.adapter_.sendKeyDownAndUpEvent(
1072 view.candidate[Name.CANDIDATE], '');
1074 this.container_.cleanStroke();
1075 this.soundController_.onKeyUp(ElementType.CANDIDATE);
1077 if (e.type == EventType.POINTER_OUT || e.type == EventType.POINTER_UP) {
1078 view.setHighlighted(false);
1079 } else if (e.type == EventType.POINTER_DOWN ||
1080 e.type == EventType.POINTER_OVER) {
1081 view.setHighlighted(true);
1085 case ElementType.ALTDATA_VIEW:
1086 view = /** @type {!i18n.input.chrome.inputview.elements.content.
1087 AltDataView} */ (view);
1088 if (e.type == EventType.POINTER_UP && e.identifier == view.identifier) {
1089 var ch = view.getHighlightedCharacter();
1091 this.adapter_.sendKeyDownAndUpEvent(ch, view.triggeredBy.id,
1092 view.triggeredBy.keyCode,
1093 {'sources': [ch.toLowerCase()], 'possibilities': [1]});
1096 this.clearUnstickyState_();
1097 this.soundController_.onKeyUp(view.type);
1101 case ElementType.MENU_ITEM:
1102 view = /** @type {!i18n.input.chrome.inputview.elements.content.
1103 MenuItem} */ (view);
1104 if (e.type == EventType.POINTER_UP) {
1105 this.executeCommand_.apply(this, view.getCommand());
1106 this.container_.menuView.hide();
1107 this.soundController_.onKeyUp(view.type);
1110 view.setHighlighted(e.type == EventType.POINTER_DOWN ||
1111 e.type == EventType.POINTER_OVER);
1112 // TODO: Add chrome vox support.
1115 case ElementType.MENU_VIEW:
1116 view = /** @type {!i18n.input.chrome.inputview.elements.content.
1117 MenuView} */ (view);
1119 if (e.type == EventType.CLICK &&
1120 e.target == view.getCoverElement()) {
1125 case ElementType.EMOJI_KEY:
1126 if (e.type == EventType.CLICK) {
1127 if (!this.container_.currentKeysetView.isDragging && view.text != '') {
1128 this.adapter_.commitText(view.text);
1129 this.soundController_.onKeyUp(view.type);
1134 case ElementType.HWT_PRIVACY_GOT_IT:
1135 // Broadcasts the handwriting privacy confirmed message to let canvas
1137 if (e.type == EventType.POINTER_UP) {
1138 this.adapter_.dispatchEvent(new goog.events.Event(
1139 Type.HWT_PRIVACY_GOT_IT));
1143 case ElementType.VOICE_PRIVACY_GOT_IT:
1144 // Broadcasts the voice privacy confirmed message to let voice
1146 if (e.type == EventType.POINTER_UP) {
1147 this.adapter_.dispatchEvent(new goog.events.Event(
1148 Type.VOICE_PRIVACY_GOT_IT));
1152 case ElementType.VOICE_VIEW:
1153 if (e.type == EventType.POINTER_UP) {
1154 this.adapter_.sendVoiceViewStateChange(false);
1155 this.container_.candidateView.switchToIcon(
1156 CandidateView.IconType.VOICE, true);
1157 this.container_.voiceView.stop();
1160 case ElementType.SWIPE_VIEW:
1161 this.stopBackspaceAutoRepeat_();
1162 if (e.type == EventType.POINTER_UP ||
1163 e.type == EventType.POINTER_OUT) {
1164 this.clearUnstickyState_();
1167 case ElementType.DRAG:
1168 if (e.type == EventType.POINTER_DOWN && this.container_.floatingView) {
1169 this.container_.floatingView.show();
1172 case ElementType.FLOATING_VIEW:
1173 if (e.type == EventType.POINTER_UP && this.container_.floatingView) {
1174 this.container_.floatingView.hide();
1177 case ElementType.RESIZE:
1178 if (e.type == EventType.POINTER_UP) {
1179 goog.dom.classlist.toggle(this.container_.getElement(), Css.SMALL);
1183 case ElementType.CUT:
1184 case ElementType.COPY:
1185 case ElementType.PASTE:
1186 case ElementType.BOLD:
1187 case ElementType.ITALICS:
1188 case ElementType.UNDERLINE:
1189 case ElementType.REDO:
1190 case ElementType.UNDO:
1191 case ElementType.SELECT_ALL:
1192 view.setHighlighted(e.type == EventType.POINTER_DOWN ||
1193 e.type == EventType.POINTER_OVER);
1194 if (e.type == EventType.POINTER_UP) {
1195 this.adapter_.sendKeyDownAndUpEvent(
1196 '', this.elementTypeToKeyCode_[view.type], undefined, undefined, {
1203 case ElementType.SOFT_KEY_VIEW:
1204 // Delegates the events on the soft key view to its soft key.
1205 view = /** @type {!i18n.input.chrome.inputview.elements.layout.
1206 SoftKeyView} */ (view);
1207 if (!view.softKey) {
1210 view = view.softKey;
1213 if (view.type != ElementType.MODIFIER_KEY &&
1214 !this.container_.altDataView.isVisible() &&
1215 !this.container_.menuView.isVisible() &&
1216 !this.container_.swipeView.isVisible()) {
1217 // The highlight of the modifier key is depending on the state instead
1218 // of the key down or up.
1219 if (e.type == EventType.POINTER_OVER || e.type == EventType.POINTER_DOWN ||
1220 e.type == EventType.DOUBLE_CLICK) {
1221 view.setHighlighted(true);
1222 } else if (e.type == EventType.POINTER_OUT ||
1223 e.type == EventType.POINTER_UP ||
1224 e.type == EventType.DOUBLE_CLICK_END) {
1225 view.setHighlighted(false);
1228 view = /** @type {!i18n.input.chrome.inputview.elements.content.
1230 this.handlePointerEventForSoftKey_(view, e);
1231 this.updateContextModifierState_();
1236 * Handles softkey of the pointer action.
1238 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} softKey .
1239 * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
1242 Controller.prototype.handlePointerEventForSoftKey_ = function(softKey, e) {
1244 switch (softKey.type) {
1245 case ElementType.VOICE_BTN:
1246 if (e.type == EventType.POINTER_UP) {
1247 this.container_.candidateView.switchToIcon(
1248 CandidateView.IconType.VOICE, false);
1249 this.container_.voiceView.start();
1252 case ElementType.CANDIDATES_PAGE_UP:
1253 if (e.type == EventType.POINTER_UP) {
1254 this.container_.expandedCandidateView.pageUp();
1257 case ElementType.CANDIDATES_PAGE_DOWN:
1258 if (e.type == EventType.POINTER_UP) {
1259 this.container_.expandedCandidateView.pageDown();
1262 case ElementType.CHARACTER_KEY:
1263 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1264 CharacterKey} */ (softKey);
1265 if (e.type == EventType.LONG_PRESS) {
1266 this.container_.altDataView.show(
1267 key, goog.i18n.bidi.isRtlLanguage(this.languageCode_),
1269 } else if (e.type == EventType.POINTER_UP) {
1270 this.model_.stateManager.triggerChording();
1271 var ch = key.getActiveCharacter();
1273 this.adapter_.sendKeyDownAndUpEvent(ch, key.id, key.keyCode,
1274 this.getSpatialData_(key, e.x, e.y));
1276 this.clearUnstickyState_();
1277 key.flickerredCharacter = '';
1281 case ElementType.MODIFIER_KEY:
1282 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1283 ModifierKey} */(softKey);
1284 var isStateEnabled = this.model_.stateManager.hasState(key.toState);
1285 var isChording = this.model_.stateManager.isChording(key.toState);
1286 if (e.type == EventType.POINTER_DOWN) {
1287 this.changeState_(key.toState, !isStateEnabled, true, false);
1288 this.model_.stateManager.setKeyDown(key.toState, true);
1289 } else if (e.type == EventType.POINTER_UP || e.type == EventType.
1292 this.changeState_(key.toState, false, false);
1293 } else if (key.toState == StateType.CAPSLOCK) {
1294 this.changeState_(key.toState, isStateEnabled, true, true);
1295 } else if (this.model_.stateManager.isKeyDown(key.toState)) {
1296 this.changeState_(key.toState, isStateEnabled, false);
1298 this.model_.stateManager.setKeyDown(key.toState, false);
1299 } else if (e.type == EventType.DOUBLE_CLICK) {
1300 this.changeState_(key.toState, isStateEnabled, true, true);
1301 } else if (e.type == EventType.LONG_PRESS) {
1303 this.changeState_(key.toState, true, true, true);
1304 this.model_.stateManager.setKeyDown(key.toState, false);
1309 case ElementType.BACKSPACE_KEY:
1310 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1311 FunctionalKey} */(softKey);
1312 if (e.type == EventType.POINTER_DOWN) {
1313 this.backspaceTick_();
1314 } else if (e.type == EventType.POINTER_UP || e.type == EventType.
1316 if (!this.container_.swipeView.isVisible()) {
1317 this.stopBackspaceAutoRepeat_();
1320 this.returnToLetterKeysetOnSpace_ = false;
1323 case ElementType.TAB_KEY:
1324 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1325 FunctionalKey} */ (softKey);
1326 if (e.type == EventType.POINTER_DOWN) {
1327 this.adapter_.sendKeyDownEvent('\u0009', KeyCodes.TAB);
1328 } else if (e.type == EventType.POINTER_UP) {
1329 this.adapter_.sendKeyUpEvent('\u0009', KeyCodes.TAB);
1331 this.returnToLetterKeysetOnSpace_ = false;
1334 case ElementType.ENTER_KEY:
1335 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1336 FunctionalKey} */ (softKey);
1337 if (e.type == EventType.POINTER_UP) {
1338 this.adapter_.sendKeyDownAndUpEvent('\u000D', KeyCodes.ENTER);
1342 case ElementType.ARROW_UP:
1343 if (e.type == EventType.POINTER_DOWN) {
1344 this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_UP);
1345 } else if (e.type == EventType.POINTER_UP) {
1346 this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_UP);
1350 case ElementType.ARROW_DOWN:
1351 if (e.type == EventType.POINTER_DOWN) {
1352 this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_DOWN);
1353 } else if (e.type == EventType.POINTER_UP) {
1354 this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_DOWN);
1358 case ElementType.ARROW_LEFT:
1359 if (e.type == EventType.POINTER_DOWN) {
1360 this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_LEFT);
1361 } else if (e.type == EventType.POINTER_UP) {
1362 this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_LEFT);
1366 case ElementType.ARROW_RIGHT:
1367 if (e.type == EventType.POINTER_DOWN) {
1368 this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_RIGHT);
1369 } else if (e.type == EventType.POINTER_UP) {
1370 this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_RIGHT);
1373 case ElementType.EN_SWITCHER:
1374 if (e.type == EventType.POINTER_UP) {
1375 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1376 EnSwitcherKey} */ (softKey);
1377 this.adapter_.toggleLanguageState(this.model_.stateManager.isEnMode);
1378 this.model_.stateManager.isEnMode = !this.model_.stateManager.isEnMode;
1382 case ElementType.SPACE_KEY:
1383 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1384 SpaceKey} */ (softKey);
1385 var doubleSpacePeriod = this.model_.settings.doubleSpacePeriod &&
1386 this.currentKeyset_ != Controller.HANDWRITING_VIEW_CODE_ &&
1387 this.currentKeyset_ != Controller.EMOJI_VIEW_CODE_;
1388 if (e.type == EventType.POINTER_UP || (!doubleSpacePeriod && e.type ==
1389 EventType.DOUBLE_CLICK_END)) {
1390 this.adapter_.sendKeyDownAndUpEvent(key.getCharacter(),
1392 this.clearUnstickyState_();
1393 } else if (e.type == EventType.DOUBLE_CLICK && doubleSpacePeriod) {
1394 this.adapter_.doubleClickOnSpaceKey();
1396 if (this.returnToLetterKeysetOnSpace_) {
1397 // Return to the letter keyset.
1398 this.switchToKeyset(key.toKeyset);
1399 this.returnToLetterKeysetOnSpace_ = false;
1403 case ElementType.SWITCHER_KEY:
1404 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1405 SwitcherKey} */ (softKey);
1406 if (e.type == EventType.POINTER_UP) {
1407 this.recordStatsForClosing_(
1408 'InputMethod.VirtualKeyboard.LayoutSwitch', 1, 25, 25);
1409 if (this.isSubKeyset_(key.toKeyset, this.currentKeyset_)) {
1410 this.model_.stateManager.reset();
1411 this.container_.update();
1412 this.updateContextModifierState_();
1413 this.container_.menuView.hide();
1417 // Switch to the specific keyboard.
1418 this.switchToKeyset(key.toKeyset);
1420 this.model_.settings.savePreference(
1421 util.getConfigName(key.toKeyset),
1425 this.returnToLetterKeysetOnSpace_ = false;
1428 case ElementType.COMPACT_KEY:
1429 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1430 CompactKey} */(softKey);
1431 if (e.type == EventType.LONG_PRESS) {
1432 this.container_.altDataView.show(
1433 key, goog.i18n.bidi.isRtlLanguage(this.languageCode_),
1435 } else if (e.type == EventType.POINTER_UP) {
1436 this.model_.stateManager.triggerChording();
1437 var ch = key.getActiveCharacter();
1438 if (ch.length == 1) {
1439 this.adapter_.sendKeyDownAndUpEvent(key.getActiveCharacter(), '', 0,
1440 this.getSpatialData_(key, e.x, e.y));
1441 } else if (ch.length > 1) {
1442 // Some compact keys contains more than 1 characters, such as '.com',
1443 // 'http://', etc. Those keys should trigger a direct commit text
1444 // instead of key events.
1445 this.adapter_.commitText(ch);
1447 this.clearUnstickyState_();
1448 key.flickerredCharacter = '';
1449 if (this.currentKeyset_.indexOf('symbol') != -1) {
1450 // If this is the symbol keyset, a space as the next input should
1451 // switch us to the standard keyset.
1452 this.returnToLetterKeysetOnSpace_ = true;
1457 case ElementType.HIDE_KEYBOARD_KEY:
1458 var defaultKeyset = this.getActiveKeyset_();
1459 if (e.type == EventType.POINTER_UP) {
1460 this.adapter_.hideKeyboard();
1461 if (this.currentKeyset_ != defaultKeyset) {
1462 this.switchToKeyset(defaultKeyset);
1467 case ElementType.MENU_KEY:
1468 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1469 MenuKey} */ (softKey);
1470 if (e.type == EventType.POINTER_DOWN) {
1471 var isCompact = this.currentKeyset_.indexOf('compact') != -1;
1472 // Gets the default full keyboard instead of default keyset because
1473 // the default keyset can be a compact keyset which would cause problem
1474 // in MenuView.show().
1475 var defaultFullKeyset = this.initialKeyset_.split(/\./)[0];
1476 var enableCompact = !this.adapter_.isA11yMode && goog.array.contains(
1477 util.KEYSETS_HAVE_COMPACT, defaultFullKeyset);
1478 if (defaultFullKeyset == 'zhuyin' &&
1479 !this.adapter_.features.isEnabled(FeatureName.EXPERIMENTAL) ||
1480 this.languageCode_ == 'ko') {
1481 // Hides 'switch to compact' for zhuyin when not in experimental env.
1482 enableCompact = false;
1484 var hasHwt = !this.adapter_.isPasswordBox() &&
1485 !Controller.DISABLE_HWT && goog.object.contains(
1486 InputToolCode, this.getHwtInputToolCode_());
1487 var hasEmoji = !this.adapter_.isPasswordBox();
1488 var enableSettings = this.shouldEnableSettings() &&
1489 !!window.inputview && !!inputview.openSettings;
1490 var enableFVK = this.adapter_.isFloatingVirtualKeyboardEnabled();
1491 this.adapter_.getInputMethods(function(inputMethods) {
1492 this.container_.menuView.show(key, defaultFullKeyset, isCompact,
1493 enableCompact, this.currentInputMethod_, inputMethods, hasHwt,
1494 enableSettings, hasEmoji, this.adapter_.isA11yMode, enableFVK,
1495 this.adapter_.isFloating);
1500 case ElementType.GLOBE_KEY:
1501 if (e.type == EventType.POINTER_UP) {
1502 this.adapter_.clearModifierStates();
1503 this.adapter_.setModifierState(
1504 i18n.input.chrome.inputview.StateType.ALT, true);
1505 this.adapter_.sendKeyDownAndUpEvent(
1506 KeyCodes.SHIFT, KeyCodes.SHIFT_LEFT, goog.events.KeyCodes.SHIFT);
1507 this.adapter_.setModifierState(
1508 i18n.input.chrome.inputview.StateType.ALT, false);
1511 case ElementType.IME_SWITCH:
1512 key = /** @type {!i18n.input.chrome.inputview.elements.content.
1513 FunctionalKey} */ (softKey);
1514 this.adapter_.sendKeyDownAndUpEvent('', key.id);
1517 // Play key sound on pointer up or double click.
1518 if (e.type == EventType.POINTER_UP || e.type == EventType.DOUBLE_CLICK)
1519 this.soundController_.onKeyUp(softKey.type);
1524 * Clears unsticky state.
1528 Controller.prototype.clearUnstickyState_ = function() {
1529 if (this.model_.stateManager.hasUnStickyState()) {
1530 for (var key in StateType) {
1531 var value = StateType[key];
1532 if (this.model_.stateManager.hasState(value) &&
1533 !this.model_.stateManager.isSticky(value)) {
1534 this.changeState_(value, false, false);
1538 this.container_.update();
1543 * Stops the auto-repeat for backspace.
1547 Controller.prototype.stopBackspaceAutoRepeat_ = function() {
1548 if (this.backspaceAutoRepeat_) {
1549 goog.dispose(this.backspaceAutoRepeat_);
1550 this.backspaceAutoRepeat_ = null;
1551 this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE);
1552 this.backspaceRepeated_ = 0;
1558 * The tick for the backspace key.
1562 Controller.prototype.backspaceTick_ = function() {
1563 if (this.backspaceRepeated_ >= Controller.BACKSPACE_REPEAT_LIMIT_) {
1564 this.stopBackspaceAutoRepeat_();
1567 this.backspaceRepeated_++;
1568 this.backspaceDown_();
1569 this.soundController_.onKeyRepeat(ElementType.BACKSPACE_KEY);
1571 if (this.backspaceAutoRepeat_) {
1572 this.backspaceAutoRepeat_.start(75);
1574 this.backspaceAutoRepeat_ = new goog.async.Delay(
1575 goog.bind(this.backspaceTick_, this), 300);
1576 this.backspaceAutoRepeat_.start();
1582 * Callback for VISIBILITY_CHANGE.
1586 Controller.prototype.onVisibilityChange_ = function() {
1587 if (!this.adapter_.isVisible) {
1588 for (var name in this.statsForClosing_) {
1589 var stat = this.statsForClosing_[name];
1590 this.statistics_.recordValue(name, stat[0], stat[1], stat[2]);
1592 this.statistics_.recordValue('InputMethod.VirtualKeyboard.Duration',
1593 Math.floor((new Date() - this.showTimeStamp_) / 1000), 4096, 50);
1594 this.statsForClosing_ = {};
1595 this.showTimeStamp_ = new Date();
1602 * Resets the whole keyboard include clearing candidates,
1603 * reset modifier state, etc.
1605 Controller.prototype.resetAll = function() {
1606 this.clearCandidates_();
1607 this.container_.cleanStroke();
1608 this.model_.stateManager.reset();
1609 this.container_.update();
1610 this.updateContextModifierState_();
1612 this.container_.expandedCandidateView.close();
1613 this.container_.menuView.hide();
1614 this.container_.swipeView.reset();
1615 this.container_.altDataView.hide();
1616 this.container_.gesturePreviewView.hide();
1617 if (this.container_.floatingView) {
1618 this.container_.floatingView.hide();
1624 * Returns whether the toolbar should be shown.
1629 Controller.prototype.shouldShowToolBar_ = function() {
1630 return this.adapter_.features.isEnabled(FeatureName.OPTIMIZED_LAYOUTS) &&
1631 this.adapter_.isGoogleDocument() &&
1632 this.adapter_.contextType == ContextType.DEFAULT;
1637 * Callback when the context is changed.
1641 Controller.prototype.onContextFocus_ = function() {
1643 this.model_.stateManager.contextType = this.adapter_.contextType;
1644 this.switchToKeyset(this.getActiveKeyset_());
1649 * Callback when surrounding text is changed.
1651 * @param {!i18n.input.chrome.inputview.events.SurroundingTextChangedEvent} e .
1654 Controller.prototype.onSurroundingTextChanged_ = function(e) {
1655 if (!this.model_.settings.autoCapital || !e.text) {
1659 var isShiftEnabled = this.model_.stateManager.hasState(StateType.SHIFT);
1660 var needAutoCap = this.model_.settings.autoCapital &&
1661 util.needAutoCap(e.text);
1662 if (needAutoCap && !isShiftEnabled) {
1663 this.changeState_(StateType.SHIFT, true, false);
1664 this.shiftForAutoCapital_ = true;
1665 } else if (!needAutoCap && this.shiftForAutoCapital_) {
1666 this.changeState_(StateType.SHIFT, false, false);
1672 * Callback for Context blurs.
1676 Controller.prototype.onContextBlur_ = function() {
1677 this.container_.cleanStroke();
1678 this.container_.menuView.hide();
1683 * Backspace key is down.
1687 Controller.prototype.backspaceDown_ = function() {
1688 if (this.container_.hasStrokesOnCanvas()) {
1689 this.clearCandidates_();
1690 this.container_.cleanStroke();
1692 this.adapter_.sendKeyDownEvent('\u0008', KeyCodes.BACKSPACE);
1694 this.recordStatsForClosing_(
1695 'InputMethod.VirtualKeyboard.BackspaceCount', 1, 4095, 4096);
1696 this.statistics_.recordEnum('InputMethod.VirtualKeyboard.BackspaceOnLayout',
1697 this.statistics_.getLayoutType(this.currentKeyset_,
1698 this.adapter_.isA11yMode),
1699 i18n.input.chrome.Statistics.LayoutTypes.MAX);
1704 * Callback for state change.
1706 * @param {StateType} stateType The state type.
1707 * @param {boolean} enable True to enable the state.
1708 * @param {boolean} isSticky True to make the state sticky.
1709 * @param {boolean=} opt_isFinalSticky .
1712 Controller.prototype.changeState_ = function(stateType, enable, isSticky,
1713 opt_isFinalSticky) {
1714 if (stateType == StateType.ALTGR) {
1715 var code = KeyCodes.ALT_RIGHT;
1717 this.adapter_.sendKeyDownEvent('', code);
1719 this.adapter_.sendKeyUpEvent('', code);
1722 if (stateType == StateType.SHIFT) {
1723 this.shiftForAutoCapital_ = false;
1725 var isEnabledBefore = this.model_.stateManager.hasState(stateType);
1726 var isStickyBefore = this.model_.stateManager.isSticky(stateType);
1727 this.model_.stateManager.setState(stateType, enable);
1728 this.model_.stateManager.setSticky(stateType, isSticky);
1729 var isFinalSticky = goog.isDef(opt_isFinalSticky) ? opt_isFinalSticky :
1731 var isFinalStikcyBefore = this.model_.stateManager.isFinalSticky(stateType);
1732 this.model_.stateManager.setFinalSticky(stateType, isFinalSticky);
1733 if (isEnabledBefore != enable || isStickyBefore != isSticky ||
1734 isFinalStikcyBefore != isFinalSticky) {
1735 this.container_.update();
1741 * Updates the modifier state for context.
1745 Controller.prototype.updateContextModifierState_ = function() {
1746 var stateManager = this.model_.stateManager;
1747 this.adapter_.setModifierState(StateType.ALT,
1748 stateManager.hasState(StateType.ALT));
1749 this.adapter_.setModifierState(StateType.CTRL,
1750 stateManager.hasState(StateType.CTRL));
1751 this.adapter_.setModifierState(StateType.CAPSLOCK,
1752 stateManager.hasState(StateType.CAPSLOCK));
1753 if (!this.shiftForAutoCapital_) {
1754 // If shift key is automatically on because of feature - autoCapital,
1755 // Don't set modifier state to adapter.
1756 this.adapter_.setModifierState(StateType.SHIFT,
1757 stateManager.hasState(StateType.SHIFT));
1763 * Callback for AUTO-COMPLETE event.
1765 * @param {!i18n.input.chrome.DataSource.CandidatesBackEvent} e .
1768 Controller.prototype.onCandidatesBack_ = function(e) {
1769 this.candidatesInfo_ = new i18n.input.chrome.inputview.CandidatesInfo(
1770 e.source, e.candidates);
1771 this.showCandidates_(e.source, e.candidates, Controller.CandidatesOperation.
1777 * Callback for gestures results event.
1779 * @param {!i18n.input.chrome.DataSource.GesturesBackEvent} e .
1782 Controller.prototype.onGesturesBack_ = function(e) {
1783 if (!this.commitNextGestureResult_ &&
1784 goog.array.equals(e.results, this.gestureResultsCache_)) {
1785 // If gesture results have not updated, do not transmit to the UI.
1788 this.gestureResultsCache_ = e.results;
1790 var bestResult = e.results[0];
1791 if (this.container_.gesturePreviewView) {
1792 this.container_.gesturePreviewView.show(bestResult);
1794 // TODO: Resolve a race where multiple decoder results return after this flag
1796 if (this.commitNextGestureResult_) {
1797 // Commit the best result.
1798 this.adapter_.commitGestureResult(bestResult);
1799 this.commitNextGestureResult_ = false;
1800 this.gestureResultsCache_ = [];
1802 this.showGestureCandidates_(e.results.slice(1));
1807 * Shows the gesture results as candidates.
1809 * @param {!Array<string>} results The gesture results to show.
1812 Controller.prototype.showGestureCandidates_ = function(results) {
1813 // Convert the results to the candidate format.
1814 var candidates = [];
1815 for (var i = 0; i < results.length; ++i) {
1817 candidate[Name.CANDIDATE] = results[i];
1818 candidate[Name.CANDIDATE_ID] = i;
1819 candidate[Name.IS_EMOJI] = false;
1820 candidate[Name.MATCHED_LENGTHS] = 0;
1821 candidates.push(candidate);
1823 // The source is empty as this is a gesture and not a series of key presses.
1824 this.showCandidates_('', candidates, Controller.CandidatesOperation.NONE);
1829 * Shows the candidates to the candidate view.
1831 * @param {string} source The source text.
1832 * @param {!Array.<!Object>} candidates The candidate text list.
1833 * @param {Controller.CandidatesOperation} operation .
1836 Controller.prototype.showCandidates_ = function(source, candidates,
1838 var state = !!source ? ExpandedCandidateView.State.COMPLETION_CORRECTION :
1839 ExpandedCandidateView.State.PREDICTION;
1840 var expandView = this.container_.expandedCandidateView;
1842 if (operation == Controller.CandidatesOperation.NONE) {
1843 expand = expandView.state == state;
1845 expand = operation == Controller.CandidatesOperation.EXPAND;
1848 if (candidates.length == 0) {
1849 this.clearCandidates_();
1850 expandView.state = ExpandedCandidateView.State.NONE;
1854 // The compact pinyin needs full candidates instead of three candidates.
1855 var isThreeCandidates = this.currentKeyset_.indexOf('compact') != -1 &&
1856 this.currentKeyset_.indexOf('pinyin-zh-CN') == -1;
1857 if (isThreeCandidates) {
1858 if (candidates.length > 1) {
1859 // Swap the first candidate and the second candidate.
1860 var tmp = candidates[0];
1861 candidates[0] = candidates[1];
1862 candidates[1] = tmp;
1865 var isHwt = Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_;
1866 this.container_.candidateView.showCandidates(candidates, isThreeCandidates,
1867 this.model_.settings.candidatesNavigation && !isHwt);
1869 // Only sum of candidate is greater than top line count. Need to update
1871 if (expand && this.container_.candidateView.candidateCount <
1872 candidates.length) {
1873 expandView.state = state;
1874 this.container_.currentKeysetView.setVisible(false);
1875 expandView.showCandidates(candidates,
1876 this.container_.candidateView.candidateCount);
1877 this.container_.candidateView.switchToIcon(CandidateView.IconType.
1878 SHRINK_CANDIDATES, true);
1880 expandView.state = ExpandedCandidateView.State.NONE;
1881 expandView.setVisible(false);
1882 this.container_.candidateView.switchToIcon(CandidateView.IconType.
1883 SHRINK_CANDIDATES, false);
1884 this.container_.currentKeysetView.setVisible(true);
1890 * Clears candidates.
1894 Controller.prototype.clearCandidates_ = function() {
1895 this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty();
1896 this.container_.candidateView.clearCandidates();
1897 this.container_.expandedCandidateView.close();
1898 this.container_.expandedCandidateView.state = ExpandedCandidateView.State.
1900 if (this.container_.currentKeysetView) {
1901 this.container_.currentKeysetView.setVisible(true);
1904 if (this.currentKeyset_ == Controller.HANDWRITING_VIEW_CODE_ ||
1905 this.currentKeyset_ == Controller.EMOJI_VIEW_CODE_) {
1906 this.container_.candidateView.switchToIcon(
1907 CandidateView.IconType.VOICE, false);
1908 this.container_.candidateView.switchToIcon(
1909 CandidateView.IconType.EXPAND_CANDIDATES, false);
1911 this.container_.candidateView.switchToIcon(CandidateView.IconType.VOICE,
1912 this.adapter_.isVoiceInputEnabled);
1918 * Callback when the layout is loaded.
1920 * @param {!i18n.input.chrome.inputview.events.LayoutLoadedEvent} e The event.
1923 Controller.prototype.onLayoutLoaded_ = function(e) {
1924 var layoutID = e.data['layoutID'];
1925 this.layoutDataMap_[layoutID] = e.data;
1926 this.perfTracker_.tick(PerfTracker.TickName.LAYOUT_LOADED);
1927 this.maybeCreateViews_();
1932 * Creates a keyset view.
1934 * @param {string} keyset The non-raw keyset.
1937 Controller.prototype.createView_ = function(keyset) {
1938 if (this.isDisposed()) {
1941 var keysetData = this.keysetDataMap_[keyset];
1942 var layoutId = keysetData[SpecNodeName.LAYOUT];
1943 var layoutData = this.layoutDataMap_[layoutId];
1944 if (this.container_.keysetViewMap[keyset] || !layoutData) {
1947 var conditions = {};
1948 conditions[ConditionName.SHOW_ALTGR] =
1949 keysetData[SpecNodeName.HAS_ALTGR_KEY];
1951 conditions[ConditionName.SHOW_MENU] =
1952 keysetData[SpecNodeName.SHOW_MENU_KEY];
1953 // In symbol and more keysets, we want to show a symbol key in the globe
1954 // SoftKeyView. So this view should alway visible in the two keysets.
1955 // Currently, SHOW_MENU_KEY is false for the two keysets, so we use
1956 // !keysetData[SpecNodeName.SHOW_MENU_KEY] here.
1957 conditions[ConditionName.SHOW_GLOBE_OR_SYMBOL] =
1958 !keysetData[SpecNodeName.SHOW_MENU_KEY] ||
1959 this.adapter_.showGlobeKey;
1960 conditions[ConditionName.SHOW_EN_SWITCHER_KEY] = false;
1962 this.container_.addKeysetView(keysetData, layoutData, keyset,
1963 this.languageCode_, this.model_, this.title_, conditions);
1964 this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_CREATED);
1969 * Creates the whole view.
1973 Controller.prototype.maybeCreateViews_ = function() {
1974 if (!this.isSettingReady) {
1978 // Emoji is temp keyset which is delay loaded. So active keyset can be 'us'
1979 // while current keyset is 'emoji'. To make sure delay load can work
1980 // correctly, here need to create/switch to 'emoji' instead of 'us'.
1981 var activeKeyset = (this.currentKeyset_ == Controller.EMOJI_VIEW_CODE_) ?
1982 this.currentKeyset_ : this.getActiveKeyset_();
1983 var remappedActiveKeyset = this.getRemappedKeyset_(activeKeyset);
1984 var created = false;
1985 if (this.keysetDataMap_[remappedActiveKeyset]) {
1986 this.createView_(remappedActiveKeyset);
1987 this.switchToKeyset(activeKeyset);
1990 // Async creating the non-active keysets to reduce the latency of showing the
1992 var keyLen = Object.keys(this.keysetDataMap_).length;
1993 if (created && keyLen > 1 || !created && keyLen > 0) {
1994 goog.Timer.callOnce((function() {
1995 for (var keyset in this.keysetDataMap_) {
1996 this.createView_(keyset);
2004 * Switch to a specific keyboard.
2006 * @param {string} keyset The keyset name.
2008 Controller.prototype.switchToKeyset = function(keyset) {
2009 if (!this.isSettingReady || this.adapter_.isSwitching()) {
2013 var contextType = this.adapter_.contextType;
2014 var ret = this.container_.switchToKeyset(this.getRemappedKeyset_(keyset),
2015 this.title_, this.adapter_.isPasswordBox(), this.adapter_.isA11yMode,
2016 keyset, this.contextTypeToLastKeysetMap_[contextType] ||
2017 this.getActiveKeyset_(), this.languageCode_);
2020 if (!this.isSubKeyset_(this.currentKeyset_, keyset) &&
2021 keyset != Controller.EMOJI_VIEW_CODE_) {
2022 // If it is the sub keyset switching, or emoji, don't record it.
2023 // Update the keyset of current context type.
2024 this.contextTypeToKeysetMap_[this.currentInputMethod_][contextType] =
2027 this.updateLanguageState_(this.currentKeyset_, keyset);
2028 this.currentKeyset_ = keyset;
2029 this.resize(Controller.DEV);
2030 this.statistics_.recordLayout(keyset, this.adapter_.isA11yMode);
2031 this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_SHOWN);
2032 this.perfTracker_.stop();
2034 // Sets the current keyset for delay switching.
2035 this.currentKeyset_ = keyset;
2036 if (keyset != Controller.EMOJI_VIEW_CODE_) { // Emoji is temp keyset.
2037 this.contextTypeToKeysetMap_[this.currentInputMethod_][contextType] =
2040 this.loadResource_(keyset);
2043 // TODO: The 'us' part of this code is a workaround an issue where other xkb
2044 // languages seem to be sharing options between each other.
2045 this.isKeysetUSCompact_ =
2046 this.currentKeyset_.indexOf(Controller.US_COMPACT_PREFIX_) >= 0;
2047 // If we're switching to a new keyset, we don't want spacebar to trigger
2048 // another keyset switch.
2049 this.returnToLetterKeysetOnSpace_ = false;
2054 * Callback when the configuration is loaded.
2056 * @param {!i18n.input.chrome.inputview.events.ConfigLoadedEvent} e The event.
2059 Controller.prototype.onConfigLoaded_ = function(e) {
2060 if (this.isDisposed()) {
2064 var keyboardCode = data[i18n.input.chrome.inputview.SpecNodeName.ID];
2065 this.keysetDataMap_[keyboardCode] = data;
2066 this.perfTracker_.tick(PerfTracker.TickName.KEYSET_LOADED);
2067 var context = data[i18n.input.chrome.inputview.SpecNodeName.ON_CONTEXT];
2068 if (context && !this.adapter_.isA11yMode) {
2069 var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
2071 keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {};
2073 keySetMap[context] = keyboardCode;
2076 var layoutId = data[i18n.input.chrome.inputview.SpecNodeName.LAYOUT];
2077 data[i18n.input.chrome.inputview.SpecNodeName.LAYOUT] = layoutId;
2078 var layoutData = this.layoutDataMap_[layoutId];
2080 this.maybeCreateViews_();
2082 this.model_.loadLayout(layoutId);
2088 * Resizes the whole UI.
2090 * @param {boolean=} opt_preventResizeTo True if prevent calling
2091 * window.resizeTo. Used in tests and local UI debug.
2093 Controller.prototype.resize = function(opt_preventResizeTo) {
2097 var candidateViewHeight;
2098 var isLandScape = screen.width > screen.height;
2100 goog.dom.classlist.addRemove(this.container_.getElement(),
2101 Css.PORTRAIT, Css.LANDSCAPE);
2103 goog.dom.classlist.addRemove(this.container_.getElement(),
2104 Css.LANDSCAPE, Css.PORTRAIT);
2106 var isWideScreen = (Math.min(screen.width, screen.height) / Math.max(
2107 screen.width, screen.height)) < 0.6;
2108 this.model_.stateManager.covariance.update(isWideScreen, isLandScape,
2109 this.adapter_.isA11yMode);
2110 if (this.adapter_.isA11yMode) {
2111 height = SizeSpec.A11Y_HEIGHT;
2112 widthPercent = screen.width > screen.height ? SizeSpec.A11Y_WIDTH_PERCENT.
2113 LANDSCAPE : SizeSpec.A11Y_WIDTH_PERCENT.PORTRAIT;
2114 candidateViewHeight = SizeSpec.A11Y_CANDIDATE_VIEW_HEIGHT;
2116 var keyset = this.keysetDataMap_[this.currentKeyset_];
2117 var layout = keyset && keyset[SpecNodeName.LAYOUT];
2118 var data = layout && this.layoutDataMap_[layout];
2119 var spec = data && data[SpecNodeName.WIDTH_PERCENT] ||
2120 SizeSpec.NON_A11Y_WIDTH_PERCENT;
2121 height = SizeSpec.NON_A11Y_HEIGHT;
2124 widthPercent = spec['LANDSCAPE_WIDE_SCREEN'];
2126 widthPercent = spec['LANDSCAPE'];
2129 widthPercent = spec['PORTRAIT'];
2131 candidateViewHeight = SizeSpec.NON_A11Y_CANDIDATE_VIEW_HEIGHT;
2133 width = this.adapter_.isFloating ?
2134 Math.floor(screen.width * widthPercent) : screen.width;
2135 widthPercent = this.adapter_.isFloating ? 1.0 : widthPercent;
2136 if (goog.dom.classlist.contains(this.container_.getElement(), Css.SMALL)) {
2137 height = SizeSpec.SMALL_SIZE_HEIGHT;
2138 width = SizeSpec.SMALL_SIZE_WIDTH;
2139 candidateViewHeight = SizeSpec.SMALL_SIZE_CANDIDATE_VIEW_HEIGHT;
2142 if ((window.innerHeight != height || window.innerWidth != width) &&
2143 !opt_preventResizeTo) {
2144 if (this.lastResizeHeight_ != height || window.innerWidth != width) {
2145 this.lastResizeHeight_ = height;
2146 window.resizeTo(width, height);
2151 this.container_.setContainerSize(width, height, widthPercent,
2152 candidateViewHeight);
2153 this.container_.candidateView.setToolbarVisible(this.shouldShowToolBar_());
2154 if (this.container_.currentKeysetView) {
2155 this.isKeyboardReady = true;
2158 // Transmit the new layout to the decoder.
2159 if (this.gestureTypingEnabled_()) {
2160 this.adapter_.sendKeyboardLayout(
2161 this.container_.currentKeysetView.getKeyboardLayoutForGesture());
2167 * Loads the resources, for currentKeyset, passwdKeyset, handwriting,
2172 Controller.prototype.loadAllResources_ = function() {
2173 var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
2174 goog.array.forEach([keysetMap[ContextType.DEFAULT],
2175 keysetMap[ContextType.PASSWORD]], function(keyset) {
2176 this.loadResource_(keyset);
2182 * Gets the remapped keyset.
2184 * @param {string} keyset .
2185 * @return {string} The remapped keyset.
2188 Controller.prototype.getRemappedKeyset_ = function(keyset) {
2189 if (goog.array.contains(util.KEYSETS_USE_US, keyset)) {
2192 var match = keyset.match(/^(.*)-rtl$/);
2193 if (match && goog.array.contains(util.KEYSETS_USE_US, match[1])) {
2201 * Loads a single resource.
2203 * @param {string} keyset .
2207 Controller.prototype.loadResource_ = function(keyset) {
2208 var remapped = this.getRemappedKeyset_(keyset);
2209 if (!this.keysetDataMap_[remapped]) {
2210 if (/^m17n:/.test(remapped)) {
2211 this.m17nModel_.loadConfig(remapped);
2213 this.model_.loadConfig(remapped);
2218 var layoutId = this.keysetDataMap_[remapped][SpecNodeName.LAYOUT];
2219 if (!this.layoutDataMap_[layoutId]) {
2220 this.model_.loadLayout(layoutId);
2227 * Sets the keyboard.
2229 * @param {string} keyset The keyboard keyset.
2230 * @param {string} languageCode The language code for this keyboard.
2231 * @param {string} passwordLayout The layout for password box.
2232 * @param {string} title The title for this keyboard.
2234 Controller.prototype.initialize = function(keyset, languageCode, passwordLayout,
2236 this.perfTracker_.restart();
2237 this.adapter_.getCurrentInputMethod(function(currentInputMethod) {
2238 // TODO: remove this hack as soon as the manifest is fixed in chromium.
2239 if (languageCode == 'ko') {
2240 if (currentInputMethod.indexOf('hangul_2set') > 0) {
2241 keyset = 'm17n:ko_2set';
2244 this.languageCode_ = languageCode;
2245 // If can't get the current input method, set the original keyset as
2246 // current input method.
2247 this.currentInputMethod_ = currentInputMethod || keyset;
2248 var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
2250 keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {};
2252 keySetMap[ContextType.PASSWORD] = passwordLayout;
2253 keySetMap[ContextType.DEFAULT] = keyset;
2255 this.initialKeyset_ = keyset;
2256 this.title_ = title;
2257 this.isSettingReady = false;
2258 this.model_.settings = new i18n.input.chrome.inputview.Settings();
2259 this.model_.stateManager.isEnMode = false;
2260 this.adapter_.initialize();
2261 this.loadAllResources_();
2262 this.switchToKeyset(this.getActiveKeyset_());
2264 // Set language attribute and font of body.
2265 document.body.setAttribute('lang', this.languageCode_);
2266 goog.dom.classlist.add(document.body, Css.FONT);
2272 Controller.prototype.disposeInternal = function() {
2273 goog.dispose(this.container_);
2274 goog.dispose(this.adapter_);
2275 goog.dispose(this.handler_);
2276 goog.dispose(this.soundController_);
2278 goog.base(this, 'disposeInternal');
2283 * Gets the handwriting Input Tool code of current language code.
2285 * @return {string} The handwriting Input Tool code.
2288 Controller.prototype.getHwtInputToolCode_ = function() {
2289 return this.languageCode_.split(/_|-/)[0] +
2290 Controller.HANDWRITING_CODE_SUFFIX_;
2295 * True to enable settings link.
2297 * @return {boolean} .
2299 Controller.prototype.shouldEnableSettings = function() {
2300 return !this.adapter_.screen || this.adapter_.screen == 'normal';
2305 * Gets the active keyset, if there is a keyset to switch, return it.
2306 * otherwise if it's a password box, return the password keyset,
2307 * otherwise return the current keyset.
2309 * @return {string} .
2312 Controller.prototype.getActiveKeyset_ = function() {
2313 var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
2314 return keySetMap[this.adapter_.contextType] || this.initialKeyset_;
2319 * True if keysetB is the sub keyset of keysetA.
2321 * @param {string} keysetA .
2322 * @param {string} keysetB .
2323 * @return {boolean} .
2326 Controller.prototype.isSubKeyset_ = function(keysetA, keysetB) {
2327 var segmentsA = keysetA.split('.');
2328 var segmentsB = keysetB.split('.');
2329 return segmentsA.length >= 2 && segmentsB.length >= 2 &&
2330 segmentsA[0] == segmentsB[0] && segmentsA[1] == segmentsB[1];
2335 * Updates the compact pinyin to set the inputcode for english and pinyin.
2337 * @param {string} fromRawKeyset .
2338 * @param {string} toRawKeyset .
2341 Controller.prototype.updateLanguageState_ =
2342 function(fromRawKeyset, toRawKeyset) {
2344 var toggleState = false;
2345 if (fromRawKeyset != toRawKeyset) {
2346 // Deal with the switch logic to/from English within the compact layout.
2347 if (fromRawKeyset.indexOf('en.compact') *
2348 toRawKeyset.indexOf('en.compact') < 0) { // Switches between non-en/en.
2350 toggleState = toRawKeyset.indexOf('en.compact') == -1;
2351 } else if (fromRawKeyset.indexOf(toRawKeyset) == 0 &&
2352 fromRawKeyset.indexOf('.compact') > 0 &&
2353 goog.array.contains(util.KEYSETS_HAVE_EN_SWTICHER, toRawKeyset) ||
2354 fromRawKeyset && toRawKeyset.indexOf(fromRawKeyset) == 0 &&
2355 toRawKeyset.indexOf('.compact') > 0) {
2356 // Switch between full/compact layouts, reset the default button and
2363 this.adapter_.toggleLanguageState(toggleState);
2364 this.model_.stateManager.isEnMode = !toggleState;
2365 this.container_.currentKeysetView.update();
2371 * Records the stats which will be reported when input view is closing.
2373 * @param {string} name The metrics name.
2374 * @param {number} count The count value for histogram.
2375 * @param {number} max .
2376 * @param {number} bucketCount .
2379 Controller.prototype.recordStatsForClosing_ = function(
2380 name, count, max, bucketCount) {
2381 if (!this.statsForClosing_[name]) {
2382 this.statsForClosing_[name] = [count, max, bucketCount];
2384 this.statsForClosing_[name][0] += count;
2385 this.statsForClosing_[name][1] = max;
2386 this.statsForClosing_[name][2] = bucketCount;
2392 * Handles language state changing event.
2394 * @param {!events.MessageEvent} e .
2397 Controller.prototype.onUpdateToggleLanguageState_ = function(e) {
2398 if (this.adapter_.isA11yMode || this.currentKeyset_.indexOf('.compact') < 0) {
2399 // e.msg value means whether is Chinese mode now.
2400 if (this.model_.stateManager.isEnMode == e.msg) {
2401 this.model_.stateManager.isEnMode = !e.msg;
2402 this.container_.currentKeysetView.update();
2405 var pos = this.currentKeyset_.indexOf('en.compact');
2407 if (pos > 0) { // Means en mode
2408 if (e.msg) { // Needs switch cn mode
2409 toKeyset = this.currentKeyset_.replace('en.compact', 'compact');
2412 if (!e.msg) { // Needs switch en mode
2413 toKeyset = this.currentKeyset_.replace('compact', 'en.compact');
2418 this.switchToKeyset(toKeyset);