OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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.system.bluetooth', function() { | |
6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; | |
7 /** @const */ var DeletableItem = options.DeletableItem; | |
8 /** @const */ var DeletableItemList = options.DeletableItemList; | |
9 | |
10 /** | |
11 * Bluetooth settings constants. | |
12 */ | |
13 function Constants() {} | |
14 | |
15 /** | |
16 * Creates a new bluetooth list item. | |
17 * @param {{name: string, | |
18 * address: string, | |
19 * paired: boolean, | |
20 * bonded: boolean, | |
21 * connected: boolean, | |
22 * pairing: string|undefined, | |
23 * passkey: number|undefined, | |
24 * entered: number|undefined}} device | |
25 * Description of the Bluetooth device. | |
26 * @constructor | |
27 * @extends {options.DeletableItem} | |
28 */ | |
29 function BluetoothListItem(device) { | |
30 var el = cr.doc.createElement('div'); | |
31 el.__proto__ = BluetoothListItem.prototype; | |
32 el.data = {}; | |
33 for (var key in device) | |
34 el.data[key] = device[key]; | |
35 el.decorate(); | |
36 // Only show the close button for paired devices. | |
37 el.deletable = device.paired; | |
38 return el; | |
39 } | |
40 | |
41 BluetoothListItem.prototype = { | |
42 __proto__: DeletableItem.prototype, | |
43 | |
44 /** | |
45 * Description of the Bluetooth device. | |
46 * @type {{name: string, | |
47 * address: string, | |
48 * paired: boolean, | |
49 * bonded: boolean, | |
50 * connected: boolean, | |
51 * pairing: string|undefined, | |
52 * passkey: number|undefined, | |
53 * entered: number|undefined}} | |
54 */ | |
55 data: null, | |
56 | |
57 /** @inheritDoc */ | |
58 decorate: function() { | |
59 DeletableItem.prototype.decorate.call(this); | |
60 var label = this.ownerDocument.createElement('div'); | |
61 label.className = 'bluetooth-device-label'; | |
62 this.classList.add('bluetooth-device'); | |
63 this.connected = this.data.connected; | |
64 // Though strictly speaking, a connected device will also be paired, we | |
65 // are interested in tracking paired devices that are not connected. | |
66 this.paired = this.data.paired && !this.data.connected; | |
67 this.connecting = !!this.data.pairing; | |
68 var content = this.data.name; | |
69 // Update label for devices that are paired but not connected. | |
70 if (this.paired) { | |
71 content = content + ' (' + | |
72 loadTimeData.getString('bluetoothDeviceNotConnected') + ')'; | |
73 } | |
74 label.textContent = content; | |
75 this.contentElement.appendChild(label); | |
76 }, | |
77 }; | |
78 | |
79 /** | |
80 * Class for displaying a list of Bluetooth devices. | |
81 * @constructor | |
82 * @extends {options.DeletableItemList} | |
83 */ | |
84 var BluetoothDeviceList = cr.ui.define('list'); | |
85 | |
86 BluetoothDeviceList.prototype = { | |
87 __proto__: DeletableItemList.prototype, | |
88 | |
89 /** | |
90 * Height of a list entry in px. | |
91 * @type {number} | |
92 * @private | |
93 */ | |
94 itemHeight_: 32, | |
95 | |
96 /** | |
97 * Width of a list entry in px. | |
98 * @type {number} | |
99 * @private. | |
100 */ | |
101 itemWidth_: 400, | |
102 | |
103 /** @inheritDoc */ | |
104 decorate: function() { | |
105 DeletableItemList.prototype.decorate.call(this); | |
106 // Force layout of all items even if not in the viewport to address | |
107 // calculation errors when the list is hidden. The impact on performance | |
108 // should be minimal given that the list is not expected to grow very | |
109 // large. | |
110 this.autoExpand = true; | |
111 this.addEventListener('blur', this.onBlur_); | |
112 this.clear(); | |
113 }, | |
114 | |
115 /** | |
116 * When the list loses focus, unselect all items in the list. | |
117 * @private | |
118 */ | |
119 onBlur_: function() { | |
120 // TODO(kevers): Should this be pushed up to the list class? | |
121 this.selectionModel.unselectAll(); | |
122 }, | |
123 | |
124 /** | |
125 * Adds a bluetooth device to the list of available devices. A check is | |
126 * made to see if the device is already in the list, in which case the | |
127 * existing device is updated. | |
128 * @param {{name: string, | |
129 * address: string, | |
130 * paired: boolean, | |
131 * bonded: boolean, | |
132 * connected: boolean, | |
133 * pairing: string|undefined, | |
134 * passkey: number|undefined, | |
135 * entered: number|undefined}} device | |
136 * Description of the bluetooth device. | |
137 * @return {boolean} True if the devies was successfully added or updated. | |
138 */ | |
139 appendDevice: function(device) { | |
140 var selectedDevice = this.getSelectedDevice_(); | |
141 var index = this.find(device.address); | |
142 if (index == undefined) { | |
143 this.dataModel.push(device); | |
144 this.redraw(); | |
145 } else { | |
146 this.dataModel.splice(index, 1, device); | |
147 this.redrawItem(index); | |
148 } | |
149 this.updateListVisibility_(); | |
150 if (selectedDevice) | |
151 this.setSelectedDevice_(selectedDevice); | |
152 return true; | |
153 }, | |
154 | |
155 /** | |
156 * Forces a revailidation of the list content. Content added while the list | |
157 * is hidden is not properly rendered when the list becomes visible. In | |
158 * addition, deleting a single item from the list results in a stale cache | |
159 * requiring an invalidation. | |
160 * @param {String=} opt_selection Optional address of device to select | |
161 * after refreshing the list. | |
162 */ | |
163 refresh: function(opt_selection) { | |
164 // TODO(kevers): Investigate if the root source of the problems can be | |
165 // fixed in cr.ui.list. | |
166 var selectedDevice = opt_selection ? opt_selection : | |
167 this.getSelectedDevice_(); | |
168 this.invalidate(); | |
169 this.redraw(); | |
170 if (selectedDevice) | |
171 this.setSelectedDevice_(selectedDevice); | |
172 }, | |
173 | |
174 /** | |
175 * Retrieves the address of the selected device, or null if no device is | |
176 * selected. | |
177 * @return {?String} Address of selected device or null. | |
178 * @private | |
179 */ | |
180 getSelectedDevice_: function() { | |
181 var selection = this.selectedItem; | |
182 if (selection) | |
183 return selection.address; | |
184 return null; | |
185 }, | |
186 | |
187 /** | |
188 * Selects the device with the matching address. | |
189 * @param {String} address The unique address of the device. | |
190 * @private | |
191 */ | |
192 setSelectedDevice_: function(address) { | |
193 var index = this.find(address); | |
194 if (index != undefined) | |
195 this.selectionModel.selectRange(index, index); | |
196 }, | |
197 | |
198 /** | |
199 * Perges all devices from the list. | |
200 */ | |
201 clear: function() { | |
202 this.dataModel = new ArrayDataModel([]); | |
203 this.redraw(); | |
204 this.updateListVisibility_(); | |
205 }, | |
206 | |
207 /** | |
208 * Returns the index of the list entry with the matching address. | |
209 * @param {string} address Unique address of the Bluetooth device. | |
210 * @return {number|undefined} Index of the matching entry or | |
211 * undefined if no match found. | |
212 */ | |
213 find: function(address) { | |
214 var size = this.dataModel.length; | |
215 for (var i = 0; i < size; i++) { | |
216 var entry = this.dataModel.item(i); | |
217 if (entry.address == address) | |
218 return i; | |
219 } | |
220 }, | |
221 | |
222 /** @inheritDoc */ | |
223 createItem: function(entry) { | |
224 return new BluetoothListItem(entry); | |
225 }, | |
226 | |
227 /** | |
228 * Overrides the default implementation, which is used to compute the | |
229 * size of an element in the list. The default implementation relies | |
230 * on adding a placeholder item to the list and fetching its size and | |
231 * position. This strategy does not work if an item is added to the list | |
232 * while it is hidden, as the computed metrics will all be zero in that | |
233 * case. | |
234 * @return {{height: number, marginTop: number, marginBottom: number, | |
235 * width: number, marginLeft: number, marginRight: number}} | |
236 * The height and width of the item, taking margins into account, | |
237 * and the margins themselves. | |
238 */ | |
239 measureItem: function() { | |
240 return { | |
241 height: this.itemHeight_, | |
242 marginTop: 0, | |
243 marginBotton: 0, | |
244 width: this.itemWidth_, | |
245 marginLeft: 0, | |
246 marginRight: 0 | |
247 }; | |
248 }, | |
249 | |
250 /** | |
251 * Override the default implementation to return a predetermined size, | |
252 * which in turns allows proper layout of items even if the list is hidden. | |
253 * @return {height: number, width: number} Dimensions of a single item in | |
254 * the list of bluetooth device. | |
255 * @private. | |
256 */ | |
257 getDefaultItemSize_: function() { | |
258 return { | |
259 height: this.itemHeight_, | |
260 width: this.itemWidth_ | |
261 }; | |
262 }, | |
263 | |
264 /** | |
265 * Override base implementation of handleClick_, which unconditionally | |
266 * removes the item. In this case, removal of the element is deferred | |
267 * pending confirmation from the Bluetooth adapter. | |
268 * @param {Event} e The click event object. | |
269 * @private | |
270 */ | |
271 handleClick_: function(e) { | |
272 if (this.disabled) | |
273 return; | |
274 | |
275 var target = e.target; | |
276 if (!target.classList.contains('row-delete-button')) | |
277 return; | |
278 | |
279 var listItem = this.getListItemAncestor(target); | |
280 var selected = this.selectionModel.selectedIndexes; | |
281 var index = this.getIndexOfListItem(listItem); | |
282 if (selected.indexOf(index) == -1) | |
283 selected = [index]; | |
284 for (var j = selected.length - 1; j >= 0; j--) { | |
285 var index = selected[j]; | |
286 var item = this.getListItemByIndex(index); | |
287 if (item && item.deletable) { | |
288 // Device is busy until we hear back from the Bluetooth adapter. | |
289 // Prevent double removal request. | |
290 item.deletable = false; | |
291 // TODO(kevers): Provide visual feedback that the device is busy. | |
292 | |
293 // Inform the bluetooth adapter that we are disconnecting or | |
294 // forgetting the device. | |
295 chrome.send('updateBluetoothDevice', | |
296 [item.data.address, item.connected ? 'disconnect' : 'forget']); | |
297 } | |
298 } | |
299 }, | |
300 | |
301 /** @inheritDoc */ | |
302 deleteItemAtIndex: function(index) { | |
303 var selectedDevice = this.getSelectedDevice_(); | |
304 this.dataModel.splice(index, 1); | |
305 this.refresh(selectedDevice); | |
306 this.updateListVisibility_(); | |
307 }, | |
308 | |
309 /** | |
310 * If the list has an associated empty list placholder then update the | |
311 * visibility of the list and placeholder. | |
312 * @private | |
313 */ | |
314 updateListVisibility_: function() { | |
315 var empty = this.dataModel.length == 0; | |
316 var listPlaceHolderID = this.id + '-empty-placeholder'; | |
317 if ($(listPlaceHolderID)) { | |
318 if (this.hidden != empty) { | |
319 this.hidden = empty; | |
320 $(listPlaceHolderID).hidden = !empty; | |
321 this.refresh(); | |
322 } | |
323 } | |
324 }, | |
325 }; | |
326 | |
327 cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR); | |
328 | |
329 cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR); | |
330 | |
331 cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR); | |
332 | |
333 return { | |
334 BluetoothListItem: BluetoothListItem, | |
335 BluetoothDeviceList: BluetoothDeviceList, | |
336 Constants: Constants | |
337 }; | |
338 }); | |
OLD | NEW |