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.
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 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
12 * Bluetooth settings constants.
14 function Constants() {}
17 * Creates a new bluetooth list item.
18 * @param {{name: string,
22 * connecting: boolean,
23 * connectable: boolean,
24 * pairing: string|undefined,
25 * passkey: number|undefined,
26 * pincode: string|undefined,
27 * entered: number|undefined}} device
28 * Description of the Bluetooth device.
30 * @extends {options.DeletableItem}
32 function BluetoothListItem(device) {
33 var el = cr.doc.createElement('div');
34 el.__proto__ = BluetoothListItem.prototype;
36 for (var key in device)
37 el.data[key] = device[key];
39 // Only show the close button for paired devices, but not for connecting
41 el.deletable = device.paired && !device.connecting;
45 BluetoothListItem.prototype = {
46 __proto__: DeletableItem.prototype,
49 * Description of the Bluetooth device.
50 * @type {{name: string,
54 * connecting: boolean,
55 * connectable: boolean,
56 * pairing: string|undefined,
57 * passkey: number|undefined,
58 * pincode: string|undefined,
59 * entered: number|undefined}}
64 decorate: function() {
65 DeletableItem.prototype.decorate.call(this);
66 var label = this.ownerDocument.createElement('div');
67 label.className = 'bluetooth-device-label';
68 this.classList.add('bluetooth-device');
69 // There are four kinds of devices we want to distinguish:
70 // * Connecting devices: in bold with a "connecting" label,
71 // * Connected devices: in bold,
72 // * Paired, not connected but connectable devices: regular and
73 // * Paired, not connected and not connectable devices: grayed out.
74 this.connected = this.data.connecting ||
75 (this.data.paired && this.data.connected);
76 this.notconnectable = this.data.paired && !this.data.connecting &&
77 !this.data.connected && !this.data.connectable;
78 // "paired" devices are those that are remembered but not connected.
79 this.paired = this.data.paired && !this.data.connected &&
80 this.data.connectable;
82 var content = this.data.name;
83 // Update the device's label according to its state. A "connecting" device
84 // can be in the process of connecting and pairing, so we check connecting
86 if (this.data.connecting) {
87 content = loadTimeData.getStringF('bluetoothDeviceConnecting',
90 label.textContent = content;
91 this.contentElement.appendChild(label);
96 * Class for displaying a list of Bluetooth devices.
98 * @extends {options.DeletableItemList}
100 var BluetoothDeviceList = cr.ui.define('list');
102 BluetoothDeviceList.prototype = {
103 __proto__: DeletableItemList.prototype,
106 * Height of a list entry in px.
113 * Width of a list entry in px.
120 decorate: function() {
121 DeletableItemList.prototype.decorate.call(this);
122 // Force layout of all items even if not in the viewport to address
123 // errors in scroll positioning when the list is hidden during initial
124 // layout. The impact on performance should be minimal given that the
125 // list is not expected to grow very large. Fixed height items are also
126 // required to avoid caching incorrect sizes during layout of a hidden
128 this.autoExpands = true;
129 this.fixedHeight = true;
131 this.selectionModel = new ListSingleSelectionModel();
135 * Adds a bluetooth device to the list of available devices. A check is
136 * made to see if the device is already in the list, in which case the
137 * existing device is updated.
138 * @param {{name: string,
141 * connected: boolean,
142 * connecting: boolean,
143 * connectable: boolean,
144 * pairing: string|undefined,
145 * passkey: number|undefined,
146 * pincode: string|undefined,
147 * entered: number|undefined}} device
148 * Description of the bluetooth device.
149 * @return {boolean} True if the devies was successfully added or updated.
151 appendDevice: function(device) {
152 var selectedDevice = this.getSelectedDevice_();
153 var index = this.find(device.address);
154 if (index == undefined) {
155 this.dataModel.push(device);
158 this.dataModel.splice(index, 1, device);
159 this.redrawItem(index);
161 this.updateListVisibility_();
163 this.setSelectedDevice_(selectedDevice);
168 * Forces a revailidation of the list content. Deleting a single item from
169 * the list results in a stale cache requiring an invalidation.
170 * @param {string=} opt_selection Optional address of device to select
171 * after refreshing the list.
173 refresh: function(opt_selection) {
174 // TODO(kevers): Investigate if the stale cache issue can be fixed in
176 var selectedDevice = opt_selection ? opt_selection :
177 this.getSelectedDevice_();
181 this.setSelectedDevice_(selectedDevice);
185 * Retrieves the address of the selected device, or null if no device is
187 * @return {?string} Address of selected device or null.
190 getSelectedDevice_: function() {
191 var selection = this.selectedItem;
193 return selection.address;
198 * Selects the device with the matching address.
199 * @param {string} address The unique address of the device.
202 setSelectedDevice_: function(address) {
203 var index = this.find(address);
204 if (index != undefined)
205 this.selectionModel.selectRange(index, index);
209 * Perges all devices from the list.
212 this.dataModel = new ArrayDataModel([]);
214 this.updateListVisibility_();
218 * Returns the index of the list entry with the matching address.
219 * @param {string} address Unique address of the Bluetooth device.
220 * @return {number|undefined} Index of the matching entry or
221 * undefined if no match found.
223 find: function(address) {
224 var size = this.dataModel.length;
225 for (var i = 0; i < size; i++) {
226 var entry = this.dataModel.item(i);
227 if (entry.address == address)
233 createItem: function(entry) {
234 return new BluetoothListItem(entry);
238 * Overrides the default implementation, which is used to compute the
239 * size of an element in the list. The default implementation relies
240 * on adding a placeholder item to the list and fetching its size and
241 * position. This strategy does not work if an item is added to the list
242 * while it is hidden, as the computed metrics will all be zero in that
244 * @return {{height: number, marginTop: number, marginBottom: number,
245 * width: number, marginLeft: number, marginRight: number}}
246 * The height and width of the item, taking margins into account,
247 * and the margins themselves.
249 measureItem: function() {
251 height: this.itemHeight_,
254 width: this.itemWidth_,
261 * Override the default implementation to return a predetermined size,
262 * which in turns allows proper layout of items even if the list is hidden.
263 * @return {height: number, width: number} Dimensions of a single item in
264 * the list of bluetooth device.
267 getDefaultItemSize_: function() {
269 height: this.itemHeight_,
270 width: this.itemWidth_
275 * Override base implementation of handleClick_, which unconditionally
276 * removes the item. In this case, removal of the element is deferred
277 * pending confirmation from the Bluetooth adapter.
278 * @param {Event} e The click event object.
281 handleClick_: function(e) {
285 var target = e.target;
286 if (!target.classList.contains('row-delete-button'))
289 var item = this.getListItemAncestor(target);
290 var selected = this.selectionModel.selectedIndex;
291 var index = this.getIndexOfListItem(item);
292 if (item && item.deletable) {
293 if (selected != index)
294 this.setSelectedDevice_(item.data.address);
295 // Device is busy until we hear back from the Bluetooth adapter.
296 // Prevent double removal request.
297 item.deletable = false;
298 // TODO(kevers): Provide visual feedback that the device is busy.
300 // Inform the bluetooth adapter that we are disconnecting or
301 // forgetting the device.
302 chrome.send('updateBluetoothDevice',
303 [item.data.address, item.connected ? 'disconnect' : 'forget']);
308 deleteItemAtIndex: function(index) {
309 var selectedDevice = this.getSelectedDevice_();
310 this.dataModel.splice(index, 1);
311 this.refresh(selectedDevice);
312 this.updateListVisibility_();
316 * If the list has an associated empty list placholder then update the
317 * visibility of the list and placeholder.
320 updateListVisibility_: function() {
321 var empty = this.dataModel.length == 0;
322 var listPlaceHolderID = this.id + '-empty-placeholder';
323 if ($(listPlaceHolderID)) {
324 if (this.hidden != empty) {
326 $(listPlaceHolderID).hidden = !empty;
333 cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR);
335 cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR);
337 cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
339 cr.defineProperty(BluetoothListItem, 'notconnectable',
340 cr.PropertyKind.BOOL_ATTR);
343 BluetoothListItem: BluetoothListItem,
344 BluetoothDeviceList: BluetoothDeviceList,