blob: 959499543c417459615e50adc033699af2806bfc [file] [log] [blame]
// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
// limitations under the License.
// See the License for the specific language governing permissions and
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// distributed under the License is distributed on an "AS-IS" BASIS,
// Unless required by applicable law or agreed to in writing, software
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// You may obtain a copy of the License at
// you may not use this file except in compliance with the License.
// Licensed under the Apache License, Version 2.0 (the "License");
//
goog.provide('i18n.input.chrome.inputview.Adapter');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('i18n.input.chrome.DataSource');
goog.require('i18n.input.chrome.inputview.ReadyState');
goog.require('i18n.input.chrome.inputview.StateType');
goog.require('i18n.input.chrome.inputview.events.EventType');
goog.require('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent');
goog.require('i18n.input.chrome.message.ContextType');
goog.require('i18n.input.chrome.message.Event');
goog.require('i18n.input.chrome.message.Name');
goog.require('i18n.input.chrome.message.Type');
goog.scope(function() {
var CandidatesBackEvent = i18n.input.chrome.DataSource.CandidatesBackEvent;
var ContextType = i18n.input.chrome.message.ContextType;
var Type = i18n.input.chrome.message.Type;
var Name = i18n.input.chrome.message.Name;
/**
* The adapter for interview.
*
* @param {!i18n.input.chrome.inputview.ReadyState} readyState .
* @extends {goog.events.EventTarget}
* @constructor
*/
i18n.input.chrome.inputview.Adapter = function(readyState) {
goog.base(this);
/**
* Whether the keyboard is visible.
*
* @type {boolean}
*/
this.isVisible = !document.webkitHidden;
/**
* The modifier state map.
*
* @type {!Object.<i18n.input.chrome.inputview.StateType, boolean>}
* @private
*/
this.modifierState_ = {};
/**
* The system ready state.
*
* @private {!i18n.input.chrome.inputview.ReadyState}
*/
this.readyState_ = readyState;
chrome.runtime.onMessage.addListener(this.onMessage_.bind(this));
/** @private {!goog.events.EventHandler} */
this.handler_ = new goog.events.EventHandler(this);
this.handler_.listen(document, 'webkitvisibilitychange',
this.onVisibilityChange_);
};
goog.inherits(i18n.input.chrome.inputview.Adapter,
goog.events.EventTarget);
var Adapter = i18n.input.chrome.inputview.Adapter;
/** @type {boolean} */
Adapter.prototype.isA11yMode = false;
/** @type {boolean} */
Adapter.prototype.isExperimental = false;
/** @type {boolean} */
Adapter.prototype.showGlobeKey = false;
/** @protected {string} */
Adapter.prototype.contextType = ContextType.DEFAULT;
/** @type {string} */
Adapter.prototype.screen = '';
/** @type {boolean} */
Adapter.prototype.isChromeVoxOn = false;
/** @type {string} */
Adapter.prototype.textBeforeCursor = '';
/**
* Callback for updating settings.
*
* @param {!Object} message .
* @private
*/
Adapter.prototype.onUpdateSettings_ = function(message) {
this.contextType = message['contextType'];
this.screen = message['screen'];
this.dispatchEvent(new i18n.input.chrome.message.Event(Type.UPDATE_SETTINGS,
message));
};
/**
* Sets the modifier states.
*
* @param {i18n.input.chrome.inputview.StateType} stateType .
* @param {boolean} enable True to enable the state, false otherwise.
*/
Adapter.prototype.setModifierState = function(stateType, enable) {
this.modifierState_[stateType] = enable;
};
/**
* Clears the modifier states.
*/
Adapter.prototype.clearModifierStates = function() {
this.modifierState_ = {};
};
/**
* Simulates to send 'keydown' and 'keyup' event.
*
* @param {string} key
* @param {string} code
* @param {number=} opt_keyCode The key code.
* @param {!Object=} opt_spatialData .
*/
Adapter.prototype.sendKeyDownAndUpEvent = function(key, code, opt_keyCode,
opt_spatialData) {
this.sendKeyEvent_([
this.generateKeyboardEvent_(
goog.events.EventType.KEYDOWN, key, code, opt_keyCode, opt_spatialData),
this.generateKeyboardEvent_(
goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData)
]);
};
/**
* Simulates to send 'keydown' event.
*
* @param {string} key
* @param {string} code
* @param {number=} opt_keyCode The key code.
* @param {!Object=} opt_spatialData .
*/
Adapter.prototype.sendKeyDownEvent = function(key, code, opt_keyCode,
opt_spatialData) {
this.sendKeyEvent_([this.generateKeyboardEvent_(
goog.events.EventType.KEYDOWN, key, code, opt_keyCode,
opt_spatialData)]);
};
/**
* Simulates to send 'keyup' event.
*
* @param {string} key
* @param {string} code
* @param {number=} opt_keyCode The key code.
* @param {!Object=} opt_spatialData .
*/
Adapter.prototype.sendKeyUpEvent = function(key, code, opt_keyCode,
opt_spatialData) {
this.sendKeyEvent_([this.generateKeyboardEvent_(
goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData)]);
};
/**
* Use {@code chrome.input.ime.sendKeyEvents} to simulate key events.
*
* @param {!Array.<!Object.<string, string|boolean>>} keyData .
* @private
*/
Adapter.prototype.sendKeyEvent_ = function(keyData) {
chrome.runtime.sendMessage(
goog.object.create(Name.TYPE, Type.SEND_KEY_EVENT, Name.KEY_DATA,
keyData));
};
/**
* Generates a {@code ChromeKeyboardEvent} by given values.
*
* @param {string} type .
* @param {string} key The key.
* @param {string} code The code.
* @param {number=} opt_keyCode The key code.
* @param {!Object=} opt_spatialData .
* @return {!Object.<string, string|boolean>}
* @private
*/
Adapter.prototype.generateKeyboardEvent_ = function(
type, key, code, opt_keyCode, opt_spatialData) {
var StateType = i18n.input.chrome.inputview.StateType;
var ctrl = !!this.modifierState_[StateType.CTRL];
var alt = !!this.modifierState_[StateType.ALT];
if (ctrl || alt) {
key = '';
}
var result = {
'type': type,
'key': key,
'code': code,
'keyCode': opt_keyCode || 0,
'spatialData': opt_spatialData
};
result['altKey'] = alt;
result['ctrlKey'] = ctrl;
result['shiftKey'] = !!this.modifierState_[StateType.SHIFT];
result['capsLock'] = !!this.modifierState_[StateType.CAPSLOCK];
return result;
};
/**
* Callback when surrounding text is changed.
*
* @param {string} text .
* @private
*/
Adapter.prototype.onSurroundingTextChanged_ = function(text) {
this.textBeforeCursor = text;
this.dispatchEvent(new i18n.input.chrome.inputview.events.
SurroundingTextChangedEvent(this.textBeforeCursor));
};
/**
* Gets the context.
*
* @return {string} .
*/
Adapter.prototype.getContext = function() {
var matches = this.textBeforeCursor.match(/([a-zA-Z'-Ḁ-ỹÀ-ȳ]+)\s+$/);
var text = matches ? matches[1] : '';
return text;
};
/**
* Gets the context type.
*
* @return {string} .
*/
Adapter.prototype.getContextType = function() {
return this.contextType || ContextType.DEFAULT;
};
/**
* Sends request for handwriting.
*
* @param {!Object} payload .
*/
Adapter.prototype.sendHwtRequest = function(payload) {
chrome.runtime.sendMessage(goog.object.create(
Name.TYPE, Type.HWT_REQUEST, Name.MSG, payload
));
};
/**
* True if it is a password box.
*
* @return {boolean} .
*/
Adapter.prototype.isPasswordBox = function() {
return this.contextType == 'password';
};
/**
* Callback when blurs in the context.
*
* @private
*/
Adapter.prototype.onContextBlur_ = function() {
this.contextType = '';
this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events.
EventType.CONTEXT_BLUR));
};
/**
* Callback when focus on a context.
*
* @param {string} contextType .
* @private
*/
Adapter.prototype.onContextFocus_ = function(contextType) {
this.contextType = contextType;
this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events.
EventType.CONTEXT_FOCUS));
};
/**
* Intializes the communication to background page.
*
* @param {string} languageCode The language code.
* @private
*/
Adapter.prototype.initBackground_ = function(languageCode) {
chrome.runtime.getBackgroundPage((function() {
chrome.runtime.sendMessage(
goog.object.create(Name.TYPE, Type.CONNECT));
chrome.runtime.sendMessage(goog.object.create(Name.TYPE,
Type.VISIBILITY_CHANGE, Name.VISIBILITY, !document.webkitHidden));
if (languageCode) {
this.setLanguage(languageCode);
}
}).bind(this));
};
/**
* Loads the keyboard settings.
*
* @param {string} languageCode The language code.
*/
Adapter.prototype.initialize = function(languageCode) {
if (chrome.accessibilityFeatures &&
chrome.accessibilityFeatures.spokenFeedback) {
chrome.accessibilityFeatures.spokenFeedback.get({}, (function(details) {
this.isChromeVoxOn = details['value'];
}).bind(this));
chrome.accessibilityFeatures.spokenFeedback.onChange.addListener((function(
details) {
this.isChromeVoxOn = details['value'];
}).bind(this));
}
this.initBackground_(languageCode);
var StateType = i18n.input.chrome.inputview.ReadyState.StateType;
if (window.inputview) {
if (inputview.getKeyboardConfig) {
inputview.getKeyboardConfig((function(config) {
this.isA11yMode = !!config['a11ymode'];
this.isExperimental = !!config['experimental'];
this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
if (this.readyState_.isReady(StateType.IME_LIST_READY)) {
this.dispatchEvent(new goog.events.Event(
i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
}
}).bind(this));
} else {
this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
}
if (inputview.getInputMethods) {
inputview.getInputMethods((function(inputMethods) {
// Only show globe key to switching between IMEs when there are more
// than one IME.
this.showGlobeKey = inputMethods.length > 1;
this.readyState_.markStateReady(StateType.IME_LIST_READY);
if (this.readyState_.isReady(StateType.KEYBOARD_CONFIG_READY)) {
this.dispatchEvent(new goog.events.Event(
i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
}
}).bind(this));
} else {
this.readyState_.markStateReady(StateType.IME_LIST_READY);
}
} else {
this.readyState_.markStateReady(StateType.IME_LIST_READY);
this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
}
if (this.readyState_.isReady(StateType.KEYBOARD_CONFIG_READY) &&
this.readyState_.isReady(StateType.IME_LIST_READY)) {
window.setTimeout((function() {
this.dispatchEvent(new goog.events.Event(
i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
}).bind(this), 0);
}
};
/**
* Gets the currently activated input method.
*
* @param {function(string)} callback .
*/
Adapter.prototype.getCurrentInputMethod = function(callback) {
if (window.inputview && inputview.getCurrentInputMethod) {
inputview.getCurrentInputMethod(callback);
} else {
callback('DU');
}
};
/**
* Gets the list of all activated input methods.
*
* @param {function(Array.<Object>)} callback .
*/
Adapter.prototype.getInputMethods = function(callback) {
if (window.inputview && inputview.getInputMethods) {
inputview.getInputMethods(callback);
} else {
// Provides a dummy IME item to enable IME switcher UI.
callback([
{'indicator': 'DU', 'id': 'DU', 'name': 'Dummy IME', 'command': 1}]);
}
};
/**
* Switches to the input method with id equals |inputMethodId|.
*
* @param {!string} inputMethodId .
*/
Adapter.prototype.switchToInputMethod = function(inputMethodId) {
if (window.inputview && inputview.switchToInputMethod) {
inputview.switchToInputMethod(inputMethodId);
}
};
/**
* Callback for visibility change on the input view window.
*
* @private
*/
Adapter.prototype.onVisibilityChange_ = function() {
this.isVisible = !document.webkitHidden;
this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.
events.EventType.VISIBILITY_CHANGE));
chrome.runtime.sendMessage(goog.object.create(Name.TYPE,
Type.VISIBILITY_CHANGE, Name.VISIBILITY, !document.webkitHidden));
};
/**
* Sends request for completion.
*
* @param {string} query .
* @param {!Object=} opt_spatialData .
*/
Adapter.prototype.sendCompletionRequest = function(query, opt_spatialData) {
var spatialData = {};
if (opt_spatialData) {
spatialData[Name.SOURCES] = opt_spatialData.sources;
spatialData[Name.POSSIBILITIES] = opt_spatialData.possibilities;
}
chrome.runtime.sendMessage(goog.object.create(Name.TYPE,
Type.COMPLETION, Name.TEXT, query, Name.SPATIAL_DATA, spatialData));
};
/**
* Selects the candidate.
*
* @param {!Object} candidate .
*/
Adapter.prototype.selectCandidate = function(candidate) {
chrome.runtime.sendMessage(goog.object.create(
Name.TYPE, Type.SELECT_CANDIDATE, Name.CANDIDATE, candidate));
};
/**
* Commits the text.
*
* @param {string} text .
*/
Adapter.prototype.commitText = function(text) {
chrome.runtime.sendMessage(goog.object.create(
Name.TYPE, Type.COMMIT_TEXT, Name.TEXT, text));
};
/**
* Sets the language.
*
* @param {string} language .
*/
Adapter.prototype.setLanguage = function(language) {
chrome.runtime.sendMessage(goog.object.create(
Name.TYPE, Type.SET_LANGUAGE, Name.LANGUAGE, language));
};
/**
* Callbck when completion is back.
*
* @param {!Object} message .
* @private
*/
Adapter.prototype.onCandidatesBack_ = function(message) {
var source = message['source'] || '';
var candidates = message['candidates'] || [];
this.dispatchEvent(new CandidatesBackEvent(source, candidates));
};
/**
* Hides the keyboard.
*/
Adapter.prototype.hideKeyboard = function() {
chrome.input.ime.hideInputView();
};
/**
* Sends Input Tool code to background.
*
* @param {string} inputToolCode .
*/
Adapter.prototype.setInputToolCode = function(inputToolCode) {
chrome.runtime.sendMessage(
goog.object.create(
Name.TYPE,
Type.HWT_SET_INPUTTOOL,
Name.MSG,
inputToolCode));
};
/**
* Sends DOUBLE_CLICK_ON_SPACE_KEY message.
*/
Adapter.prototype.doubleClickOnSpaceKey = function() {
chrome.runtime.sendMessage(
goog.object.create(
Name.TYPE,
Type.DOUBLE_CLICK_ON_SPACE_KEY));
};
/**
* Sends message to the background when switch to emoji.
*
*/
Adapter.prototype.setEmojiInputToolCode = function() {
chrome.runtime.sendMessage(
goog.object.create(
Name.TYPE,
Type.EMOJI_SET_INPUTTOOL));
};
/**
* Sends message to the background when do internal inputtool switch.
*
* @param {boolean} inputToolValue The value of the language flag.
*/
Adapter.prototype.toggleLanguageState = function(inputToolValue) {
chrome.runtime.sendMessage(
goog.object.create(
Name.TYPE,
Type.TOGGLE_LANGUAGE_STATE,
Name.MSG,
inputToolValue));
};
/**
* Sends unset Input Tool code to background.
*/
Adapter.prototype.unsetInputToolCode = function() {
chrome.runtime.sendMessage(
goog.object.create(
Name.TYPE,
Type.HWT_UNSET_INPUTTOOL));
};
/**
* Sends message to the background when switch to other mode from emoji.
*
*/
Adapter.prototype.unsetEmojiInputToolCode = function() {
chrome.runtime.sendMessage(
goog.object.create(
Name.TYPE,
Type.EMOJI_UNSET_INPUTTOOL));
};
/**
* Processes incoming message from option page or inputview window.
*
* @param {*} request Message from option page or inputview window.
* @param {*} sender Information about the script
* context that sent the message.
* @param {function(*): void} sendResponse Function to call to send a response.
* @return {boolean|undefined} {@code true} to keep the message channel open in
* order to send a response asynchronously.
* @private
*/
Adapter.prototype.onMessage_ = function(request, sender, sendResponse) {
var type = request[Name.TYPE];
var msg = request[Name.MSG];
switch (type) {
case Type.CANDIDATES_BACK:
this.onCandidatesBack_(msg);
break;
case Type.CONTEXT_FOCUS:
this.onContextFocus_(request[Name.CONTEXT_TYPE]);
break;
case Type.CONTEXT_BLUR:
this.onContextBlur_();
break;
case Type.SURROUNDING_TEXT_CHANGED:
this.onSurroundingTextChanged_(request[Name.TEXT]);
break;
case Type.UPDATE_SETTINGS:
this.onUpdateSettings_(msg);
break;
case Type.HWT_NETWORK_ERROR:
case Type.HWT_PRIVACY_INFO:
this.dispatchEvent(new i18n.input.chrome.message.Event(type, msg));
break;
}
};
/**
* Sends the privacy confirmed message to background and broadcasts it.
*/
Adapter.prototype.sendHwtPrivacyConfirmMessage = function() {
chrome.runtime.sendMessage(
goog.object.create(Name.TYPE, Type.HWT_PRIVACY_GOT_IT));
this.dispatchEvent(
new goog.events.Event(Type.HWT_PRIVACY_GOT_IT));
};
/** @override */
Adapter.prototype.disposeInternal = function() {
goog.dispose(this.handler_);
goog.base(this, 'disposeInternal');
};
}); // goog.scope