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 |