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 util
= i18n
.input
.chrome
.inputview
.util
;
87 * The controller of the input view keyboard.
89 * @param {string} keyset The keyboard keyset.
90 * @param {string} languageCode The language code for this keyboard.
91 * @param {string} passwordLayout The layout for password box.
92 * @param {string} name The input tool name.
94 * @extends {goog.Disposable}
96 i18n
.input
.chrome
.inputview
.Controller = function(keyset
, languageCode
,
97 passwordLayout
, name
) {
101 * @type {!i18n.input.chrome.inputview.Model}
104 this.model_
= new i18n
.input
.chrome
.inputview
.Model();
106 /** @private {!i18n.input.chrome.inputview.PerfTracker} */
107 this.perfTracker_
= new i18n
.input
.chrome
.inputview
.PerfTracker(
108 PerfTracker
.TickName
.HTML_LOADED
);
113 * @type {!Object.<string, !Object>}
116 this.layoutDataMap_
= {};
121 * @private {!Object.<ElementType, !KeyCodes>}
123 this.elementTypeToKeyCode_
= goog
.object
.create(
124 ElementType
.BOLD
, KeyCodes
.KEY_B
,
125 ElementType
.ITALICS
, KeyCodes
.KEY_I
,
126 ElementType
.UNDERLINE
, KeyCodes
.KEY_U
,
127 ElementType
.COPY
, KeyCodes
.KEY_C
,
128 ElementType
.PASTE
, KeyCodes
.KEY_V
,
129 ElementType
.CUT
, KeyCodes
.KEY_X
,
130 ElementType
.SELECT_ALL
, KeyCodes
.KEY_A
,
131 ElementType
.REDO
, KeyCodes
.KEY_Y
,
132 ElementType
.UNDO
, KeyCodes
.KEY_Z
136 * The keyset data map.
138 * @type {!Object.<string, !Object>}
141 this.keysetDataMap_
= {};
146 * @type {!goog.events.EventHandler}
149 this.handler_
= new goog
.events
.EventHandler(this);
154 * @type {!i18n.input.chrome.inputview.M17nModel}
157 this.m17nModel_
= new i18n
.input
.chrome
.inputview
.M17nModel();
160 * The pointer handler.
162 * @type {!i18n.input.chrome.inputview.handler.PointerHandler}
165 this.pointerHandler_
= new i18n
.input
.chrome
.inputview
.handler
.
169 * The statistics object for recording metrics values.
171 * @type {!i18n.input.chrome.Statistics}
174 this.statistics_
= i18n
.input
.chrome
.Statistics
.getInstance();
176 /** @private {!i18n.input.chrome.inputview.ReadyState} */
177 this.readyState_
= new i18n
.input
.chrome
.inputview
.ReadyState();
179 /** @private {!i18n.input.chrome.inputview.Adapter} */
180 this.adapter_
= new i18n
.input
.chrome
.inputview
.Adapter(this.readyState_
);
182 /** @private {!SoundController} */
183 this.soundController_
= new SoundController(false);
185 /** @private {!i18n.input.chrome.inputview.KeyboardContainer} */
186 this.container_
= new i18n
.input
.chrome
.inputview
.KeyboardContainer(
187 this.adapter_
, this.soundController_
);
188 this.container_
.render();
191 * The context type and keyset mapping group by input method id.
192 * key: input method id.
194 * key: context type string.
195 * value: keyset string.
197 * @private {!Object.<string, !Object.<string, string>>}
199 this.contextTypeToKeysetMap_
= {};
203 * The previous raw keyset code before switched to hwt or emoji layout.
204 * key: context type string.
205 * value: keyset string.
207 * @private {!Object.<string, string>}
209 this.contextTypeToLastKeysetMap_
= {};
212 * The stats map for input view closing.
214 * @type {!Object.<string, !Array.<number>>}
217 this.statsForClosing_
= {};
220 * The last height sent to window.resizeTo to avoid multiple equivalent calls.
224 this.lastResizeHeight_
= -1;
227 * The activate (show) time stamp for statistics.
232 this.showTimeStamp_
= new Date();
234 this.initialize(keyset
, languageCode
, passwordLayout
, name
);
237 * Note: sets a default empty result to avoid null check.
239 * @private {!i18n.input.chrome.inputview.CandidatesInfo}
241 this.candidatesInfo_
= i18n
.input
.chrome
.inputview
.CandidatesInfo
.getEmpty();
243 this.registerEventHandler_();
245 goog
.inherits(i18n
.input
.chrome
.inputview
.Controller
,
247 var Controller
= i18n
.input
.chrome
.inputview
.Controller
;
251 * @define {boolean} Flag to disable handwriting.
253 Controller
.DISABLE_HWT
= false;
257 * A flag to indicate whether the shift is for auto capital.
261 Controller
.prototype.shiftForAutoCapital_
= false;
265 * @define {boolean} Flag to indicate whether it is debugging.
267 Controller
.DEV
= false;
271 * The handwriting view code, use the code can switch handwriting panel view.
276 Controller
.HANDWRITING_VIEW_CODE_
= 'hwt';
280 * The emoji view code, use the code can switch to emoji.
285 Controller
.EMOJI_VIEW_CODE_
= 'emoji';
289 * The limitation for backspace repeat time to avoid unexpected
290 * problem that backspace is held all the time.
294 Controller
.BACKSPACE_REPEAT_LIMIT_
= 255;
298 * The repeated times of the backspace.
302 Controller
.prototype.backspaceRepeated_
= 0;
306 * The handwriting input tool code suffix.
311 Controller
.HANDWRITING_CODE_SUFFIX_
= '-t-i0-handwrit';
315 * True if the settings is loaded.
319 Controller
.prototype.isSettingReady
= false;
323 * True if the keyboard is set up.
324 * Note: This flag is only used for automation testing.
328 Controller
.prototype.isKeyboardReady
= false;
332 * The auto repeat timer for backspace hold.
334 * @type {goog.async.Delay}
337 Controller
.prototype.backspaceAutoRepeat_
;
341 * The initial keyset determined by inputview url and/or settings.
346 Controller
.prototype.initialKeyset_
= '';
350 * The current raw keyset code.
355 Controller
.prototype.currentKeyset_
= '';
359 * The current input method id.
363 Controller
.prototype.currentInputmethod_
= '';
367 * The operations on candidates.
371 Controller
.CandidatesOperation
= {
379 * A temporary list to track keysets have customized in material design.
381 * @private {!Array.<string>}
383 Controller
.MATERIAL_KEYSETS_
= [
390 * The active language code.
395 Controller
.prototype.lang_
;
399 * The password keyset.
403 Controller
.prototype.passwordKeyset_
= '';
407 * The soft key map, because key configuration is loaded before layout,
408 * controller needs this varaible to save it and hook into keyboard view.
410 * @type {!Array.<!i18n.input.chrome.inputview.elements.content.SoftKey>}
413 Controller
.prototype.softKeyList_
;
417 * The mapping from soft key id to soft key view id.
419 * @type {!Object.<string, string>}
422 Controller
.prototype.mapping_
;
426 * The input tool name.
431 Controller
.prototype.title_
;
435 * Registers event handlers.
438 Controller
.prototype.registerEventHandler_ = function() {
441 EventType
.LAYOUT_LOADED
,
442 this.onLayoutLoaded_
).
444 EventType
.CONFIG_LOADED
,
445 this.onConfigLoaded_
).
446 listen(this.m17nModel_
,
447 EventType
.CONFIG_LOADED
,
448 this.onConfigLoaded_
).
449 listen(this.pointerHandler_
, [
450 EventType
.LONG_PRESS
,
452 EventType
.DOUBLE_CLICK
,
453 EventType
.DOUBLE_CLICK_END
,
454 EventType
.POINTER_UP
,
455 EventType
.POINTER_DOWN
,
456 EventType
.POINTER_OVER
,
457 EventType
.POINTER_OUT
,
459 ], this.onPointerEvent_
).
460 listen(this.pointerHandler_
,
463 listen(window
, goog
.events
.EventType
.RESIZE
, this.resize
).
464 listen(this.adapter_
,
465 EventType
.SURROUNDING_TEXT_CHANGED
, this.onSurroundingTextChanged_
).
466 listen(this.adapter_
,
467 i18n
.input
.chrome
.DataSource
.EventType
.CANDIDATES_BACK
,
468 this.onCandidatesBack_
).
469 listen(this.adapter_
, EventType
.URL_CHANGED
, this.onURLChanged_
).
470 listen(this.adapter_
, EventType
.CONTEXT_FOCUS
, this.onContextFocus_
).
471 listen(this.adapter_
, EventType
.CONTEXT_BLUR
, this.onContextBlur_
).
472 listen(this.adapter_
, EventType
.VISIBILITY_CHANGE
,
473 this.onVisibilityChange_
).
474 listen(this.adapter_
, EventType
.SETTINGS_READY
, this.onSettingsReady_
).
475 listen(this.adapter_
, Type
.UPDATE_SETTINGS
, this.onUpdateSettings_
).
476 listen(this.adapter_
, Type
.FRONT_TOGGLE_LANGUAGE_STATE
,
477 this.onUpdateToggleLanguateState_
).
478 listen(this.adapter_
, Type
.VOICE_STATE_CHANGE
, this.onVoiceStateChange_
).
479 listen(this.adapter_
, EventType
.REFRESH
, this.onRefresh_
);
484 * Handler for voice module state change.
486 * @param {!i18n.input.chrome.message.Event} e .
489 Controller
.prototype.onVoiceStateChange_ = function(e
) {
490 if (!e
.msg
[Name
.VOICE_STATE
]) {
491 this.container_
.candidateView
.switchToIcon(
492 CandidateView
.IconType
.VOICE
, true);
493 this.container_
.voiceView
.stop();
499 * Handles the refresh event from adapter.
503 Controller
.prototype.onRefresh_ = function() {
504 window
.location
.reload();
509 * Sets the default keyset for context types.
511 * @param {string} newKeyset .
514 Controller
.prototype.setDefaultKeyset_ = function(newKeyset
) {
515 var keysetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
];
516 for (var context
in keysetMap
) {
517 if (context
!= ContextType
.DEFAULT
&&
518 keysetMap
[context
] == keysetMap
[ContextType
.DEFAULT
]) {
519 keysetMap
[context
] = newKeyset
;
522 keysetMap
[ContextType
.DEFAULT
] = this.initialKeyset_
= newKeyset
;
527 * Callback for updating settings.
529 * @param {!i18n.input.chrome.message.Event} e .
532 Controller
.prototype.onUpdateSettings_ = function(e
) {
533 var settings
= this.model_
.settings
;
534 if (goog
.isDef(e
.msg
['autoSpace'])) {
535 settings
.autoSpace
= e
.msg
['autoSpace'];
537 if (goog
.isDef(e
.msg
['autoCapital'])) {
538 settings
.autoCapital
= e
.msg
['autoCapital'];
540 if (goog
.isDef(e
.msg
['candidatesNavigation'])) {
541 settings
.candidatesNavigation
= e
.msg
['candidatesNavigation'];
542 this.container_
.candidateView
.setNavigation(settings
.candidatesNavigation
);
544 if (goog
.isDef(e
.msg
[Name
.KEYSET
])) {
545 this.setDefaultKeyset_(e
.msg
[Name
.KEYSET
]);
547 if (goog
.isDef(e
.msg
['enableLongPress'])) {
548 settings
.enableLongPress
= e
.msg
['enableLongPress'];
550 if (goog
.isDef(e
.msg
['doubleSpacePeriod'])) {
551 settings
.doubleSpacePeriod
= e
.msg
['doubleSpacePeriod'];
553 if (goog
.isDef(e
.msg
['soundOnKeypress'])) {
554 settings
.soundOnKeypress
= e
.msg
['soundOnKeypress'];
555 this.soundController_
.setEnabled(settings
.soundOnKeypress
);
557 this.perfTracker_
.tick(PerfTracker
.TickName
.BACKGROUND_SETTINGS_FETCHED
);
558 this.model_
.stateManager
.contextType
= this.adapter_
.contextType
;
559 this.maybeCreateViews_();
564 * Callback for url changed.
568 Controller
.prototype.onURLChanged_ = function() {
569 this.container_
.candidateView
.setToolbarVisible(this.shouldShowToolBar_());
574 * Callback for setting ready.
578 Controller
.prototype.onSettingsReady_ = function() {
579 if (this.isSettingReady
) {
583 this.isSettingReady
= true;
584 var keysetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
];
586 if (this.adapter_
.isA11yMode
) {
587 newKeyset
= util
.getConfigName(keysetMap
[ContextType
.DEFAULT
]);
589 newKeyset
= /** @type {string} */ (this.model_
.settings
.
590 getPreference(util
.getConfigName(keysetMap
[ContextType
.DEFAULT
])));
592 if (!this.adapter_
.features
.isEnabled(FeatureName
.EXPERIMENTAL
) &&
593 keysetMap
[ContextType
.DEFAULT
] ==
594 'zhuyin.compact.qwerty') {
595 newKeyset
= 'zhuyin';
598 this.setDefaultKeyset_(newKeyset
);
600 this.container_
.selectView
.setVisible(
601 this.adapter_
.features
.isEnabled(FeatureName
.GESTURE_SELECTION
));
602 // Loads resources in case the default keyset is changed.
603 this.loadAllResources_();
604 this.maybeCreateViews_();
609 * Gets the data for spatial module.
611 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key .
612 * @param {number} x The x-offset of the touch point.
613 * @param {number} y The y-offset of the touch point.
614 * @return {!Object} .
617 Controller
.prototype.getSpatialData_ = function(key
, x
, y
) {
619 items
.push([this.getKeyContent_(key
), key
.estimator
.estimateInLogSpace(x
, y
)
621 for (var i
= 0; i
< key
.nearbyKeys
.length
; i
++) {
622 var nearByKey
= key
.nearbyKeys
[i
];
623 var content
= this.getKeyContent_(nearByKey
);
624 if (content
&& util
.REGEX_LANGUAGE_MODEL_CHARACTERS
.test(content
)) {
625 items
.push([content
, nearByKey
.estimator
.estimateInLogSpace(x
, y
)]);
628 goog
.array
.sort(items
, function(item1
, item2
) {
629 return item1
[1] - item2
[1];
631 var sources
= items
.map(function(item
) {
632 return item
[0].toLowerCase();
634 var possibilities
= items
.map(function(item
) {
639 'possibilities': possibilities
645 * Gets the key content.
647 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key .
651 Controller
.prototype.getKeyContent_ = function(key
) {
652 if (key
.type
== i18n
.input
.chrome
.inputview
.elements
.ElementType
.
654 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
655 CharacterKey} */ (key
);
656 return key
.getActiveCharacter();
658 if (key
.type
== i18n
.input
.chrome
.inputview
.elements
.ElementType
.
660 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
661 FunctionalKey} */ (key
);
669 * Callback for pointer event.
671 * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
674 Controller
.prototype.onPointerEvent_ = function(e
) {
675 if (e
.type
== EventType
.LONG_PRESS
) {
676 if (this.adapter_
.isChromeVoxOn
|| !this.model_
.settings
.enableLongPress
) {
679 var keyset
= this.keysetDataMap_
[this.currentKeyset_
];
680 var layout
= keyset
&& keyset
[SpecNodeName
.LAYOUT
];
681 var data
= layout
&& this.layoutDataMap_
[layout
];
682 if (data
&& data
[SpecNodeName
.DISABLE_LONGPRESS
]) {
687 // POINTER_UP event may be dispatched without a view. This is the case when
688 // user selected an accent character which is displayed outside of the
689 // keyboard window bounds. For other cases, we expect a view associated with a
691 if (e
.type
== EventType
.POINTER_UP
&& !e
.view
) {
692 if (this.container_
.altDataView
.isVisible() &&
693 e
.identifier
== this.container_
.altDataView
.identifier
) {
694 var altDataView
= this.container_
.altDataView
;
695 var ch
= altDataView
.getHighlightedCharacter();
697 this.adapter_
.sendKeyDownAndUpEvent(ch
, altDataView
.triggeredBy
.id
,
698 altDataView
.triggeredBy
.keyCode
,
699 {'sources': [ch
.toLowerCase()], 'possibilities': [1]});
702 this.clearUnstickyState_();
707 if (e
.type
== EventType
.POINTER_UP
) {
708 this.stopBackspaceAutoRepeat_();
712 this.handlePointerAction_(e
.view
, e
);
718 * Handles the drag events. Generally, this will forward the event details to
719 * the components that handle drawing, decoding, etc.
721 * @param {!i18n.input.chrome.inputview.events.DragEvent} e .
724 Controller
.prototype.onDragEvent_ = function(e
) {
725 if (this.adapter_
.isGestureTypingEnabled() && e
.type
== EventType
.DRAG
) {
726 this.container_
.gestureCanvasView
.addPoint(e
);
733 * Handles the swipe action.
735 * @param {!i18n.input.chrome.inputview.elements.Element} view The view, for
736 * swipe event, the view would be the soft key which starts the swipe.
737 * @param {!i18n.input.chrome.inputview.events.SwipeEvent} e The swipe event.
740 Controller
.prototype.handleSwipeAction_ = function(view
, e
) {
741 var direction
= e
.direction
;
742 if (this.container_
.altDataView
.isVisible()) {
743 this.container_
.altDataView
.highlightItem(e
.x
, e
.y
, e
.identifier
);
746 if (view
.type
== ElementType
.BACKSPACE_KEY
) {
747 if (this.container_
.swipeView
.isVisible() ||
748 this.container_
.swipeView
.isArmed()) {
749 this.stopBackspaceAutoRepeat_();
754 if (view
.type
== ElementType
.CHARACTER_KEY
) {
755 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
756 CharacterKey} */ (view
);
757 if (direction
& i18n
.input
.chrome
.inputview
.SwipeDirection
.UP
||
758 direction
& i18n
.input
.chrome
.inputview
.SwipeDirection
.DOWN
) {
759 var ch
= view
.getCharacterByGesture(!!(direction
&
760 i18n
.input
.chrome
.inputview
.SwipeDirection
.UP
));
762 view
.flickerredCharacter
= ch
;
767 if (view
.type
== ElementType
.COMPACT_KEY
) {
768 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
769 CompactKey} */ (view
);
770 if ((direction
& i18n
.input
.chrome
.inputview
.SwipeDirection
.UP
) &&
772 view
.flickerredCharacter
= view
.hintText
;
781 * @param {!i18n.input.chrome.inputview.elements.content.MenuView.Command}
782 * command The command that about to be executed.
783 * @param {string=} opt_arg The optional command argument.
786 Controller
.prototype.executeCommand_ = function(command
, opt_arg
) {
787 var CommandEnum
= MenuView
.Command
;
789 case CommandEnum
.SWITCH_IME
:
790 var inputMethodId
= opt_arg
;
792 this.adapter_
.switchToInputMethod(inputMethodId
);
796 case CommandEnum
.SWITCH_KEYSET
:
797 var keyset
= opt_arg
;
799 this.recordStatsForClosing_(
800 'InputMethod.VirtualKeyboard.LayoutSwitch', 1, 25, 25);
801 this.switchToKeyset(keyset
);
804 case CommandEnum
.OPEN_EMOJI
:
805 this.contextTypeToLastKeysetMap_
[this.adapter_
.contextType
] =
807 this.switchToKeyset(Controller
.EMOJI_VIEW_CODE_
);
810 case CommandEnum
.OPEN_HANDWRITING
:
811 this.contextTypeToLastKeysetMap_
[this.adapter_
.contextType
] =
813 // TODO: remember handwriting keyset.
814 this.switchToKeyset(Controller
.HANDWRITING_VIEW_CODE_
);
817 case CommandEnum
.OPEN_SETTING
:
818 if (window
.inputview
) {
819 inputview
.openSettings();
827 * Handles the pointer action.
829 * @param {!i18n.input.chrome.inputview.elements.Element} view The view.
830 * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
833 Controller
.prototype.handlePointerAction_ = function(view
, e
) {
834 if (this.adapter_
.isGestureTypingEnabled() &&
835 e
.type
== EventType
.POINTER_DOWN
) {
836 this.container_
.gestureCanvasView
.startStroke(e
);
839 if (this.adapter_
.isGestureTypingEnabled() &&
840 e
.type
== EventType
.POINTER_UP
) {
841 this.container_
.gestureCanvasView
.endStroke(e
);
844 // Do not trigger other actives when gesturing.
845 if (this.adapter_
.isGestureTypingEnabled() &&
846 this.container_
.gestureCanvasView
.isGesturing
) {
850 // Listen for DOUBLE_CLICK as well to capture secondary taps on the spacebar.
851 if (e
.type
== EventType
.POINTER_UP
|| e
.type
== EventType
.DOUBLE_CLICK
) {
852 this.recordStatsForClosing_(
853 'InputMethod.VirtualKeyboard.TapCount', 1, 4095, 4096);
856 if (e
.type
== EventType
.SWIPE
) {
857 e
= /** @type {!i18n.input.chrome.inputview.events.SwipeEvent} */ (e
);
858 this.handleSwipeAction_(view
, e
);
861 case ElementType
.KEYBOARD_CONTAINER_VIEW
:
862 if (e
.type
== EventType
.POINTER_DOWN
) {
863 var tabbableKeysets
= [
864 Controller
.HANDWRITING_VIEW_CODE_
,
865 Controller
.EMOJI_VIEW_CODE_
];
866 if (goog
.array
.contains(tabbableKeysets
, this.currentKeyset_
)) {
868 this.switchToKeyset(this.container_
.currentKeysetView
.fromKeyset
);
872 case ElementType
.BACK_BUTTON
:
873 case ElementType
.BACK_TO_KEYBOARD
:
874 if (e
.type
== EventType
.POINTER_OUT
|| e
.type
== EventType
.POINTER_UP
) {
875 view
.setHighlighted(false);
876 } else if (e
.type
== EventType
.POINTER_DOWN
||
877 e
.type
== EventType
.POINTER_OVER
) {
878 view
.setHighlighted(true);
880 if (e
.type
== EventType
.POINTER_UP
) {
881 this.switchToKeyset(this.container_
.currentKeysetView
.fromKeyset
);
882 this.clearCandidates_();
883 this.soundController_
.onKeyUp(view
.type
);
886 case ElementType
.EXPAND_CANDIDATES
:
887 if (e
.type
== EventType
.POINTER_UP
) {
888 this.showCandidates_(this.candidatesInfo_
.source
,
889 this.candidatesInfo_
.candidates
,
890 Controller
.CandidatesOperation
.EXPAND
);
891 this.soundController_
.onKeyUp(view
.type
);
894 case ElementType
.SHRINK_CANDIDATES
:
895 if (e
.type
== EventType
.POINTER_UP
) {
896 this.showCandidates_(this.candidatesInfo_
.source
,
897 this.candidatesInfo_
.candidates
,
898 Controller
.CandidatesOperation
.SHRINK
);
899 this.soundController_
.onKeyUp(view
.type
);
902 case ElementType
.CANDIDATE
:
903 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
904 Candidate} */ (view
);
905 if (e
.type
== EventType
.POINTER_UP
) {
906 if (view
.candidateType
== CandidateType
.CANDIDATE
) {
907 this.adapter_
.selectCandidate(view
.candidate
);
908 } else if (view
.candidateType
== CandidateType
.NUMBER
) {
909 this.adapter_
.commitText(view
.candidate
[Name
.CANDIDATE
]);
911 this.container_
.cleanStroke();
912 this.soundController_
.onKeyUp(ElementType
.CANDIDATE
);
914 if (e
.type
== EventType
.POINTER_OUT
|| e
.type
== EventType
.POINTER_UP
) {
915 view
.setHighlighted(false);
916 } else if (e
.type
== EventType
.POINTER_DOWN
||
917 e
.type
== EventType
.POINTER_OVER
) {
918 view
.setHighlighted(true);
922 case ElementType
.ALTDATA_VIEW
:
923 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
924 AltDataView} */ (view
);
925 if (e
.type
== EventType
.POINTER_UP
&& e
.identifier
== view
.identifier
) {
926 var ch
= view
.getHighlightedCharacter();
928 this.adapter_
.sendKeyDownAndUpEvent(ch
, view
.triggeredBy
.id
,
929 view
.triggeredBy
.keyCode
,
930 {'sources': [ch
.toLowerCase()], 'possibilities': [1]});
933 this.clearUnstickyState_();
934 this.soundController_
.onKeyUp(view
.type
);
938 case ElementType
.MENU_ITEM
:
939 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
941 if (e
.type
== EventType
.POINTER_UP
) {
942 this.executeCommand_
.apply(this, view
.getCommand());
943 this.container_
.menuView
.hide();
944 this.soundController_
.onKeyUp(view
.type
);
947 view
.setHighlighted(e
.type
== EventType
.POINTER_DOWN
||
948 e
.type
== EventType
.POINTER_OVER
);
949 // TODO: Add chrome vox support.
952 case ElementType
.MENU_VIEW
:
953 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
956 if (e
.type
== EventType
.CLICK
&&
957 e
.target
== view
.getCoverElement()) {
962 case ElementType
.EMOJI_KEY
:
963 if (e
.type
== EventType
.CLICK
) {
964 if (!this.container_
.currentKeysetView
.isDragging
&& view
.text
!= '') {
965 this.adapter_
.commitText(view
.text
);
966 this.soundController_
.onKeyUp(view
.type
);
971 case ElementType
.HWT_PRIVACY_GOT_IT
:
972 // Broadcasts the handwriting privacy confirmed message to let canvas
974 this.adapter_
.dispatchEvent(new goog
.events
.Event(
975 Type
.HWT_PRIVACY_GOT_IT
));
978 case ElementType
.VOICE_PRIVACY_GOT_IT
:
979 // Broadcasts the voice privacy confirmed message to let voice
981 this.adapter_
.dispatchEvent(new goog
.events
.Event(
982 Type
.VOICE_PRIVACY_GOT_IT
));
985 case ElementType
.VOICE_VIEW
:
986 if (e
.type
== EventType
.POINTER_UP
) {
987 this.adapter_
.sendVoiceViewStateChange(false);
988 this.container_
.candidateView
.switchToIcon(
989 CandidateView
.IconType
.VOICE
, true);
990 this.container_
.voiceView
.stop();
993 case ElementType
.SWIPE_VIEW
:
994 this.stopBackspaceAutoRepeat_();
995 if (e
.type
== EventType
.POINTER_UP
||
996 e
.type
== EventType
.POINTER_OUT
) {
997 this.clearUnstickyState_();
1000 case ElementType
.CUT
:
1001 case ElementType
.COPY
:
1002 case ElementType
.PASTE
:
1003 case ElementType
.BOLD
:
1004 case ElementType
.ITALICS
:
1005 case ElementType
.UNDERLINE
:
1006 case ElementType
.REDO
:
1007 case ElementType
.UNDO
:
1008 case ElementType
.SELECT_ALL
:
1009 view
.setHighlighted(e
.type
== EventType
.POINTER_DOWN
||
1010 e
.type
== EventType
.POINTER_OVER
);
1011 if (e
.type
== EventType
.POINTER_UP
) {
1012 this.adapter_
.sendKeyDownAndUpEvent(
1013 '', this.elementTypeToKeyCode_
[view
.type
], undefined, undefined, {
1020 case ElementType
.SOFT_KEY_VIEW
:
1021 // Delegates the events on the soft key view to its soft key.
1022 view
= /** @type {!i18n.input.chrome.inputview.elements.layout.
1023 SoftKeyView} */ (view
);
1024 if (!view
.softKey
) {
1027 view
= view
.softKey
;
1030 if (view
.type
!= ElementType
.MODIFIER_KEY
&&
1031 !this.container_
.altDataView
.isVisible() &&
1032 !this.container_
.menuView
.isVisible() &&
1033 !this.container_
.swipeView
.isVisible()) {
1034 // The highlight of the modifier key is depending on the state instead
1035 // of the key down or up.
1036 if (e
.type
== EventType
.POINTER_OVER
|| e
.type
== EventType
.POINTER_DOWN
||
1037 e
.type
== EventType
.DOUBLE_CLICK
) {
1038 view
.setHighlighted(true);
1039 } else if (e
.type
== EventType
.POINTER_OUT
||
1040 e
.type
== EventType
.POINTER_UP
||
1041 e
.type
== EventType
.DOUBLE_CLICK_END
) {
1042 view
.setHighlighted(false);
1045 view
= /** @type {!i18n.input.chrome.inputview.elements.content.
1047 this.handlePointerEventForSoftKey_(view
, e
);
1048 this.updateContextModifierState_();
1053 * Handles softkey of the pointer action.
1055 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} softKey .
1056 * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
1059 Controller
.prototype.handlePointerEventForSoftKey_ = function(softKey
, e
) {
1061 switch (softKey
.type
) {
1062 case ElementType
.VOICE_BTN
:
1063 if (e
.type
== EventType
.POINTER_UP
) {
1064 this.container_
.candidateView
.switchToIcon(
1065 CandidateView
.IconType
.VOICE
, false);
1066 this.container_
.voiceView
.start();
1069 case ElementType
.CANDIDATES_PAGE_UP
:
1070 if (e
.type
== EventType
.POINTER_UP
) {
1071 this.container_
.expandedCandidateView
.pageUp();
1074 case ElementType
.CANDIDATES_PAGE_DOWN
:
1075 if (e
.type
== EventType
.POINTER_UP
) {
1076 this.container_
.expandedCandidateView
.pageDown();
1079 case ElementType
.CHARACTER_KEY
:
1080 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1081 CharacterKey} */ (softKey
);
1082 if (e
.type
== EventType
.LONG_PRESS
) {
1083 this.container_
.altDataView
.show(
1084 key
, goog
.i18n
.bidi
.isRtlLanguage(this.languageCode_
),
1086 } else if (e
.type
== EventType
.POINTER_UP
) {
1087 this.model_
.stateManager
.triggerChording();
1088 var ch
= key
.getActiveCharacter();
1090 this.adapter_
.sendKeyDownAndUpEvent(ch
, key
.id
, key
.keyCode
,
1091 this.getSpatialData_(key
, e
.x
, e
.y
));
1093 this.clearUnstickyState_();
1094 key
.flickerredCharacter
= '';
1098 case ElementType
.MODIFIER_KEY
:
1099 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1100 ModifierKey} */(softKey
);
1101 var isStateEnabled
= this.model_
.stateManager
.hasState(key
.toState
);
1102 var isChording
= this.model_
.stateManager
.isChording(key
.toState
);
1103 if (e
.type
== EventType
.POINTER_DOWN
) {
1104 this.changeState_(key
.toState
, !isStateEnabled
, true, false);
1105 this.model_
.stateManager
.setKeyDown(key
.toState
, true);
1106 } else if (e
.type
== EventType
.POINTER_UP
|| e
.type
== EventType
.
1109 this.changeState_(key
.toState
, false, false);
1110 } else if (key
.toState
== StateType
.CAPSLOCK
) {
1111 this.changeState_(key
.toState
, isStateEnabled
, true, true);
1112 } else if (this.model_
.stateManager
.isKeyDown(key
.toState
)) {
1113 this.changeState_(key
.toState
, isStateEnabled
, false);
1115 this.model_
.stateManager
.setKeyDown(key
.toState
, false);
1116 } else if (e
.type
== EventType
.DOUBLE_CLICK
) {
1117 this.changeState_(key
.toState
, isStateEnabled
, true, true);
1118 } else if (e
.type
== EventType
.LONG_PRESS
) {
1120 this.changeState_(key
.toState
, true, true, true);
1121 this.model_
.stateManager
.setKeyDown(key
.toState
, false);
1126 case ElementType
.BACKSPACE_KEY
:
1127 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1128 FunctionalKey} */(softKey
);
1129 if (e
.type
== EventType
.POINTER_DOWN
) {
1130 this.backspaceTick_();
1131 } else if (e
.type
== EventType
.POINTER_UP
|| e
.type
== EventType
.
1133 if (!this.container_
.swipeView
.isVisible()) {
1134 this.stopBackspaceAutoRepeat_();
1139 case ElementType
.TAB_KEY
:
1140 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1141 FunctionalKey} */ (softKey
);
1142 if (e
.type
== EventType
.POINTER_DOWN
) {
1143 this.adapter_
.sendKeyDownEvent('\u0009', KeyCodes
.TAB
);
1144 } else if (e
.type
== EventType
.POINTER_UP
) {
1145 this.adapter_
.sendKeyUpEvent('\u0009', KeyCodes
.TAB
);
1149 case ElementType
.ENTER_KEY
:
1150 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1151 FunctionalKey} */ (softKey
);
1152 if (e
.type
== EventType
.POINTER_UP
) {
1153 this.adapter_
.sendKeyDownAndUpEvent('\u000D', KeyCodes
.ENTER
);
1157 case ElementType
.ARROW_UP
:
1158 if (e
.type
== EventType
.POINTER_DOWN
) {
1159 this.adapter_
.sendKeyDownEvent('', KeyCodes
.ARROW_UP
);
1160 } else if (e
.type
== EventType
.POINTER_UP
) {
1161 this.adapter_
.sendKeyUpEvent('', KeyCodes
.ARROW_UP
);
1165 case ElementType
.ARROW_DOWN
:
1166 if (e
.type
== EventType
.POINTER_DOWN
) {
1167 this.adapter_
.sendKeyDownEvent('', KeyCodes
.ARROW_DOWN
);
1168 } else if (e
.type
== EventType
.POINTER_UP
) {
1169 this.adapter_
.sendKeyUpEvent('', KeyCodes
.ARROW_DOWN
);
1173 case ElementType
.ARROW_LEFT
:
1174 if (e
.type
== EventType
.POINTER_DOWN
) {
1175 this.adapter_
.sendKeyDownEvent('', KeyCodes
.ARROW_LEFT
);
1176 } else if (e
.type
== EventType
.POINTER_UP
) {
1177 this.adapter_
.sendKeyUpEvent('', KeyCodes
.ARROW_LEFT
);
1181 case ElementType
.ARROW_RIGHT
:
1182 if (e
.type
== EventType
.POINTER_DOWN
) {
1183 this.adapter_
.sendKeyDownEvent('', KeyCodes
.ARROW_RIGHT
);
1184 } else if (e
.type
== EventType
.POINTER_UP
) {
1185 this.adapter_
.sendKeyUpEvent('', KeyCodes
.ARROW_RIGHT
);
1188 case ElementType
.EN_SWITCHER
:
1189 if (e
.type
== EventType
.POINTER_UP
) {
1190 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1191 EnSwitcherKey} */ (softKey
);
1192 this.adapter_
.toggleLanguageState(this.model_
.stateManager
.isEnMode
);
1193 this.model_
.stateManager
.isEnMode
= !this.model_
.stateManager
.isEnMode
;
1197 case ElementType
.SPACE_KEY
:
1198 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1199 SpaceKey} */ (softKey
);
1200 var doubleSpacePeriod
= this.model_
.settings
.doubleSpacePeriod
&&
1201 this.currentKeyset_
!= Controller
.HANDWRITING_VIEW_CODE_
&&
1202 this.currentKeyset_
!= Controller
.EMOJI_VIEW_CODE_
;
1203 if (e
.type
== EventType
.POINTER_UP
|| (!doubleSpacePeriod
&& e
.type
==
1204 EventType
.DOUBLE_CLICK_END
)) {
1205 this.adapter_
.sendKeyDownAndUpEvent(key
.getCharacter(),
1207 this.clearUnstickyState_();
1208 } else if (e
.type
== EventType
.DOUBLE_CLICK
&& doubleSpacePeriod
) {
1209 this.adapter_
.doubleClickOnSpaceKey();
1213 case ElementType
.SWITCHER_KEY
:
1214 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1215 SwitcherKey} */ (softKey
);
1216 if (e
.type
== EventType
.POINTER_UP
) {
1217 this.recordStatsForClosing_(
1218 'InputMethod.VirtualKeyboard.LayoutSwitch', 1, 25, 25);
1219 if (this.isSubKeyset_(key
.toKeyset
, this.currentKeyset_
)) {
1220 this.model_
.stateManager
.reset();
1221 this.container_
.update();
1222 this.updateContextModifierState_();
1223 this.container_
.menuView
.hide();
1227 // Switch to the specific keyboard.
1228 this.switchToKeyset(key
.toKeyset
);
1230 this.model_
.settings
.savePreference(
1231 util
.getConfigName(key
.toKeyset
),
1237 case ElementType
.COMPACT_KEY
:
1238 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1239 CompactKey} */(softKey
);
1240 if (e
.type
== EventType
.LONG_PRESS
) {
1241 this.container_
.altDataView
.show(
1242 key
, goog
.i18n
.bidi
.isRtlLanguage(this.languageCode_
),
1244 } else if (e
.type
== EventType
.POINTER_UP
) {
1245 this.model_
.stateManager
.triggerChording();
1246 var ch
= key
.getActiveCharacter();
1247 if (ch
.length
== 1) {
1248 this.adapter_
.sendKeyDownAndUpEvent(key
.getActiveCharacter(), '', 0,
1249 this.getSpatialData_(key
, e
.x
, e
.y
));
1250 } else if (ch
.length
> 1) {
1251 // Some compact keys contains more than 1 characters, such as '.com',
1252 // 'http://', etc. Those keys should trigger a direct commit text
1253 // instead of key events.
1254 this.adapter_
.commitText(ch
);
1256 this.clearUnstickyState_();
1257 key
.flickerredCharacter
= '';
1261 case ElementType
.HIDE_KEYBOARD_KEY
:
1262 var defaultKeyset
= this.getActiveKeyset_();
1263 if (e
.type
== EventType
.POINTER_UP
) {
1264 this.adapter_
.hideKeyboard();
1265 if (this.currentKeyset_
!= defaultKeyset
) {
1266 this.switchToKeyset(defaultKeyset
);
1271 case ElementType
.MENU_KEY
:
1272 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1273 MenuKey} */ (softKey
);
1274 if (e
.type
== EventType
.POINTER_DOWN
) {
1275 var isCompact
= this.currentKeyset_
.indexOf('compact') != -1;
1276 // Gets the default full keyboard instead of default keyset because
1277 // the default keyset can be a compact keyset which would cause problem
1278 // in MenuView.show().
1279 var defaultFullKeyset
= this.initialKeyset_
.split(/\./)[0];
1280 var enableCompact
= !this.adapter_
.isA11yMode
&& goog
.array
.contains(
1281 util
.KEYSETS_HAVE_COMPACT
, defaultFullKeyset
);
1282 if (defaultFullKeyset
== 'zhuyin' &&
1283 !this.adapter_
.features
.isEnabled(FeatureName
.EXPERIMENTAL
) ||
1284 this.languageCode_
== 'ko') {
1285 // Hides 'switch to compact' for zhuyin when not in experimental env.
1286 enableCompact
= false;
1288 var hasHwt
= !this.adapter_
.isPasswordBox() &&
1289 !Controller
.DISABLE_HWT
&& goog
.object
.contains(
1290 InputToolCode
, this.getHwtInputToolCode_());
1291 var hasEmoji
= !this.adapter_
.isPasswordBox();
1292 var enableSettings
= this.shouldEnableSettings() &&
1293 !!window
.inputview
&& !!inputview
.openSettings
;
1294 this.adapter_
.getInputMethods(function(inputMethods
) {
1295 this.container_
.menuView
.show(key
, defaultFullKeyset
, isCompact
,
1296 enableCompact
, this.currentInputMethod_
, inputMethods
, hasHwt
,
1297 enableSettings
, hasEmoji
, this.adapter_
.isA11yMode
);
1302 case ElementType
.GLOBE_KEY
:
1303 if (e
.type
== EventType
.POINTER_UP
) {
1304 this.adapter_
.clearModifierStates();
1305 this.adapter_
.setModifierState(
1306 i18n
.input
.chrome
.inputview
.StateType
.ALT
, true);
1307 this.adapter_
.sendKeyDownAndUpEvent(
1308 KeyCodes
.SHIFT
, KeyCodes
.SHIFT_LEFT
, goog
.events
.KeyCodes
.SHIFT
);
1309 this.adapter_
.setModifierState(
1310 i18n
.input
.chrome
.inputview
.StateType
.ALT
, false);
1313 case ElementType
.IME_SWITCH
:
1314 key
= /** @type {!i18n.input.chrome.inputview.elements.content.
1315 FunctionalKey} */ (softKey
);
1316 this.adapter_
.sendKeyDownAndUpEvent('', key
.id
);
1319 // Play key sound on pointer up or double click.
1320 if (e
.type
== EventType
.POINTER_UP
|| e
.type
== EventType
.DOUBLE_CLICK
)
1321 this.soundController_
.onKeyUp(softKey
.type
);
1326 * Clears unsticky state.
1330 Controller
.prototype.clearUnstickyState_ = function() {
1331 if (this.model_
.stateManager
.hasUnStickyState()) {
1332 for (var key
in StateType
) {
1333 var value
= StateType
[key
];
1334 if (this.model_
.stateManager
.hasState(value
) &&
1335 !this.model_
.stateManager
.isSticky(value
)) {
1336 this.changeState_(value
, false, false);
1340 this.container_
.update();
1345 * Stops the auto-repeat for backspace.
1349 Controller
.prototype.stopBackspaceAutoRepeat_ = function() {
1350 if (this.backspaceAutoRepeat_
) {
1351 goog
.dispose(this.backspaceAutoRepeat_
);
1352 this.backspaceAutoRepeat_
= null;
1353 this.adapter_
.sendKeyUpEvent('\u0008', KeyCodes
.BACKSPACE
);
1354 this.backspaceRepeated_
= 0;
1360 * The tick for the backspace key.
1364 Controller
.prototype.backspaceTick_ = function() {
1365 if (this.backspaceRepeated_
>= Controller
.BACKSPACE_REPEAT_LIMIT_
) {
1366 this.stopBackspaceAutoRepeat_();
1369 this.backspaceRepeated_
++;
1370 this.backspaceDown_();
1371 this.soundController_
.onKeyRepeat(ElementType
.BACKSPACE_KEY
);
1373 if (this.backspaceAutoRepeat_
) {
1374 this.backspaceAutoRepeat_
.start(75);
1376 this.backspaceAutoRepeat_
= new goog
.async
.Delay(
1377 goog
.bind(this.backspaceTick_
, this), 300);
1378 this.backspaceAutoRepeat_
.start();
1384 * Callback for VISIBILITY_CHANGE.
1388 Controller
.prototype.onVisibilityChange_ = function() {
1389 if (!this.adapter_
.isVisible
) {
1390 for (var name
in this.statsForClosing_
) {
1391 var stat
= this.statsForClosing_
[name
];
1392 this.statistics_
.recordValue(name
, stat
[0], stat
[1], stat
[2]);
1394 this.statistics_
.recordValue('InputMethod.VirtualKeyboard.Duration',
1395 Math
.floor((new Date() - this.showTimeStamp_
) / 1000), 4096, 50);
1396 this.statsForClosing_
= {};
1397 this.showTimeStamp_
= new Date();
1404 * Resets the whole keyboard include clearing candidates,
1405 * reset modifier state, etc.
1407 Controller
.prototype.resetAll = function() {
1408 this.clearCandidates_();
1409 this.container_
.cleanStroke();
1410 this.model_
.stateManager
.reset();
1411 this.container_
.update();
1412 this.updateContextModifierState_();
1414 this.container_
.expandedCandidateView
.close();
1415 this.container_
.menuView
.hide();
1416 this.container_
.swipeView
.reset();
1417 this.container_
.altDataView
.hide();
1422 * Returns whether the toolbar should be shown.
1427 Controller
.prototype.shouldShowToolBar_ = function() {
1428 return this.adapter_
.features
.isEnabled(FeatureName
.OPTIMIZED_LAYOUTS
) &&
1429 this.adapter_
.isGoogleDocument() &&
1430 this.adapter_
.contextType
== ContextType
.DEFAULT
;
1435 * Callback when the context is changed.
1439 Controller
.prototype.onContextFocus_ = function() {
1441 this.model_
.stateManager
.contextType
= this.adapter_
.contextType
;
1442 this.switchToKeyset(this.getActiveKeyset_());
1447 * Callback when surrounding text is changed.
1449 * @param {!i18n.input.chrome.inputview.events.SurroundingTextChangedEvent} e .
1452 Controller
.prototype.onSurroundingTextChanged_ = function(e
) {
1453 if (!this.model_
.settings
.autoCapital
|| !e
.text
) {
1457 var isShiftEnabled
= this.model_
.stateManager
.hasState(StateType
.SHIFT
);
1458 var needAutoCap
= this.model_
.settings
.autoCapital
&&
1459 util
.needAutoCap(e
.text
);
1460 if (needAutoCap
&& !isShiftEnabled
) {
1461 this.changeState_(StateType
.SHIFT
, true, false);
1462 this.shiftForAutoCapital_
= true;
1463 } else if (!needAutoCap
&& this.shiftForAutoCapital_
) {
1464 this.changeState_(StateType
.SHIFT
, false, false);
1470 * Callback for Context blurs.
1474 Controller
.prototype.onContextBlur_ = function() {
1475 this.container_
.cleanStroke();
1476 this.container_
.menuView
.hide();
1481 * Backspace key is down.
1485 Controller
.prototype.backspaceDown_ = function() {
1486 if (this.container_
.hasStrokesOnCanvas()) {
1487 this.clearCandidates_();
1488 this.container_
.cleanStroke();
1490 this.adapter_
.sendKeyDownEvent('\u0008', KeyCodes
.BACKSPACE
);
1492 this.recordStatsForClosing_(
1493 'InputMethod.VirtualKeyboard.BackspaceCount', 1, 4095, 4096);
1494 this.statistics_
.recordEnum('InputMethod.VirtualKeyboard.BackspaceOnLayout',
1495 this.statistics_
.getLayoutType(this.currentKeyset_
,
1496 this.adapter_
.isA11yMode
),
1497 i18n
.input
.chrome
.Statistics
.LayoutTypes
.MAX
);
1502 * Callback for state change.
1504 * @param {StateType} stateType The state type.
1505 * @param {boolean} enable True to enable the state.
1506 * @param {boolean} isSticky True to make the state sticky.
1507 * @param {boolean=} opt_isFinalSticky .
1510 Controller
.prototype.changeState_ = function(stateType
, enable
, isSticky
,
1511 opt_isFinalSticky
) {
1512 if (stateType
== StateType
.ALTGR
) {
1513 var code
= KeyCodes
.ALT_RIGHT
;
1515 this.adapter_
.sendKeyDownEvent('', code
);
1517 this.adapter_
.sendKeyUpEvent('', code
);
1520 if (stateType
== StateType
.SHIFT
) {
1521 this.shiftForAutoCapital_
= false;
1523 var isEnabledBefore
= this.model_
.stateManager
.hasState(stateType
);
1524 var isStickyBefore
= this.model_
.stateManager
.isSticky(stateType
);
1525 this.model_
.stateManager
.setState(stateType
, enable
);
1526 this.model_
.stateManager
.setSticky(stateType
, isSticky
);
1527 var isFinalSticky
= goog
.isDef(opt_isFinalSticky
) ? opt_isFinalSticky
:
1529 var isFinalStikcyBefore
= this.model_
.stateManager
.isFinalSticky(stateType
);
1530 this.model_
.stateManager
.setFinalSticky(stateType
, isFinalSticky
);
1531 if (isEnabledBefore
!= enable
|| isStickyBefore
!= isSticky
||
1532 isFinalStikcyBefore
!= isFinalSticky
) {
1533 this.container_
.update();
1539 * Updates the modifier state for context.
1543 Controller
.prototype.updateContextModifierState_ = function() {
1544 var stateManager
= this.model_
.stateManager
;
1545 this.adapter_
.setModifierState(StateType
.ALT
,
1546 stateManager
.hasState(StateType
.ALT
));
1547 this.adapter_
.setModifierState(StateType
.CTRL
,
1548 stateManager
.hasState(StateType
.CTRL
));
1549 this.adapter_
.setModifierState(StateType
.CAPSLOCK
,
1550 stateManager
.hasState(StateType
.CAPSLOCK
));
1551 if (!this.shiftForAutoCapital_
) {
1552 // If shift key is automatically on because of feature - autoCapital,
1553 // Don't set modifier state to adapter.
1554 this.adapter_
.setModifierState(StateType
.SHIFT
,
1555 stateManager
.hasState(StateType
.SHIFT
));
1561 * Callback for AUTO-COMPLETE event.
1563 * @param {!i18n.input.chrome.DataSource.CandidatesBackEvent} e .
1566 Controller
.prototype.onCandidatesBack_ = function(e
) {
1567 this.candidatesInfo_
= new i18n
.input
.chrome
.inputview
.CandidatesInfo(
1568 e
.source
, e
.candidates
);
1569 this.showCandidates_(e
.source
, e
.candidates
, Controller
.CandidatesOperation
.
1575 * Shows the candidates to the candidate view.
1577 * @param {string} source The source text.
1578 * @param {!Array.<!Object>} candidates The candidate text list.
1579 * @param {Controller.CandidatesOperation} operation .
1582 Controller
.prototype.showCandidates_ = function(source
, candidates
,
1584 var state
= !!source
? ExpandedCandidateView
.State
.COMPLETION_CORRECTION
:
1585 ExpandedCandidateView
.State
.PREDICTION
;
1586 var expandView
= this.container_
.expandedCandidateView
;
1588 if (operation
== Controller
.CandidatesOperation
.NONE
) {
1589 expand
= expandView
.state
== state
;
1591 expand
= operation
== Controller
.CandidatesOperation
.EXPAND
;
1594 if (candidates
.length
== 0) {
1595 this.clearCandidates_();
1596 expandView
.state
= ExpandedCandidateView
.State
.NONE
;
1600 // The compact pinyin needs full candidates instead of three candidates.
1601 var isThreeCandidates
= this.currentKeyset_
.indexOf('compact') != -1 &&
1602 this.currentKeyset_
.indexOf('pinyin-zh-CN') == -1;
1603 if (isThreeCandidates
) {
1604 if (candidates
.length
> 1) {
1605 // Swap the first candidate and the second candidate.
1606 var tmp
= candidates
[0];
1607 candidates
[0] = candidates
[1];
1608 candidates
[1] = tmp
;
1611 var isHwt
= Controller
.HANDWRITING_VIEW_CODE_
== this.currentKeyset_
;
1612 this.container_
.candidateView
.showCandidates(candidates
, isThreeCandidates
,
1613 this.model_
.settings
.candidatesNavigation
&& !isHwt
);
1615 // Only sum of candidate is greater than top line count. Need to update
1617 if (expand
&& this.container_
.candidateView
.candidateCount
<
1618 candidates
.length
) {
1619 expandView
.state
= state
;
1620 this.container_
.currentKeysetView
.setVisible(false);
1621 expandView
.showCandidates(candidates
,
1622 this.container_
.candidateView
.candidateCount
);
1623 this.container_
.candidateView
.switchToIcon(CandidateView
.IconType
.
1624 SHRINK_CANDIDATES
, true);
1626 expandView
.state
= ExpandedCandidateView
.State
.NONE
;
1627 expandView
.setVisible(false);
1628 this.container_
.candidateView
.switchToIcon(CandidateView
.IconType
.
1629 SHRINK_CANDIDATES
, false);
1630 this.container_
.currentKeysetView
.setVisible(true);
1636 * Clears candidates.
1640 Controller
.prototype.clearCandidates_ = function() {
1641 this.candidatesInfo_
= i18n
.input
.chrome
.inputview
.CandidatesInfo
.getEmpty();
1642 this.container_
.candidateView
.clearCandidates();
1643 this.container_
.expandedCandidateView
.close();
1644 this.container_
.expandedCandidateView
.state
= ExpandedCandidateView
.State
.
1646 if (this.container_
.currentKeysetView
) {
1647 this.container_
.currentKeysetView
.setVisible(true);
1650 if (this.currentKeyset_
== Controller
.HANDWRITING_VIEW_CODE_
||
1651 this.currentKeyset_
== Controller
.EMOJI_VIEW_CODE_
) {
1652 if (!this.adapter_
.isQPInputView
) {
1653 this.container_
.candidateView
.switchToIcon(
1654 CandidateView
.IconType
.BACK
, true);
1656 this.container_
.candidateView
.switchToIcon(
1657 CandidateView
.IconType
.VOICE
, false);
1658 this.container_
.candidateView
.switchToIcon(
1659 CandidateView
.IconType
.EXPAND_CANDIDATES
, false);
1662 this.container_
.candidateView
.switchToIcon(CandidateView
.IconType
.VOICE
,
1663 this.adapter_
.isVoiceInputEnabled
);
1669 * Callback when the layout is loaded.
1671 * @param {!i18n.input.chrome.inputview.events.LayoutLoadedEvent} e The event.
1674 Controller
.prototype.onLayoutLoaded_ = function(e
) {
1675 var layoutID
= e
.data
['layoutID'];
1676 this.layoutDataMap_
[layoutID
] = e
.data
;
1677 this.perfTracker_
.tick(PerfTracker
.TickName
.LAYOUT_LOADED
);
1678 this.maybeCreateViews_();
1683 * Creates a keyset view.
1685 * @param {string} keyset The non-raw keyset.
1688 Controller
.prototype.createView_ = function(keyset
) {
1689 if (this.isDisposed()) {
1692 var keysetData
= this.keysetDataMap_
[keyset
];
1693 var layoutId
= keysetData
[SpecNodeName
.LAYOUT
];
1694 var layoutData
= this.layoutDataMap_
[layoutId
];
1695 if (this.container_
.keysetViewMap
[keyset
] || !layoutData
) {
1698 var conditions
= {};
1699 conditions
[ConditionName
.SHOW_ALTGR
] =
1700 keysetData
[SpecNodeName
.HAS_ALTGR_KEY
];
1702 conditions
[ConditionName
.SHOW_MENU
] =
1703 keysetData
[SpecNodeName
.SHOW_MENU_KEY
];
1704 // In symbol and more keysets, we want to show a symbol key in the globe
1705 // SoftKeyView. So this view should alway visible in the two keysets.
1706 // Currently, SHOW_MENU_KEY is false for the two keysets, so we use
1707 // !keysetData[SpecNodeName.SHOW_MENU_KEY] here.
1708 conditions
[ConditionName
.SHOW_GLOBE_OR_SYMBOL
] =
1709 !keysetData
[SpecNodeName
.SHOW_MENU_KEY
] ||
1710 this.adapter_
.showGlobeKey
;
1711 conditions
[ConditionName
.SHOW_EN_SWITCHER_KEY
] = false;
1713 this.container_
.addKeysetView(keysetData
, layoutData
, keyset
,
1714 this.languageCode_
, this.model_
, this.title_
, conditions
);
1715 this.perfTracker_
.tick(PerfTracker
.TickName
.KEYBOARD_CREATED
);
1720 * Creates the whole view.
1724 Controller
.prototype.maybeCreateViews_ = function() {
1725 if (!this.isSettingReady
) {
1729 // Emoji is temp keyset which is delay loaded. So active keyset can be 'us'
1730 // while current keyset is 'emoji'. To make sure delay load can work
1731 // correctly, here need to create/switch to 'emoji' instead of 'us'.
1732 var activeKeyset
= (this.currentKeyset_
== Controller
.EMOJI_VIEW_CODE_
) ?
1733 this.currentKeyset_
: this.getActiveKeyset_();
1734 var remappedActiveKeyset
= this.getRemappedKeyset_(activeKeyset
);
1735 var created
= false;
1736 if (this.keysetDataMap_
[remappedActiveKeyset
]) {
1737 this.createView_(remappedActiveKeyset
);
1738 this.switchToKeyset(activeKeyset
);
1741 // Async creating the non-active keysets to reduce the latency of showing the
1743 var keyLen
= Object
.keys(this.keysetDataMap_
).length
;
1744 if (created
&& keyLen
> 1 || !created
&& keyLen
> 0) {
1745 goog
.Timer
.callOnce((function() {
1746 for (var keyset
in this.keysetDataMap_
) {
1747 this.createView_(keyset
);
1755 * Switch to a specific keyboard.
1757 * @param {string} keyset The keyset name.
1759 Controller
.prototype.switchToKeyset = function(keyset
) {
1760 if (!this.isSettingReady
|| this.adapter_
.isSwitching()) {
1764 var contextType
= this.adapter_
.contextType
;
1765 var ret
= this.container_
.switchToKeyset(this.getRemappedKeyset_(keyset
),
1766 this.title_
, this.adapter_
.isPasswordBox(), this.adapter_
.isA11yMode
,
1767 keyset
, this.contextTypeToLastKeysetMap_
[contextType
] ||
1768 this.getActiveKeyset_(), this.languageCode_
);
1771 if (!this.isSubKeyset_(this.currentKeyset_
, keyset
) &&
1772 keyset
!= Controller
.EMOJI_VIEW_CODE_
) {
1773 // If it is the sub keyset switching, or emoji, don't record it.
1774 // Update the keyset of current context type.
1775 this.contextTypeToKeysetMap_
[this.currentInputMethod_
][contextType
] =
1778 this.updateLanguageState_(this.currentKeyset_
, keyset
);
1779 this.currentKeyset_
= keyset
;
1780 this.resize(Controller
.DEV
);
1781 this.statistics_
.recordLayout(keyset
, this.adapter_
.isA11yMode
);
1782 this.perfTracker_
.tick(PerfTracker
.TickName
.KEYBOARD_SHOWN
);
1783 this.perfTracker_
.stop();
1785 // Sets the current keyset for delay switching.
1786 this.currentKeyset_
= keyset
;
1787 if (keyset
!= Controller
.EMOJI_VIEW_CODE_
) { // Emoji is temp keyset.
1788 this.contextTypeToKeysetMap_
[this.currentInputMethod_
][contextType
] =
1791 if (this.adapter_
.isQPInputView
&&
1792 goog
.array
.contains(Controller
.MATERIAL_KEYSETS_
, keyset
)) {
1793 this.loadResource_('m-' + keyset
);
1795 this.loadResource_(keyset
);
1802 * Callback when the configuration is loaded.
1804 * @param {!i18n.input.chrome.inputview.events.ConfigLoadedEvent} e The event.
1807 Controller
.prototype.onConfigLoaded_ = function(e
) {
1808 if (this.isDisposed()) {
1812 var keyboardCode
= data
[i18n
.input
.chrome
.inputview
.SpecNodeName
.ID
];
1813 this.keysetDataMap_
[keyboardCode
] = data
;
1814 this.perfTracker_
.tick(PerfTracker
.TickName
.KEYSET_LOADED
);
1815 var context
= data
[i18n
.input
.chrome
.inputview
.SpecNodeName
.ON_CONTEXT
];
1816 if (context
&& !this.adapter_
.isA11yMode
) {
1817 var keySetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
];
1819 keySetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
] = {};
1821 keySetMap
[context
] = keyboardCode
;
1824 var layoutId
= data
[i18n
.input
.chrome
.inputview
.SpecNodeName
.LAYOUT
];
1825 if (this.adapter_
.isQPInputView
) {
1826 layoutId
= 'm-' + layoutId
;
1827 data
[i18n
.input
.chrome
.inputview
.SpecNodeName
.LAYOUT
] = layoutId
;
1829 var layoutData
= this.layoutDataMap_
[layoutId
];
1831 this.maybeCreateViews_();
1833 this.model_
.loadLayout(layoutId
);
1839 * Resizes the whole UI.
1841 * @param {boolean=} opt_ignoreWindowResize .
1843 Controller
.prototype.resize = function(opt_ignoreWindowResize
) {
1846 var candidateViewHeight
;
1847 var isLandScape
= screen
.width
> screen
.height
;
1849 goog
.dom
.classlist
.addRemove(this.container_
.getElement(),
1850 Css
.PORTRAIT
, Css
.LANDSCAPE
);
1852 goog
.dom
.classlist
.addRemove(this.container_
.getElement(),
1853 Css
.LANDSCAPE
, Css
.PORTRAIT
);
1855 var isWideScreen
= (Math
.min(screen
.width
, screen
.height
) / Math
.max(
1856 screen
.width
, screen
.height
)) < 0.6;
1857 this.model_
.stateManager
.covariance
.update(isWideScreen
, isLandScape
,
1858 this.adapter_
.isA11yMode
);
1859 if (this.adapter_
.isA11yMode
) {
1860 height
= SizeSpec
.A11Y_HEIGHT
;
1861 widthPercent
= screen
.width
> screen
.height
? SizeSpec
.A11Y_WIDTH_PERCENT
.
1862 LANDSCAPE
: SizeSpec
.A11Y_WIDTH_PERCENT
.PORTRAIT
;
1863 candidateViewHeight
= SizeSpec
.A11Y_CANDIDATE_VIEW_HEIGHT
;
1865 var keyset
= this.keysetDataMap_
[this.currentKeyset_
];
1866 var layout
= keyset
&& keyset
[SpecNodeName
.LAYOUT
];
1867 var data
= layout
&& this.layoutDataMap_
[layout
];
1868 var spec
= data
&& data
[SpecNodeName
.WIDTH_PERCENT
] ||
1869 SizeSpec
.NON_A11Y_WIDTH_PERCENT
;
1870 height
= SizeSpec
.NON_A11Y_HEIGHT
;
1873 widthPercent
= spec
['LANDSCAPE_WIDE_SCREEN'];
1875 widthPercent
= spec
['LANDSCAPE'];
1878 widthPercent
= spec
['PORTRAIT'];
1880 candidateViewHeight
= SizeSpec
.NON_A11Y_CANDIDATE_VIEW_HEIGHT
;
1883 if (window
.innerHeight
!= height
&& !opt_ignoreWindowResize
) {
1884 if (this.lastResizeHeight_
!= height
) {
1885 this.lastResizeHeight_
= height
;
1886 window
.resizeTo(screen
.width
, height
);
1891 this.container_
.setContainerSize(screen
.width
, height
, widthPercent
,
1892 candidateViewHeight
);
1893 this.container_
.candidateView
.setToolbarVisible(this.shouldShowToolBar_());
1894 if (this.container_
.currentKeysetView
) {
1895 this.isKeyboardReady
= true;
1901 * Loads the resources, for currentKeyset, passwdKeyset, handwriting,
1906 Controller
.prototype.loadAllResources_ = function() {
1907 var keysetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
];
1908 goog
.array
.forEach([keysetMap
[ContextType
.DEFAULT
],
1909 keysetMap
[ContextType
.PASSWORD
]], function(keyset
) {
1910 this.loadResource_(keyset
);
1916 * Gets the remapped keyset.
1918 * @param {string} keyset .
1919 * @return {string} The remapped keyset.
1922 Controller
.prototype.getRemappedKeyset_ = function(keyset
) {
1923 if (goog
.array
.contains(util
.KEYSETS_USE_US
, keyset
)) {
1926 var match
= keyset
.match(/^(.*)-rtl$/);
1927 if (match
&& goog
.array
.contains(util
.KEYSETS_USE_US
, match
[1])) {
1935 * Loads a single resource.
1937 * @param {string} keyset .
1941 Controller
.prototype.loadResource_ = function(keyset
) {
1942 var remapped
= this.getRemappedKeyset_(keyset
);
1943 if (!this.keysetDataMap_
[remapped
]) {
1944 if (/^m17n:/.test(remapped
)) {
1945 this.m17nModel_
.loadConfig(remapped
);
1947 this.model_
.loadConfig(remapped
);
1952 var layoutId
= this.keysetDataMap_
[remapped
][SpecNodeName
.LAYOUT
];
1953 if (!this.layoutDataMap_
[layoutId
]) {
1954 this.model_
.loadLayout(layoutId
);
1961 * Sets the keyboard.
1963 * @param {string} keyset The keyboard keyset.
1964 * @param {string} languageCode The language code for this keyboard.
1965 * @param {string} passwordLayout The layout for password box.
1966 * @param {string} title The title for this keyboard.
1968 Controller
.prototype.initialize = function(keyset
, languageCode
, passwordLayout
,
1970 this.perfTracker_
.restart();
1971 this.adapter_
.getCurrentInputMethod(function(currentInputMethod
) {
1972 // TODO: remove this hack as soon as the manifest is fixed in chromium.
1973 if (languageCode
== 'ko') {
1974 if (currentInputMethod
.indexOf('hangul_2set') > 0) {
1975 keyset
= 'm17n:ko_2set';
1978 this.languageCode_
= languageCode
;
1979 this.currentInputMethod_
= currentInputMethod
;
1980 var keySetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
];
1982 keySetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
] = {};
1984 keySetMap
[ContextType
.PASSWORD
] = passwordLayout
;
1985 keySetMap
[ContextType
.DEFAULT
] = keyset
;
1987 this.initialKeyset_
= keyset
;
1988 this.title_
= title
;
1989 this.isSettingReady
= false;
1990 this.model_
.settings
= new i18n
.input
.chrome
.inputview
.Settings();
1991 this.model_
.stateManager
.isEnMode
= false;
1992 this.adapter_
.initialize(languageCode
? languageCode
.split('-')[0] : '');
1993 this.loadAllResources_();
1994 this.switchToKeyset(this.getActiveKeyset_());
1996 // Set language attribute and font of body.
1997 document
.body
.setAttribute('lang', this.languageCode_
);
1998 goog
.dom
.classlist
.add(document
.body
, Css
.FONT
);
2004 Controller
.prototype.disposeInternal = function() {
2005 goog
.dispose(this.container_
);
2006 goog
.dispose(this.adapter_
);
2007 goog
.dispose(this.handler_
);
2008 goog
.dispose(this.soundController_
);
2010 goog
.base(this, 'disposeInternal');
2015 * Gets the handwriting Input Tool code of current language code.
2017 * @return {string} The handwriting Input Tool code.
2020 Controller
.prototype.getHwtInputToolCode_ = function() {
2021 return this.languageCode_
.split(/_|-/)[0] +
2022 Controller
.HANDWRITING_CODE_SUFFIX_
;
2027 * True to enable settings link.
2029 * @return {boolean} .
2031 Controller
.prototype.shouldEnableSettings = function() {
2032 return !this.adapter_
.screen
|| this.adapter_
.screen
== 'normal';
2037 * Gets the active keyset, if there is a keyset to switch, return it.
2038 * otherwise if it's a password box, return the password keyset,
2039 * otherwise return the current keyset.
2041 * @return {string} .
2044 Controller
.prototype.getActiveKeyset_ = function() {
2045 var keySetMap
= this.contextTypeToKeysetMap_
[this.currentInputMethod_
];
2046 return keySetMap
[this.adapter_
.contextType
] || this.initialKeyset_
;
2051 * True if keysetB is the sub keyset of keysetA.
2053 * @param {string} keysetA .
2054 * @param {string} keysetB .
2055 * @return {boolean} .
2058 Controller
.prototype.isSubKeyset_ = function(keysetA
, keysetB
) {
2059 var segmentsA
= keysetA
.split('.');
2060 var segmentsB
= keysetB
.split('.');
2061 return segmentsA
.length
>= 2 && segmentsB
.length
>= 2 &&
2062 segmentsA
[0] == segmentsB
[0] && segmentsA
[1] == segmentsB
[1];
2067 * Updates the compact pinyin to set the inputcode for english and pinyin.
2069 * @param {string} fromRawKeyset .
2070 * @param {string} toRawKeyset .
2073 Controller
.prototype.updateLanguageState_
=
2074 function(fromRawKeyset
, toRawKeyset
) {
2076 var toggleState
= false;
2077 if (fromRawKeyset
!= toRawKeyset
) {
2078 // Deal with the switch logic to/from English within the compact layout.
2079 if (fromRawKeyset
.indexOf('en.compact') *
2080 toRawKeyset
.indexOf('en.compact') < 0) { // Switches between non-en/en.
2082 toggleState
= toRawKeyset
.indexOf('en.compact') == -1;
2083 } else if (fromRawKeyset
.indexOf(toRawKeyset
) == 0 &&
2084 fromRawKeyset
.indexOf('.compact') > 0 &&
2085 goog
.array
.contains(util
.KEYSETS_HAVE_EN_SWTICHER
, toRawKeyset
) ||
2086 fromRawKeyset
&& toRawKeyset
.indexOf(fromRawKeyset
) == 0 &&
2087 toRawKeyset
.indexOf('.compact') > 0) {
2088 // Switch between full/compact layouts, reset the default button and
2095 this.adapter_
.toggleLanguageState(toggleState
);
2096 this.model_
.stateManager
.isEnMode
= !toggleState
;
2097 this.container_
.currentKeysetView
.update();
2103 * Records the stats which will be reported when input view is closing.
2105 * @param {string} name The metrics name.
2106 * @param {number} count The count value for histogram.
2107 * @param {number} max .
2108 * @param {number} bucketCount .
2111 Controller
.prototype.recordStatsForClosing_ = function(
2112 name
, count
, max
, bucketCount
) {
2113 if (!this.statsForClosing_
[name
]) {
2114 this.statsForClosing_
[name
] = [count
, max
, bucketCount
];
2116 this.statsForClosing_
[name
][0] += count
;
2117 this.statsForClosing_
[name
][1] = max
;
2118 this.statsForClosing_
[name
][2] = bucketCount
;
2124 * Handles language state changing event.
2126 * @param {!i18n.input.chrome.message.Event} e .
2129 Controller
.prototype.onUpdateToggleLanguateState_ = function(e
) {
2130 if (this.adapter_
.isA11yMode
|| this.currentKeyset_
.indexOf('.compact') < 0) {
2131 // e.msg value means whether is Chinese mode now.
2132 if (this.model_
.stateManager
.isEnMode
== e
.msg
) {
2133 this.model_
.stateManager
.isEnMode
= !e
.msg
;
2134 this.container_
.currentKeysetView
.update();
2137 var pos
= this.currentKeyset_
.indexOf('en.compact');
2139 if (pos
> 0) { // Means en mode
2140 if (e
.msg
) { // Needs switch cn mode
2141 toKeyset
= this.currentKeyset_
.replace('en.compact', 'compact');
2144 if (!e
.msg
) { // Needs switch en mode
2145 toKeyset
= this.currentKeyset_
.replace('compact', 'en.compact');
2150 this.switchToKeyset(toKeyset
);