Source: modules/keyboard_context.js

  1. /**
  2. * KeyboardContext provides installed keyboard apps and enabled keyboard layouts
  3. * in terms of ObservableArrays. It listens to the events from KeyboardHelper
  4. * and update the ObservableArrays.
  5. * KeyboardHelper helps on the following things:
  6. * - Get all installed keyboard apps and layouts.
  7. * - Enable or disable keyboard layouts.
  8. * - Notify keyboard layout changes via the 'keyboardsrefresh' event.
  9. * KeyboardContext handles only data and does not involve in any UI logic.
  10. *
  11. * @module KeyboardContext
  12. */
  13. define(function(require) {
  14. 'use strict';
  15. var Observable = require('modules/mvvm/observable');
  16. var ObservableArray = require('modules/mvvm/observable_array');
  17. var KeyboardHelper = require('shared/keyboard_helper');
  18. var ManifestHelper = require('shared/manifest_helper');
  19. // stores layout indexed by app manifestURL and layoutId
  20. var _layoutDict = null;
  21. var _keyboards = ObservableArray([]);
  22. var _enabledLayouts = ObservableArray([]);
  23. var _isReady = false;
  24. var _parsingApps = false;
  25. var _callbacks = [];
  26. var _defaultEnabledCallbacks = [];
  27. /**
  28. * @alias module:Keyboard
  29. * @class Keyboard
  30. * @param {String} name
  31. The name of the keyboard.
  32. * @param {String} description
  33. The description of the keyboard.
  34. * @param {String} launchPath
  35. The launch path of the keyboard.
  36. * @param {Array} layouts
  37. All layouts included in the keyboard.
  38. * @param {App} app
  39. The keyboard app object.
  40. * @returns {Keyboard}
  41. */
  42. var Keyboard = function(name, description, launchPath, layouts, app) {
  43. var _observable = Observable({
  44. name: name,
  45. description: description,
  46. launchPath: launchPath,
  47. layouts: layouts,
  48. app: app
  49. });
  50. return _observable;
  51. };
  52. /**
  53. * @alias module:Layout
  54. * @class Layout
  55. * @param {String} id
  56. The id of the layout.
  57. * @param {String} appName
  58. The name of the keyboard app containing the layout.
  59. * @param {String} appManifestURL
  60. The manifest url of the keyboard app containing the layout.
  61. * @param {String} name
  62. The name of the layout.
  63. * @param {String} description
  64. The description of the layout.
  65. * @param {Array} types
  66. The supported input types of the layout.
  67. * @param {Boolean} enabled
  68. The value indicating if the layout is enabled or not.
  69. * @returns {Layout}
  70. */
  71. var Layout =
  72. function(id, appName, appManifestURL, name, description, types, enabled) {
  73. var _observable = Observable({
  74. id: id,
  75. appName: appName,
  76. name: name,
  77. description: description,
  78. types: types,
  79. enabled: enabled
  80. });
  81. // Layout enabled changed.
  82. _observable.observe('enabled', function(newValue, oldValue) {
  83. if (!_parsingApps) {
  84. KeyboardHelper.setLayoutEnabled(appManifestURL, id, newValue);
  85. // only check the defaults if we disabled a checkbox
  86. if (!newValue) {
  87. KeyboardHelper.checkDefaults(function(layouts, missingTypes) {
  88. refreshEnabledLayouts(layouts);
  89. notifyDefaultEnabled(layouts, missingTypes);
  90. });
  91. }
  92. }
  93. });
  94. return _observable;
  95. };
  96. var _waitForLayouts;
  97. function refreshEnabledLayouts(reEnabledLayouts) {
  98. reEnabledLayouts.forEach(function(layout) {
  99. var app = _layoutDict[layout.app.manifestURL];
  100. if (app) {
  101. app[layout.layoutId].enabled = true;
  102. }
  103. });
  104. }
  105. function notifyDefaultEnabled(layouts, missingTypes) {
  106. _defaultEnabledCallbacks.forEach(function withCallbacks(callback) {
  107. callback(layouts[0], missingTypes[0]);
  108. });
  109. }
  110. function updateLayouts(layouts, reason) {
  111. function mapLayout(layout) {
  112. var app = _layoutDict[layout.app.manifestURL];
  113. if (!app) {
  114. app = _layoutDict[layout.app.manifestURL] = {};
  115. }
  116. if (app[layout.layoutId]) {
  117. app[layout.layoutId].enabled = layout.enabled;
  118. return app[layout.layoutId];
  119. }
  120. app[layout.layoutId] = Layout(layout.layoutId,
  121. layout.manifest.name, layout.app.manifestURL,
  122. layout.inputManifest.name, layout.inputManifest.description,
  123. layout.inputManifest.types, layout.enabled);
  124. return app[layout.layoutId];
  125. }
  126. function reduceApps(carry, layout) {
  127. // if we already found this app, add it to the layouts
  128. if (!carry.some(function checkApp(app) {
  129. if (app.app === layout.app) {
  130. app.layouts.push(mapLayout(layout));
  131. return true;
  132. }
  133. })) {
  134. carry.push({
  135. app: layout.app,
  136. manifest: layout.manifest,
  137. layouts: [mapLayout(layout)]
  138. });
  139. }
  140. return carry;
  141. }
  142. function mapKeyboard(app) {
  143. return Keyboard(app.manifest.name, app.manifest.description,
  144. app.manifest.launch_path, app.layouts, app.app);
  145. }
  146. _parsingApps = true;
  147. // if we changed apps
  148. if (reason.apps) {
  149. // re parse every layout
  150. _layoutDict = {};
  151. var apps = layouts.reduce(reduceApps, []);
  152. var keyboards = apps.map(mapKeyboard);
  153. _keyboards.reset(keyboards);
  154. }
  155. var enabled = layouts.filter(function filterEnabled(layout) {
  156. return layout.enabled;
  157. }).map(mapLayout);
  158. _enabledLayouts.reset(enabled);
  159. _parsingApps = false;
  160. if (_waitForLayouts) {
  161. _waitForLayouts();
  162. _waitForLayouts = undefined;
  163. }
  164. }
  165. var _init = function(callback) {
  166. window.addEventListener('localized', function() {
  167. // refresh keyboard and layout in _keyboards
  168. _keyboards.forEach(function(keyboard) {
  169. var keyboardAppInstance = keyboard.app;
  170. var keyboardManifest =
  171. new ManifestHelper(keyboardAppInstance.manifest);
  172. var inputs = keyboardManifest.inputs;
  173. keyboard.name = keyboardManifest.name;
  174. keyboard.description = keyboardManifest.description;
  175. keyboard.layouts.forEach(function(layout) {
  176. var key = layout.id;
  177. var layoutInstance = inputs[key];
  178. layout.appName = keyboardManifest.name;
  179. layout.name = layoutInstance.name;
  180. layout.description = layoutInstance.description;
  181. });
  182. });
  183. });
  184. _waitForLayouts = callback;
  185. KeyboardHelper.stopWatching();
  186. KeyboardHelper.watchLayouts(updateLayouts);
  187. };
  188. var _ready = function(callback) {
  189. if (!callback) {
  190. return;
  191. }
  192. if (_isReady) {
  193. callback();
  194. } else {
  195. _callbacks.push(callback);
  196. }
  197. };
  198. return {
  199. /**
  200. * Reset the keyboard context. It clears all cached data of installed
  201. * keyboards and current enabled layouts.
  202. *
  203. * @alias module:KeyboardContext#reset
  204. */
  205. reset: function kc_reset() {
  206. _layoutDict = null;
  207. _keyboards = ObservableArray([]);
  208. _enabledLayouts = ObservableArray([]);
  209. _isReady = false;
  210. _parsingApps = false;
  211. _callbacks = [];
  212. _defaultEnabledCallbacks = [];
  213. },
  214. /**
  215. * Initialize the keyboard context. After the context initialized, we are
  216. * able to get the installed keyboards and enabled layouts.
  217. *
  218. * @alias module:KeyboardContext#init
  219. * @param {Function} callback
  220. * The callback when the context is initialized.
  221. */
  222. init: function kc_init(callback) {
  223. _defaultEnabledCallbacks = [];
  224. _isReady = false;
  225. _init(function() {
  226. _isReady = true;
  227. _callbacks.forEach(function(callback) {
  228. callback();
  229. });
  230. });
  231. _ready(callback);
  232. },
  233. /**
  234. * Get the installed keyboards in terms of an observable array.
  235. *
  236. * @alias module:KeyboardContext#keyboards
  237. * @param {Function} callback
  238. * The result is passed to the callback when ready.
  239. */
  240. keyboards: function kc_keyboards(callback) {
  241. _ready(function() {
  242. callback(_keyboards);
  243. });
  244. },
  245. /**
  246. * Get the enabled layouts in terms of an observable array.
  247. *
  248. * @alias module:KeyboardContext#enabledLayouts
  249. * @param {Function} callback
  250. * The result is passed to the callback when ready.
  251. */
  252. enabledLayouts: function kc_enabledLayouts(callback) {
  253. _ready(function() {
  254. callback(_enabledLayouts);
  255. });
  256. },
  257. /**
  258. * Add a callback to be triggered when the default keyboard is enabled.
  259. *
  260. * @alias module:KeyboardContext#defaultKeyboardEnabled
  261. * @param {Function} callback
  262. * The callback to be triggered.
  263. */
  264. defaultKeyboardEnabled: function kc_defaultKeyboardEnabled(callback) {
  265. _defaultEnabledCallbacks.push(callback);
  266. }
  267. };
  268. });