/**
* 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 {App} 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 {String} id
The id of the layout.
* @param {String} appName
The name of the keyboard app containing the layout.
* @param {String} appManifestURL
The manifest url of the keyboard app containing the layout.
* @param {String} name
The name of the layout.
* @param {String} description
The description of the layout.
* @param {Array} types
The supported input types of the layout.
* @param {Boolean} enabled
The value indicating if the layout is enabled or not.
* @returns {Layout}
*/
var Layout =
function(id, appName, appManifestURL, name, description, types, enabled) {
var _observable = Observable({
id: id,
appName: appName,
name: name,
description: description,
types: types,
enabled: enabled
});
// Layout enabled changed.
_observable.observe('enabled', function(newValue, oldValue) {
if (!_parsingApps) {
KeyboardHelper.setLayoutEnabled(appManifestURL, 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(layout.layoutId,
layout.manifest.name, layout.app.manifestURL,
layout.inputManifest.name, layout.inputManifest.description,
layout.inputManifest.types, 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) {
window.addEventListener('localized', function() {
// refresh keyboard and layout in _keyboards
_keyboards.forEach(function(keyboard) {
var keyboardAppInstance = keyboard.app;
var keyboardManifest =
new ManifestHelper(keyboardAppInstance.manifest);
var inputs = keyboardManifest.inputs;
keyboard.name = keyboardManifest.name;
keyboard.description = keyboardManifest.description;
keyboard.layouts.forEach(function(layout) {
var key = layout.id;
var layoutInstance = inputs[key];
layout.appName = keyboardManifest.name;
layout.name = layoutInstance.name;
layout.description = layoutInstance.description;
});
});
});
_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);
}
};
});