| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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 cr.define('options.language', function() { | |
| 6 const ArrayDataModel = cr.ui.ArrayDataModel; | |
| 7 const LanguageOptions = options.LanguageOptions; | |
| 8 const List = cr.ui.List; | |
| 9 const ListItem = cr.ui.ListItem; | |
| 10 const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; | |
| 11 | |
| 12 /** | |
| 13 * Creates a new language list. | |
| 14 * @param {Object=} opt_propertyBag Optional properties. | |
| 15 * @constructor | |
| 16 * @extends {cr.ui.List} | |
| 17 */ | |
| 18 var LanguageList = cr.ui.define('list'); | |
| 19 | |
| 20 /** | |
| 21 * Gets display name from the given language code. | |
| 22 * @param {string} languageCode Language code (ex. "fr"). | |
| 23 */ | |
| 24 LanguageList.getDisplayNameFromLanguageCode = function(languageCode) { | |
| 25 // Build the language code to display name dictionary at first time. | |
| 26 if (!this.languageCodeToDisplayName_) { | |
| 27 this.languageCodeToDisplayName_ = {}; | |
| 28 var languageList = templateData.languageList; | |
| 29 for (var i = 0; i < languageList.length; i++) { | |
| 30 var language = languageList[i]; | |
| 31 this.languageCodeToDisplayName_[language.code] = language.displayName; | |
| 32 } | |
| 33 } | |
| 34 | |
| 35 return this.languageCodeToDisplayName_[languageCode]; | |
| 36 } | |
| 37 | |
| 38 /** | |
| 39 * Gets native display name from the given language code. | |
| 40 * @param {string} languageCode Language code (ex. "fr"). | |
| 41 */ | |
| 42 LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) { | |
| 43 // Build the language code to display name dictionary at first time. | |
| 44 if (!this.languageCodeToNativeDisplayName_) { | |
| 45 this.languageCodeToNativeDisplayName_ = {}; | |
| 46 var languageList = templateData.languageList; | |
| 47 for (var i = 0; i < languageList.length; i++) { | |
| 48 var language = languageList[i]; | |
| 49 this.languageCodeToNativeDisplayName_[language.code] = | |
| 50 language.nativeDisplayName; | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 return this.languageCodeToNativeDisplayName_[languageCode]; | |
| 55 } | |
| 56 | |
| 57 /** | |
| 58 * Returns true if the given language code is valid. | |
| 59 * @param {string} languageCode Language code (ex. "fr"). | |
| 60 */ | |
| 61 LanguageList.isValidLanguageCode = function(languageCode) { | |
| 62 // Having the display name for the language code means that the | |
| 63 // language code is valid. | |
| 64 if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) { | |
| 65 return true; | |
| 66 } | |
| 67 return false; | |
| 68 } | |
| 69 | |
| 70 LanguageList.prototype = { | |
| 71 __proto__: List.prototype, | |
| 72 | |
| 73 // The list item being dragged. | |
| 74 draggedItem: null, | |
| 75 // The drop position information: "below" or "above". | |
| 76 dropPos: null, | |
| 77 // The preference is a CSV string that describes preferred languages | |
| 78 // in Chrome OS. The language list is used for showing the language | |
| 79 // list in "Language and Input" options page. | |
| 80 preferredLanguagesPref: 'settings.language.preferred_languages', | |
| 81 // The preference is a CSV string that describes accept languages used | |
| 82 // for content negotiation. To be more precise, the list will be used | |
| 83 // in "Accept-Language" header in HTTP requests. | |
| 84 acceptLanguagesPref: 'intl.accept_languages', | |
| 85 | |
| 86 /** @inheritDoc */ | |
| 87 decorate: function() { | |
| 88 List.prototype.decorate.call(this); | |
| 89 this.selectionModel = new ListSingleSelectionModel; | |
| 90 | |
| 91 // HACK(arv): http://crbug.com/40902 | |
| 92 window.addEventListener('resize', this.redraw.bind(this)); | |
| 93 | |
| 94 // Listen to pref change. | |
| 95 Preferences.getInstance().addEventListener(this.preferredLanguagesPref, | |
| 96 this.handlePreferredLanguagesPrefChange_.bind(this)); | |
| 97 | |
| 98 // Listen to drag and drop events. | |
| 99 this.addEventListener('dragstart', this.handleDragStart_.bind(this)); | |
| 100 this.addEventListener('dragenter', this.handleDragEnter_.bind(this)); | |
| 101 this.addEventListener('dragover', this.handleDragOver_.bind(this)); | |
| 102 this.addEventListener('drop', this.handleDrop_.bind(this)); | |
| 103 }, | |
| 104 | |
| 105 createItem: function(languageCode) { | |
| 106 var languageDisplayName = | |
| 107 LanguageList.getDisplayNameFromLanguageCode(languageCode); | |
| 108 var languageNativeDisplayName = | |
| 109 LanguageList.getNativeDisplayNameFromLanguageCode(languageCode); | |
| 110 return new ListItem({ | |
| 111 label: languageDisplayName, | |
| 112 draggable: true, | |
| 113 languageCode: languageCode, | |
| 114 title: languageNativeDisplayName // Show native name as tooltip. | |
| 115 }); | |
| 116 }, | |
| 117 | |
| 118 /* | |
| 119 * Adds a language to the language list. | |
| 120 * @param {string} languageCode language code (ex. "fr"). | |
| 121 */ | |
| 122 addLanguage: function(languageCode) { | |
| 123 // It shouldn't happen but ignore the language code if it's | |
| 124 // null/undefined, or already present. | |
| 125 if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) { | |
| 126 return; | |
| 127 } | |
| 128 this.dataModel.push(languageCode); | |
| 129 // Select the last item, which is the language added. | |
| 130 this.selectionModel.selectedIndex = this.dataModel.length - 1; | |
| 131 | |
| 132 this.savePreference_(); | |
| 133 }, | |
| 134 | |
| 135 /* | |
| 136 * Gets the language codes of the currently listed languages. | |
| 137 */ | |
| 138 getLanguageCodes: function() { | |
| 139 return this.dataModel.slice(); | |
| 140 }, | |
| 141 | |
| 142 /* | |
| 143 * Gets the language code of the selected language. | |
| 144 */ | |
| 145 getSelectedLanguageCode: function() { | |
| 146 return this.selectedItem; | |
| 147 }, | |
| 148 | |
| 149 /* | |
| 150 * Selects the language by the given language code. | |
| 151 * @returns {boolean} True if the operation is successful. | |
| 152 */ | |
| 153 selectLanguageByCode: function(languageCode) { | |
| 154 var index = this.dataModel.indexOf(languageCode); | |
| 155 if (index >= 0) { | |
| 156 this.selectionModel.selectedIndex = index; | |
| 157 return true; | |
| 158 } | |
| 159 return false; | |
| 160 }, | |
| 161 | |
| 162 /* | |
| 163 * Removes the currently selected language. | |
| 164 */ | |
| 165 removeSelectedLanguage: function() { | |
| 166 if (this.selectionModel.selectedIndex >= 0) { | |
| 167 this.dataModel.splice(this.selectionModel.selectedIndex, 1); | |
| 168 // Once the selected item is removed, there will be no selected item. | |
| 169 // Select the item pointed by the lead index. | |
| 170 this.selectionModel.selectedIndex = this.selectionModel.leadIndex; | |
| 171 this.savePreference_(); | |
| 172 } | |
| 173 }, | |
| 174 | |
| 175 /* | |
| 176 * Handles the dragstart event. | |
| 177 * @param {Event} e The dragstart event. | |
| 178 * @private | |
| 179 */ | |
| 180 handleDragStart_: function(e) { | |
| 181 var target = e.target; | |
| 182 // ListItem should be the only draggable element type in the page, | |
| 183 // but just in case. | |
| 184 if (target instanceof ListItem) { | |
| 185 this.draggedItem = target; | |
| 186 e.dataTransfer.effectAllowed = 'move'; | |
| 187 } | |
| 188 }, | |
| 189 | |
| 190 /* | |
| 191 * Handles the dragenter event. | |
| 192 * @param {Event} e The dragenter event. | |
| 193 * @private | |
| 194 */ | |
| 195 handleDragEnter_: function(e) { | |
| 196 e.preventDefault(); | |
| 197 }, | |
| 198 | |
| 199 /* | |
| 200 * Handles the dragover event. | |
| 201 * @param {Event} e The dragover event. | |
| 202 * @private | |
| 203 */ | |
| 204 handleDragOver_: function(e) { | |
| 205 var dropTarget = e.target; | |
| 206 // Determins whether the drop target is to accept the drop. | |
| 207 // The drop is only successful on another ListItem. | |
| 208 if (!(dropTarget instanceof ListItem) || | |
| 209 dropTarget == this.draggedItem) { | |
| 210 return; | |
| 211 } | |
| 212 // Compute the drop postion. Should we move the dragged item to | |
| 213 // below or above the drop target? | |
| 214 var rect = dropTarget.getBoundingClientRect(); | |
| 215 var dy = e.clientY - rect.top; | |
| 216 var yRatio = dy / rect.height; | |
| 217 var dropPos = yRatio <= .5 ? 'above' : 'below'; | |
| 218 this.dropPos = dropPos; | |
| 219 e.preventDefault(); | |
| 220 // TODO(satorux): Show the drop marker just like the bookmark manager. | |
| 221 }, | |
| 222 | |
| 223 /* | |
| 224 * Handles the drop event. | |
| 225 * @param {Event} e The drop event. | |
| 226 * @private | |
| 227 */ | |
| 228 handleDrop_: function(e) { | |
| 229 var dropTarget = e.target; | |
| 230 | |
| 231 // Delete the language from the original position. | |
| 232 var languageCode = this.draggedItem.languageCode; | |
| 233 var originalIndex = this.dataModel.indexOf(languageCode); | |
| 234 this.dataModel.splice(originalIndex, 1); | |
| 235 // Insert the language to the new position. | |
| 236 var newIndex = this.dataModel.indexOf(dropTarget.languageCode); | |
| 237 if (this.dropPos == 'below') | |
| 238 newIndex += 1; | |
| 239 this.dataModel.splice(newIndex, 0, languageCode); | |
| 240 // The cursor should move to the moved item. | |
| 241 this.selectionModel.selectedIndex = newIndex; | |
| 242 // Save the preference. | |
| 243 this.savePreference_(); | |
| 244 }, | |
| 245 | |
| 246 /** | |
| 247 * Handles preferred languages pref change. | |
| 248 * @param {Event} e The change event object. | |
| 249 * @private | |
| 250 */ | |
| 251 handlePreferredLanguagesPrefChange_: function(e) { | |
| 252 var languageCodesInCsv = e.value.value; | |
| 253 var languageCodes = this.filterBadLanguageCodes_( | |
| 254 languageCodesInCsv.split(',')); | |
| 255 this.load_(languageCodes); | |
| 256 }, | |
| 257 | |
| 258 /** | |
| 259 * Loads given language list. | |
| 260 * @param {Array} languageCodes List of language codes. | |
| 261 * @private | |
| 262 */ | |
| 263 load_: function(languageCodes) { | |
| 264 // Preserve the original selected index. See comments below. | |
| 265 var originalSelectedIndex = (this.selectionModel ? | |
| 266 this.selectionModel.selectedIndex : -1); | |
| 267 this.dataModel = new ArrayDataModel(languageCodes); | |
| 268 if (originalSelectedIndex >= 0 && | |
| 269 originalSelectedIndex < this.dataModel.length) { | |
| 270 // Restore the original selected index if the selected index is | |
| 271 // valid after the data model is loaded. This is neeeded to keep | |
| 272 // the selected language after the languge is added or removed. | |
| 273 this.selectionModel.selectedIndex = originalSelectedIndex; | |
| 274 // The lead index should be updated too. | |
| 275 this.selectionModel.leadIndex = originalSelectedIndex; | |
| 276 } else if (this.dataModel.length > 0){ | |
| 277 // Otherwise, select the first item if it's not empty. | |
| 278 // Note that ListSingleSelectionModel won't select an item | |
| 279 // automatically, hence we manually select the first item here. | |
| 280 this.selectionModel.selectedIndex = 0; | |
| 281 } | |
| 282 }, | |
| 283 | |
| 284 /** | |
| 285 * Saves the preference. | |
| 286 */ | |
| 287 savePreference_: function() { | |
| 288 // Encode the language codes into a CSV string. | |
| 289 Preferences.setStringPref(this.preferredLanguagesPref, | |
| 290 this.dataModel.slice().join(',')); | |
| 291 // Save the same language list as accept languages preference as | |
| 292 // well, but we need to expand the language list, to make it more | |
| 293 // acceptable. For instance, some web sites don't understand 'en-US' | |
| 294 // but 'en'. See crosbug.com/9884. | |
| 295 var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice()); | |
| 296 Preferences.setStringPref(this.acceptLanguagesPref, | |
| 297 acceptLanguages.join(',')); | |
| 298 cr.dispatchSimpleEvent(this, 'save'); | |
| 299 }, | |
| 300 | |
| 301 /** | |
| 302 * Expands language codes to make these more suitable for Accept-Language. | |
| 303 * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA']. | |
| 304 * 'en' won't appear twice as this function eliminates duplicates. | |
| 305 * @param {Array} languageCodes List of language codes. | |
| 306 * @private | |
| 307 */ | |
| 308 expandLanguageCodes: function(languageCodes) { | |
| 309 var expandedLanguageCodes = []; | |
| 310 var seen = {}; // Used to eliminiate duplicates. | |
| 311 for (var i = 0; i < languageCodes.length; i++) { | |
| 312 var languageCode = languageCodes[i]; | |
| 313 if (!(languageCode in seen)) { | |
| 314 expandedLanguageCodes.push(languageCode); | |
| 315 seen[languageCode] = true; | |
| 316 } | |
| 317 var parts = languageCode.split('-'); | |
| 318 if (!(parts[0] in seen)) { | |
| 319 expandedLanguageCodes.push(parts[0]); | |
| 320 seen[parts[0]] = true; | |
| 321 } | |
| 322 } | |
| 323 return expandedLanguageCodes; | |
| 324 }, | |
| 325 | |
| 326 /** | |
| 327 * Filters bad language codes in case bad language codes are | |
| 328 * stored in the preference. Removes duplicates as well. | |
| 329 * @param {Array} languageCodes List of language codes. | |
| 330 * @private | |
| 331 */ | |
| 332 filterBadLanguageCodes_: function(languageCodes) { | |
| 333 var filteredLanguageCodes = []; | |
| 334 var seen = {}; | |
| 335 for (var i = 0; i < languageCodes.length; i++) { | |
| 336 // Check if the the language code is valid, and not | |
| 337 // duplicate. Otherwise, skip it. | |
| 338 if (LanguageList.isValidLanguageCode(languageCodes[i]) && | |
| 339 !(languageCodes[i] in seen)) { | |
| 340 filteredLanguageCodes.push(languageCodes[i]); | |
| 341 seen[languageCodes[i]] = true; | |
| 342 } | |
| 343 } | |
| 344 return filteredLanguageCodes; | |
| 345 }, | |
| 346 }; | |
| 347 | |
| 348 return { | |
| 349 LanguageList: LanguageList | |
| 350 }; | |
| 351 }); | |
| OLD | NEW |