Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview 'cr-settings-languages-model' provides convenient access to | |
| 7 * Chrome's language and input method settings. | |
| 8 * | |
| 9 * A singleton element, 'cr-settings-language-model-private', provides a | |
| 10 * read-only model object with information about languages and input methods. | |
| 11 * The singleton updates this model whenever Chrome's pref store and other | |
| 12 * settings change, and also provides an API for changing these settings. | |
| 13 * | |
| 14 * To access the model's object and the singleton's API, add an instance of | |
| 15 * cr-settings-languages-model to your local DOM: | |
| 16 * | |
| 17 * <template> | |
| 18 * <cr-settings-languages-model model="{{model}}"> | |
| 19 * </cr-settings-languages-model> | |
| 20 * <div>[[model.someProperty]]</div> | |
| 21 * </template> | |
| 22 * | |
| 23 * This element exposes the model and the API, so it can be used like any other | |
| 24 * Polymer element without having to actually create duplicate models. | |
| 25 * | |
| 26 * Internally, this element communicates with the singleton to propagate | |
| 27 * changes for data binding. Because each instance of this element has a | |
| 28 * reference to the same singleton, each element can add a listener for the | |
| 29 * singleton's 'model-changed' event. Once triggered, each element then forwards | |
| 30 * this event on to its host. From the perspective of the host, the element | |
| 31 * emits 'model-changed' events just like any other property change events. | |
| 32 * | |
| 33 * This data binding is one-way, child-to-host: the model property is read-only. | |
|
stevenjb
2015/09/22 16:47:33
This seems to contradict 'model="{{model}}"' above
michaelpg
2015/09/23 21:27:27
Yes, as described by https://www.polymer-project.o
| |
| 34 * Any changes should be made through this element's public functions. | |
| 35 * | |
| 36 * @group Chrome Settings Elements | |
| 37 * @element cr-settings-languages-model | |
| 38 */ | |
| 39 | |
| 40 /** | |
| 41 * @typedef {{spellCheckEnabled: boolean}} | |
| 42 */ | |
| 43 var LanguageState; | |
| 44 | |
| 45 /** | |
| 46 * @typedef {{language: !chrome.languageSettingsPrivate.Language, | |
| 47 * state: !LanguageState}} | |
| 48 */ | |
| 49 var LanguageInfo; | |
| 50 | |
| 51 /** | |
| 52 * The model includes: | |
| 53 * supportedLanguages: an array of languages, ordered alphabetically. | |
| 54 * enabledLanguages: an array of enabled language info and state, ordered by | |
| 55 * preference. | |
| 56 * @typedef {{ | |
| 57 * supportedLanguages: !Array<!chrome.languageSettingsPrivate.Language>, | |
| 58 * enabledLanguages: !Array<!LanguageInfo> | |
| 59 * }} | |
| 60 */ | |
| 61 var LanguagesModel; | |
| 62 | |
| 63 (function() { | |
| 64 'use strict'; | |
| 65 | |
| 66 /** | |
| 67 * This element has a reference to the singleton, exposing its model to other | |
| 68 * Polymer objects. | |
| 69 * */ | |
| 70 Polymer({ | |
| 71 is: 'cr-settings-languages-model', | |
| 72 | |
| 73 properties: { | |
| 74 /** | |
| 75 * Singleton element created at startup which provides the model shared by | |
| 76 * all instances of cr-settings-language-model. | |
| 77 * @type {!Element} | |
| 78 */ | |
| 79 privateModel_: { | |
| 80 type: Object, | |
| 81 value: document.createElement('cr-settings-languages-model-private'), | |
| 82 }, | |
| 83 | |
| 84 /** | |
| 85 * The singleton's private model, exposed as a property so hosts can bind | |
|
stevenjb
2015/09/22 16:47:33
This is super confusing, why is the above member n
| |
| 86 * to it. | |
| 87 * @type {LanguagesModel|undefined} | |
| 88 */ | |
| 89 model: { | |
| 90 type: Object, | |
| 91 notify: true, | |
| 92 readOnly: true, | |
| 93 }, | |
| 94 }, | |
| 95 | |
| 96 ready: function() { | |
| 97 // Set the 'model' property to reference the singleton's model. | |
| 98 this._setModel(this.privateModel_.model); | |
| 99 // Have to manually notify the host of changes to this model property | |
| 100 // by listening for changes to the singleton's model property. | |
| 101 this.listen(this.privateModel_, 'model-changed', 'privateModelChanged_'); | |
| 102 }, | |
| 103 | |
| 104 /** | |
| 105 * Forwards changes to the model reported by the singleton to the host. | |
| 106 * From the host's perspective, our 'model' property will have changed. | |
| 107 * @private | |
| 108 */ | |
| 109 privateModelChanged_: function(e) { | |
| 110 // Forward the change notification to the host. | |
| 111 this.fire(e.type, e.detail, {bubbles: false}); | |
| 112 }, | |
| 113 | |
| 114 // Forward public methods to the singleton. | |
|
michaelpg
2015/09/22 04:03:15
Obviously forwarding all these methods is ugly. Bu
| |
| 115 | |
| 116 /** @param {string} languageCode */ | |
| 117 setUILanguage: function(languageCode) { | |
| 118 this.privateModel_.setUILanguage(languageCode); | |
| 119 }, | |
| 120 | |
| 121 /** @param {string} languageCode */ | |
| 122 enableLanguage: function(languageCode) { | |
| 123 this.privateModel_.enableLanguage(languageCode); | |
| 124 }, | |
| 125 | |
| 126 /** @param {string} languageCode */ | |
| 127 disableLanguage: function(languageCode) { | |
| 128 this.privateModel_.disableLanguage(languageCode); | |
| 129 }, | |
| 130 | |
| 131 /** | |
| 132 * @param {string} languageCode | |
| 133 * @return {boolean} | |
| 134 */ | |
| 135 isEnabled: function(languageCode) { | |
| 136 return this.privateModel_.isEnabled(languageCode); | |
| 137 }, | |
| 138 | |
| 139 /** | |
| 140 * @param {string} languageCode | |
| 141 * @param {boolean} enable | |
| 142 */ | |
| 143 toggleSpellCheck: function(languageCode, enable) { | |
| 144 this.privateModel_.toggleSpellCheck(languageCode, enable); | |
| 145 }, | |
| 146 }); | |
| 147 | |
| 148 var preferredLanguagesPath; | |
| 149 if (cr.isChromeOS) | |
| 150 preferredLanguagesPath = 'prefs.settings.language.preferred_languages.value'; | |
| 151 else | |
| 152 preferredLanguagesPath = 'prefs.intl.accept_languages.value'; | |
| 153 | |
| 154 /** | |
| 155 * Privately used element that contains, listens to and updates the shared | |
| 156 * languages model. | |
| 157 */ | |
| 158 Polymer({ | |
| 159 is: 'cr-settings-languages-model-private', | |
| 160 | |
| 161 properties: { | |
| 162 /** | |
| 163 * @type {LanguagesModel|undefined} | |
| 164 */ | |
| 165 model: { | |
| 166 type: Object, | |
| 167 notify: true, | |
| 168 }, | |
| 169 | |
| 170 /** | |
| 171 * Object containing all preferences, for use by Polymer controls. | |
| 172 */ | |
| 173 prefs: { | |
| 174 type: Object, | |
| 175 notify: true, | |
| 176 }, | |
| 177 }, | |
| 178 | |
| 179 /** | |
| 180 * Hash map of model.supportedLanguages using language codes as keys for | |
| 181 * fast lookup. Not data bound. | |
| 182 * @private {!Object<!chrome.languageSettingsPrivate.Language>} | |
| 183 */ | |
| 184 supportedLanguageMap_: {}, | |
| 185 | |
| 186 /** | |
| 187 * Hash maps of model.enabledLanguages using language codes as keys for | |
| 188 * fast lookup. Not data bound. | |
| 189 * @private {!Object<!LanguageInfo>} | |
| 190 */ | |
| 191 enabledLanguageMap_: {}, | |
| 192 | |
| 193 observers: [ | |
| 194 'preferredLanguagesPrefChanged_(' + preferredLanguagesPath + ')', | |
| 195 'spellCheckDictionariesPrefChanged_(prefs.spellcheck.dictionaries.value.*)', | |
| 196 ], | |
| 197 | |
| 198 /** @override */ | |
| 199 created: function() { | |
| 200 chrome.languageSettingsPrivate.getLanguageList(function(languageList) { | |
| 201 // Wait until prefs are initialized before creating the model, so we can | |
| 202 // include information about enabled languages. | |
| 203 CrSettingsPrefs.initialized.then(function() { | |
| 204 this.createModel_(languageList); | |
| 205 this.initialized_ = true; | |
| 206 }.bind(this)); | |
| 207 }.bind(this)); | |
| 208 }, | |
| 209 | |
| 210 /** | |
| 211 * Constructs the model with the given language list. | |
| 212 * @param {!Array<!chrome.languageSettingsPrivate.Language>} | |
| 213 * supportedLanguages | |
| 214 */ | |
| 215 createModel_: function(supportedLanguages) { | |
| 216 // Populate the hash map of supported languages. | |
| 217 for (var i = 0; i < supportedLanguages.length; i++) { | |
| 218 this.supportedLanguageMap_[supportedLanguages[i].code] = | |
| 219 supportedLanguages[i]; | |
| 220 } | |
| 221 | |
| 222 // Create a list of enabled language info from the supported languages. | |
| 223 var enabledLanguages = this.getEnabledLanguages_(); | |
| 224 // Populate the hash map of enabled languages. | |
| 225 for (var i = 0; i < enabledLanguages.length; i++) { | |
| 226 var languageInfo = enabledLanguages[i]; | |
| 227 this.enabledLanguageMap_[languageInfo.language.code] = languageInfo; | |
| 228 } | |
| 229 | |
| 230 // Initialize the Polymer model. | |
| 231 this.model = { | |
| 232 supportedLanguages: supportedLanguages, | |
| 233 enabledLanguages: enabledLanguages, | |
| 234 }; | |
| 235 }, | |
| 236 | |
| 237 /** | |
| 238 * Returns a list of LanguageInfos for each enabled language in the supported | |
| 239 * languages list. | |
| 240 * @private | |
| 241 * @return {!Array<!LanguageInfo>} | |
| 242 */ | |
| 243 getEnabledLanguages_: function() { | |
| 244 assert(CrSettingsPrefs.isInitialized); | |
| 245 | |
| 246 var languageCodes = this.get(preferredLanguagesPath).split(','); | |
| 247 var enabledLanguages = []; | |
| 248 var spellCheckMap = this.getSpellCheckMap_(); | |
| 249 for (var i = 0; i < languageCodes.length; i++) { | |
| 250 var code = languageCodes[i]; | |
| 251 var language = this.supportedLanguageMap_[code]; | |
| 252 if (!language) | |
| 253 continue; | |
| 254 var state = {spellCheckEnabled: !!spellCheckMap[code]}; | |
| 255 enabledLanguages.push({language: language, state: state}); | |
| 256 } | |
| 257 return enabledLanguages; | |
| 258 }, | |
| 259 | |
| 260 /** | |
| 261 * Creates a map whose keys are languages enabled for spell check. | |
| 262 * @return {!Object<boolean>} | |
| 263 */ | |
| 264 getSpellCheckMap_: function() { | |
| 265 assert(CrSettingsPrefs.isInitialized); | |
| 266 | |
| 267 var spellCheckPref = /** @type {chrome.settingsPrivate.PrefObject} */( | |
| 268 this.get('prefs.spellcheck.dictionaries')); | |
| 269 var spellCheckCodes = spellCheckPref.value; | |
| 270 var spellCheckMap = {}; | |
| 271 for (var i = 0; i < spellCheckCodes.length; i++) | |
| 272 spellCheckMap[spellCheckCodes[i]] = true; | |
| 273 return spellCheckMap; | |
| 274 }, | |
| 275 | |
| 276 /** @private */ | |
| 277 preferredLanguagesPrefChanged_: function() { | |
| 278 if (!this.initialized_) | |
| 279 return; | |
| 280 | |
| 281 var enabledLanguages = this.getEnabledLanguages_(); | |
| 282 // Reset the enabled language map. Do this before notifying of the change | |
| 283 // via model.enabledLanguages. | |
| 284 this.enabledLanguageMap_ = {}; | |
| 285 for (var i = 0; i < enabledLanguages.length; i++) { | |
| 286 var languageInfo = enabledLanguages[i]; | |
| 287 this.enabledLanguageMap_[languageInfo.language.code] = languageInfo; | |
| 288 } | |
| 289 this.set('model.enabledLanguages', enabledLanguages); | |
| 290 }, | |
| 291 | |
| 292 /** | |
| 293 * Updates the spellCheckEnabled state of each enabled language. | |
| 294 * @private | |
| 295 */ | |
| 296 spellCheckDictionariesPrefChanged_: function() { | |
| 297 if (!this.initialized_) | |
| 298 return; | |
| 299 | |
| 300 var spellCheckMap = this.getSpellCheckMap_(); | |
| 301 for (var i = 0; i < this.model.enabledLanguages.length; i++) { | |
| 302 this.set('model.enabledLanguages.' + i + '.state.spellCheckEnabled', | |
| 303 !!spellCheckMap[this.model.enabledLanguages[i].language.code]); | |
| 304 } | |
| 305 }, | |
| 306 | |
| 307 /** | |
| 308 * Windows and Chrome OS only: Sets the prospective UI language to the chosen | |
| 309 * language. This dosen't affect the actual UI language until a restart. | |
| 310 * @param {string} languageCode | |
| 311 */ | |
| 312 setUILanguage: function(languageCode) { | |
| 313 // Set the prospective UI language. This won't take effect until a restart. | |
| 314 chrome.send('setUILanguage', [languageCode]); | |
| 315 }, | |
| 316 | |
| 317 /** | |
| 318 * Enables the language, making it available for spell check and input. | |
| 319 * @param {string} languageCode | |
| 320 */ | |
| 321 enableLanguage: function(languageCode) { | |
| 322 if (!CrSettingsPrefs.isInitialized) | |
| 323 return; | |
| 324 | |
| 325 var languageCodes = this.get(preferredLanguagesPath); | |
| 326 var index = languageCodes.split(',').indexOf(languageCode); | |
| 327 if (index > -1) | |
| 328 return; | |
| 329 this.set(preferredLanguagesPath, languageCodes + ',' + languageCode); | |
| 330 }, | |
| 331 | |
| 332 /** | |
| 333 * Disables the language. | |
| 334 * @param {string} languageCode | |
| 335 */ | |
| 336 disableLanguage: function(languageCode) { | |
| 337 if (!CrSettingsPrefs.isInitialized) | |
| 338 return; | |
| 339 | |
| 340 // Don't disable the UI language. | |
| 341 var appLocale = this.get('prefs.intl.app_locale.value') || | |
| 342 navigator.language; | |
| 343 if (languageCode == appLocale) | |
| 344 return; | |
| 345 | |
| 346 var languageCodes = this.get(preferredLanguagesPath).split(','); | |
| 347 // Don't disable the only enabled language. | |
| 348 if (languageCodes.length == 1) | |
| 349 return; | |
| 350 | |
| 351 // Remove the language from spell check. | |
| 352 var spellCheckIndex = | |
| 353 this.get('prefs.spellcheck.dictionaries.value').indexOf(languageCode); | |
| 354 if (spellCheckIndex != -1) | |
| 355 this.splice('prefs.spellcheck.dictionaries.value', spellCheckIndex, 1); | |
| 356 | |
| 357 var languageIndex = languageCodes.indexOf(languageCode); | |
| 358 if (languageIndex == -1) | |
| 359 return; | |
| 360 languageCodes.splice(languageIndex, 1); | |
| 361 this.set(preferredLanguagesPath, languageCodes.join(',')); | |
| 362 }, | |
| 363 | |
| 364 /** | |
| 365 * @param {string} languageCode | |
| 366 * @return {boolean} True if the language is enabled. | |
| 367 */ | |
| 368 isEnabled: function(languageCode) { | |
| 369 return !!this.enabledLanguageMap_[languageCode]; | |
| 370 }, | |
| 371 | |
| 372 /** | |
| 373 * Enables or disables spell check for the given language. | |
| 374 * @param {string} languageCode | |
| 375 * @param {boolean} enable | |
| 376 */ | |
| 377 toggleSpellCheck: function(languageCode, enable) { | |
| 378 if (!this.initialized_) | |
| 379 return; | |
| 380 | |
| 381 var spellCheckPref = /** @type {chrome.settingsPrivate.PrefObject} */( | |
| 382 this.get('prefs.spellcheck.dictionaries')); | |
| 383 if (enable) { | |
| 384 if (spellCheckPref.value.indexOf(languageCode) == -1) | |
| 385 this.push('prefs.spellcheck.dictionaries.value', languageCode); | |
| 386 } else { | |
| 387 // TODO: need update externs before committing | |
| 388 this.arrayDelete('prefs.spellcheck.dictionaries.value', languageCode); | |
| 389 } | |
| 390 }, | |
| 391 }); | |
| 392 })(); | |
| OLD | NEW |