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.
6 * @typedef {{name: string,
10 * connecting: boolean,
11 * connectable: boolean,
12 * pairing: (string|undefined),
13 * passkey: (number|undefined),
14 * pincode: (string|undefined),
15 * entered: (number|undefined)}}
19 cr.define('options.system.bluetooth', function() {
20 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
21 /** @const */ var DeletableItem = options.DeletableItem;
22 /** @const */ var DeletableItemList = options.DeletableItemList;
23 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
26 * Bluetooth settings constants.
28 function Constants() {}
31 * Creates a new bluetooth list item.
32 * @param {BluetoothDevice} device Description of the Bluetooth device.
34 * @extends {options.DeletableItem}
36 function BluetoothListItem(device) {
37 var el = cr.doc.createElement('div');
38 el.__proto__ = BluetoothListItem.prototype;
40 for (var key in device)
41 el.data[key] = device[key];
43 // Only show the close button for paired devices, but not for connecting
45 el.deletable = device.paired && !device.connecting;
49 BluetoothListItem.prototype = {
50 __proto__: DeletableItem.prototype,
53 * Description of the Bluetooth device.
54 * @type {?BluetoothDevice}
59 decorate: function() {
60 DeletableItem.prototype.decorate.call(this);
61 var label = this.ownerDocument.createElement('div');
62 label.className = 'bluetooth-device-label';
63 this.classList.add('bluetooth-device');
64 // There are four kinds of devices we want to distinguish:
65 // * Connecting devices: in bold with a "connecting" label,
66 // * Connected devices: in bold,
67 // * Paired, not connected but connectable devices: regular and
68 // * Paired, not connected and not connectable devices: grayed out.
69 this.connected = this.data.connecting ||
70 (this.data.paired && this.data.connected);
71 this.notconnectable = this.data.paired && !this.data.connecting &&
72 !this.data.connected && !this.data.connectable;
73 // "paired" devices are those that are remembered but not connected.
74 this.paired = this.data.paired && !this.data.connected &&
75 this.data.connectable;
77 var content = this.data.name;
78 // Update the device's label according to its state. A "connecting" device
79 // can be in the process of connecting and pairing, so we check connecting
81 if (this.data.connecting) {
82 content = loadTimeData.getStringF('bluetoothDeviceConnecting',
85 label.textContent = content;
86 this.contentElement.appendChild(label);
91 * Class for displaying a list of Bluetooth devices.
93 * @extends {options.DeletableItemList}
95 var BluetoothDeviceList = cr.ui.define('list');
97 BluetoothDeviceList.prototype = {
98 __proto__: DeletableItemList.prototype,
101 * Height of a list entry in px.
108 * Width of a list entry in px.
115 decorate: function() {
116 DeletableItemList.prototype.decorate.call(this);
117 // Force layout of all items even if not in the viewport to address
118 // errors in scroll positioning when the list is hidden during initial
119 // layout. The impact on performance should be minimal given that the
120 // list is not expected to grow very large. Fixed height items are also
121 // required to avoid caching incorrect sizes during layout of a hidden
123 this.autoExpands = true;
124 this.fixedHeight = true;
126 this.selectionModel = new ListSingleSelectionModel();
130 * Adds a bluetooth device to the list of available devices. A check is
131 * made to see if the device is already in the list, in which case the
132 * existing device is updated.
133 * @param {{name: string,
136 * connected: boolean,
137 * connecting: boolean,
138 * connectable: boolean,
139 * pairing: (string|undefined),
140 * passkey: (number|undefined),
141 * pincode: (string|undefined),
142 * entered: (number|undefined)}} device
143 * Description of the bluetooth device.
144 * @return {boolean} True if the devies was successfully added or updated.
146 appendDevice: function(device) {
147 var selectedDevice = this.getSelectedDevice_();
148 var index = this.find(device.address);
149 if (index == undefined) {
150 this.dataModel.push(device);
153 this.dataModel.splice(index, 1, device);
154 this.redrawItem(index);
156 this.updateListVisibility_();
158 this.setSelectedDevice_(selectedDevice);
163 * Forces a revailidation of the list content. Deleting a single item from
164 * the list results in a stale cache requiring an invalidation.
165 * @param {string=} opt_selection Optional address of device to select
166 * after refreshing the list.
168 refresh: function(opt_selection) {
169 // TODO(kevers): Investigate if the stale cache issue can be fixed in
171 var selectedDevice = opt_selection ? opt_selection :
172 this.getSelectedDevice_();
176 this.setSelectedDevice_(selectedDevice);
180 * Retrieves the address of the selected device, or null if no device is
182 * @return {(string|undefined)} Address of selected device or null.
185 getSelectedDevice_: function() {
186 var selection = this.selectedItem;
188 return selection.address;
193 * Selects the device with the matching address.
194 * @param {string} address The unique address of the device.
197 setSelectedDevice_: function(address) {
198 var index = this.find(address);
199 if (index != undefined)
200 this.selectionModel.selectRange(index, index);
204 * Perges all devices from the list.
207 this.dataModel = new ArrayDataModel([]);
209 this.updateListVisibility_();
213 * Returns the index of the list entry with the matching address.
214 * @param {string} address Unique address of the Bluetooth device.
215 * @return {number|undefined} Index of the matching entry or
216 * undefined if no match found.
218 find: function(address) {
219 var size = this.dataModel.length;
220 for (var i = 0; i < size; i++) {
221 var entry = this.dataModel.item(i);
222 if (entry.address == address)
229 * @param {BluetoothDevice} entry
231 createItem: function(entry) {
232 return new BluetoothListItem(entry);
236 * Overrides the default implementation, which is used to compute the
237 * size of an element in the list. The default implementation relies
238 * on adding a placeholder item to the list and fetching its size and
239 * position. This strategy does not work if an item is added to the list
240 * while it is hidden, as the computed metrics will all be zero in that
242 * @return {{height: number, marginTop: number, marginBottom: number,
243 * width: number, marginLeft: number, marginRight: number}}
244 * The height and width of the item, taking margins into account,
245 * and the margins themselves.
247 measureItem: function() {
249 height: this.itemHeight_,
252 width: this.itemWidth_,
259 * Override the default implementation to return a predetermined size,
260 * which in turns allows proper layout of items even if the list is hidden.
261 * @return {{height: number, width: number}} Dimensions of a single item in
262 * the list of bluetooth device.
265 getDefaultItemSize_: function() {
267 height: this.itemHeight_,
268 width: this.itemWidth_
273 * Override base implementation of handleClick, which unconditionally
274 * removes the item. In this case, removal of the element is deferred
275 * pending confirmation from the Bluetooth adapter.
276 * @param {Event} e The click event object.
279 handleClick: function(e) {
283 var target = /** @type {HTMLElement} */(e.target);
284 if (!target.classList.contains('row-delete-button'))
287 var item = this.getListItemAncestor(target);
288 var selected = this.selectionModel.selectedIndex;
289 var index = this.getIndexOfListItem(item);
290 if (item && item.deletable) {
291 if (selected != index)
292 this.setSelectedDevice_(item.data.address);
293 // Device is busy until we hear back from the Bluetooth adapter.
294 // Prevent double removal request.
295 item.deletable = false;
296 // TODO(kevers): Provide visual feedback that the device is busy.
298 // Inform the bluetooth adapter that we are disconnecting or
299 // forgetting the device.
300 chrome.send('updateBluetoothDevice',
301 [item.data.address, item.connected ? 'disconnect' : 'forget']);
303 chrome.send('coreOptionsUserMetricsAction',
304 ['Options_BluetoothRemoveDevice']);
309 deleteItemAtIndex: function(index) {
310 var selectedDevice = this.getSelectedDevice_();
311 this.dataModel.splice(index, 1);
312 this.refresh(selectedDevice);
313 this.updateListVisibility_();
317 * If the list has an associated empty list placholder then update the
318 * visibility of the list and placeholder.
321 updateListVisibility_: function() {
322 var empty = this.dataModel.length == 0;
323 var listPlaceHolderID = this.id + '-empty-placeholder';
324 if ($(listPlaceHolderID)) {
325 if (this.hidden != empty) {
327 $(listPlaceHolderID).hidden = !empty;
334 cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR);
336 cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR);
338 cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
340 cr.defineProperty(BluetoothListItem, 'notconnectable',
341 cr.PropertyKind.BOOL_ATTR);
344 BluetoothListItem: BluetoothListItem,
345 BluetoothDeviceList: BluetoothDeviceList,