/** * KeyboardContext provides installed keyboard apps and enabled keyboard layouts * in terms of ObservableArrays. It listens to the events from KeyboardHelper * and update the ObservableArrays. * KeyboardHelper helps on the following things: * - Get all installed keyboard apps and layouts. * - Enable or disable keyboard layouts. * - Notify keyboard layout changes via the 'keyboardsrefresh' event. * KeyboardContext handles only data and does not involve in any UI logic. * * @module KeyboardContext */ define(function(require) { 'use strict'; var Observable = require('modules/mvvm/observable'); var ObservableArray = require('modules/mvvm/observable_array'); var KeyboardHelper = require('shared/keyboard_helper'); var ManifestHelper = require('shared/manifest_helper'); // stores layout indexed by app manifestURL and layoutId var _layoutDict = null; var _keyboards = ObservableArray([]); var _enabledLayouts = ObservableArray([]); var _isReady = false; var _parsingApps = false; var _callbacks = []; var _defaultEnabledCallbacks = []; /** * @alias module:Keyboard * @class Keyboard * @param {String} name The name of the keyboard. * @param {String} description The description of the keyboard. * @param {String} launchPath The launch path of the keyboard. * @param {Array} layouts All layouts included in the keyboard. * @param {DOMApplication} app The keyboard app object. * @returns {Keyboard} */ var Keyboard = function(name, description, launchPath, layouts, app) { var _observable = Observable({ name: name, description: description, launchPath: launchPath, layouts: layouts, app: app }); return _observable; }; /** * @alias module:Layout * @class Layout * @param {Object} Contain the following properties * - id The id of the layout. * - appName The name of the keyboard app containing the layout. * - appManifestURL The manifest url of the keyboard app containing the layout. * - name The name of the layout. * - nameL10nId The l10n id of the layout name. * - description The description of the layout. * - types The supported input types of the layout. * - enabled The value indicating if the layout is enabled or not. * @returns {Layout} */ var Layout = function(options) { var _observable = Observable({ id: options.id, appName: options.appName, name: options.name, nameL10nId: options.nameL10nId, description: options.description, types: options.types, enabled: options.enabled }); // Layout enabled changed. _observable.observe('enabled', function(newValue, oldValue) { if (!_parsingApps) { KeyboardHelper.setLayoutEnabled( options.appManifestURL, options.id, newValue); // only check the defaults if we disabled a checkbox if (!newValue) { KeyboardHelper.checkDefaults(function(layouts, missingTypes) { refreshEnabledLayouts(layouts); notifyDefaultEnabled(layouts, missingTypes); }); } } }); return _observable; }; var _waitForLayouts; function refreshEnabledLayouts(reEnabledLayouts) { reEnabledLayouts.forEach(function(layout) { var app = _layoutDict[layout.app.manifestURL]; if (app) { app[layout.layoutId].enabled = true; } }); } function notifyDefaultEnabled(layouts, missingTypes) { _defaultEnabledCallbacks.forEach(function withCallbacks(callback) { callback(layouts[0], missingTypes[0]); }); } function updateLayouts(layouts, reason) { function mapLayout(layout) { var app = _layoutDict[layout.app.manifestURL]; if (!app) { app = _layoutDict[layout.app.manifestURL] = {}; } if (app[layout.layoutId]) { app[layout.layoutId].enabled = layout.enabled; return app[layout.layoutId]; } app[layout.layoutId] = Layout({ id: layout.layoutId, appName: layout.manifest.name, appManifestURL: layout.app.manifestURL, name: layout.inputManifest.name, nameL10nId: layout.inputManifest.nameL10nId, description: layout.inputManifest.description, types: layout.inputManifest.types, enabled: layout.enabled}); return app[layout.layoutId]; } function reduceApps(carry, layout) { // if we already found this app, add it to the layouts if (!carry.some(function checkApp(app) { if (app.app === layout.app) { app.layouts.push(mapLayout(layout)); return true; } })) { carry.push({ app: layout.app, manifest: layout.manifest, layouts: [mapLayout(layout)] }); } return carry; } function mapKeyboard(app) { return Keyboard(app.manifest.name, app.manifest.description, app.manifest.launch_path, app.layouts, app.app); } _parsingApps = true; // if we changed apps if (reason.apps) { // re parse every layout _layoutDict = {}; var apps = layouts.reduce(reduceApps, []); var keyboards = apps.map(mapKeyboard); _keyboards.reset(keyboards); } var enabled = layouts.filter(function filterEnabled(layout) { return layout.enabled; }).map(mapLayout); _enabledLayouts.reset(enabled); _parsingApps = false; if (_waitForLayouts) { _waitForLayouts(); _waitForLayouts = undefined; } } var _init = function(callback) { document.addEventListener('DOMRetranslated', function() { // refresh keyboard and layout in _keyboards _keyboards.forEach(function(keyboard) { var keyboardAppInstance = keyboard.app; var keyboardManifest = new ManifestHelper(keyboardAppInstance.manifest); keyboard.name = keyboardManifest.name; keyboard.description = keyboardManifest.description; keyboard.layouts.forEach(function(layout) { layout.appName = keyboardManifest.name; }); }); }); _waitForLayouts = callback; KeyboardHelper.stopWatching(); KeyboardHelper.watchLayouts(updateLayouts); }; var _ready = function(callback) { if (!callback) { return; } if (_isReady) { callback(); } else { _callbacks.push(callback); } }; return { /** * Reset the keyboard context. It clears all cached data of installed * keyboards and current enabled layouts. * * @alias module:KeyboardContext#reset */ reset: function kc_reset() { _layoutDict = null; _keyboards = ObservableArray([]); _enabledLayouts = ObservableArray([]); _isReady = false; _parsingApps = false; _callbacks = []; _defaultEnabledCallbacks = []; }, /** * Initialize the keyboard context. After the context initialized, we are * able to get the installed keyboards and enabled layouts. * * @alias module:KeyboardContext#init * @param {Function} callback * The callback when the context is initialized. */ init: function kc_init(callback) { _defaultEnabledCallbacks = []; _isReady = false; _init(function() { _isReady = true; _callbacks.forEach(function(callback) { callback(); }); }); _ready(callback); }, /** * Get the installed keyboards in terms of an observable array. * * @alias module:KeyboardContext#keyboards * @param {Function} callback * The result is passed to the callback when ready. */ keyboards: function kc_keyboards(callback) { _ready(function() { callback(_keyboards); }); }, /** * Get the enabled layouts in terms of an observable array. * * @alias module:KeyboardContext#enabledLayouts * @param {Function} callback * The result is passed to the callback when ready. */ enabledLayouts: function kc_enabledLayouts(callback) { _ready(function() { callback(_enabledLayouts); }); }, /** * Add a callback to be triggered when the default keyboard is enabled. * * @alias module:KeyboardContext#defaultKeyboardEnabled * @param {Function} callback * The callback to be triggered. */ defaultKeyboardEnabled: function kc_defaultKeyboardEnabled(callback) { _defaultEnabledCallbacks.push(callback); } }; });