blob: 57775715e740212793d488216f3e5df1b5ee8ae7 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.system.bluetooth', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/**
* Bluetooth settings constants.
*/
function Constants() {}
/**
* Creates a new bluetooth list item.
* @param {{name: string,
* address: string,
* paired: boolean,
* connected: boolean,
* connecting: boolean,
* connectable: boolean,
* pairing: string|undefined,
* passkey: number|undefined,
* pincode: string|undefined,
* entered: number|undefined}} device
* Description of the Bluetooth device.
* @constructor
* @extends {options.DeletableItem}
*/
function BluetoothListItem(device) {
var el = cr.doc.createElement('div');
el.__proto__ = BluetoothListItem.prototype;
el.data = {};
for (var key in device)
el.data[key] = device[key];
el.decorate();
// Only show the close button for paired devices, but not for connecting
// devices.
el.deletable = device.paired && !device.connecting;
return el;
}
BluetoothListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* Description of the Bluetooth device.
* @type {{name: string,
* address: string,
* paired: boolean,
* connected: boolean,
* connecting: boolean,
* connectable: boolean,
* pairing: string|undefined,
* passkey: number|undefined,
* pincode: string|undefined,
* entered: number|undefined}}
*/
data: null,
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
var label = this.ownerDocument.createElement('div');
label.className = 'bluetooth-device-label';
this.classList.add('bluetooth-device');
// There are four kinds of devices we want to distinguish:
// * Connecting devices: in bold with a "connecting" label,
// * Connected devices: in bold,
// * Paired, not connected but connectable devices: regular and
// * Paired, not connected and not connectable devices: grayed out.
this.connected = this.data.connecting ||
(this.data.paired && this.data.connected);
this.notconnectable = this.data.paired && !this.data.connecting &&
!this.data.connected && !this.data.connectable;
// "paired" devices are those that are remembered but not connected.
this.paired = this.data.paired && !this.data.connected &&
this.data.connectable;
var content = this.data.name;
// Update the device's label according to its state. A "connecting" device
// can be in the process of connecting and pairing, so we check connecting
// first.
if (this.data.connecting) {
content = loadTimeData.getStringF('bluetoothDeviceConnecting',
this.data.name);
}
label.textContent = content;
this.contentElement.appendChild(label);
},
};
/**
* Class for displaying a list of Bluetooth devices.
* @constructor
* @extends {options.DeletableItemList}
*/
var BluetoothDeviceList = cr.ui.define('list');
BluetoothDeviceList.prototype = {
__proto__: DeletableItemList.prototype,
/**
* Height of a list entry in px.
* @type {number}
* @private
*/
itemHeight_: 32,
/**
* Width of a list entry in px.
* @type {number}
* @private.
*/
itemWidth_: 400,
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
// Force layout of all items even if not in the viewport to address
// errors in scroll positioning when the list is hidden during initial
// layout. The impact on performance should be minimal given that the
// list is not expected to grow very large. Fixed height items are also
// required to avoid caching incorrect sizes during layout of a hidden
// list.
this.autoExpands = true;
this.fixedHeight = true;
this.clear();
},
/**
* Adds a bluetooth device to the list of available devices. A check is
* made to see if the device is already in the list, in which case the
* existing device is updated.
* @param {{name: string,
* address: string,
* paired: boolean,
* connected: boolean,
* connecting: boolean,
* connectable: boolean,
* pairing: string|undefined,
* passkey: number|undefined,
* pincode: string|undefined,
* entered: number|undefined}} device
* Description of the bluetooth device.
* @return {boolean} True if the devies was successfully added or updated.
*/
appendDevice: function(device) {
var selectedDevice = this.getSelectedDevice_();
var index = this.find(device.address);
if (index == undefined) {
this.dataModel.push(device);
this.redraw();
} else {
this.dataModel.splice(index, 1, device);
this.redrawItem(index);
}
this.updateListVisibility_();
if (selectedDevice)
this.setSelectedDevice_(selectedDevice);
return true;
},
/**
* Forces a revailidation of the list content. Deleting a single item from
* the list results in a stale cache requiring an invalidation.
* @param {string=} opt_selection Optional address of device to select
* after refreshing the list.
*/
refresh: function(opt_selection) {
// TODO(kevers): Investigate if the stale cache issue can be fixed in
// cr.ui.list.
var selectedDevice = opt_selection ? opt_selection :
this.getSelectedDevice_();
this.invalidate();
this.redraw();
if (selectedDevice)
this.setSelectedDevice_(selectedDevice);
},
/**
* Retrieves the address of the selected device, or null if no device is
* selected.
* @return {?string} Address of selected device or null.
* @private
*/
getSelectedDevice_: function() {
var selection = this.selectedItem;
if (selection)
return selection.address;
return null;
},
/**
* Selects the device with the matching address.
* @param {string} address The unique address of the device.
* @private
*/
setSelectedDevice_: function(address) {
var index = this.find(address);
if (index != undefined)
this.selectionModel.selectRange(index, index);
},
/**
* Perges all devices from the list.
*/
clear: function() {
this.dataModel = new ArrayDataModel([]);
this.redraw();
this.updateListVisibility_();
},
/**
* Returns the index of the list entry with the matching address.
* @param {string} address Unique address of the Bluetooth device.
* @return {number|undefined} Index of the matching entry or
* undefined if no match found.
*/
find: function(address) {
var size = this.dataModel.length;
for (var i = 0; i < size; i++) {
var entry = this.dataModel.item(i);
if (entry.address == address)
return i;
}
},
/** @override */
createItem: function(entry) {
return new BluetoothListItem(entry);
},
/**
* Overrides the default implementation, which is used to compute the
* size of an element in the list. The default implementation relies
* on adding a placeholder item to the list and fetching its size and
* position. This strategy does not work if an item is added to the list
* while it is hidden, as the computed metrics will all be zero in that
* case.
* @return {{height: number, marginTop: number, marginBottom: number,
* width: number, marginLeft: number, marginRight: number}}
* The height and width of the item, taking margins into account,
* and the margins themselves.
*/
measureItem: function() {
return {
height: this.itemHeight_,
marginTop: 0,
marginBotton: 0,
width: this.itemWidth_,
marginLeft: 0,
marginRight: 0
};
},
/**
* Override the default implementation to return a predetermined size,
* which in turns allows proper layout of items even if the list is hidden.
* @return {height: number, width: number} Dimensions of a single item in
* the list of bluetooth device.
* @private.
*/
getDefaultItemSize_: function() {
return {
height: this.itemHeight_,
width: this.itemWidth_
};
},
/**
* Override base implementation of handleClick_, which unconditionally
* removes the item. In this case, removal of the element is deferred
* pending confirmation from the Bluetooth adapter.
* @param {Event} e The click event object.
* @private
*/
handleClick_: function(e) {
if (this.disabled)
return;
var target = e.target;
if (!target.classList.contains('row-delete-button'))
return;
var listItem = this.getListItemAncestor(target);
var selected = this.selectionModel.selectedIndexes;
var index = this.getIndexOfListItem(listItem);
if (selected.indexOf(index) == -1)
selected = [index];
for (var j = selected.length - 1; j >= 0; j--) {
var index = selected[j];
var item = this.getListItemByIndex(index);
if (item && item.deletable) {
// Device is busy until we hear back from the Bluetooth adapter.
// Prevent double removal request.
item.deletable = false;
// TODO(kevers): Provide visual feedback that the device is busy.
// Inform the bluetooth adapter that we are disconnecting or
// forgetting the device.
chrome.send('updateBluetoothDevice',
[item.data.address, item.connected ? 'disconnect' : 'forget']);
}
}
},
/** @override */
deleteItemAtIndex: function(index) {
var selectedDevice = this.getSelectedDevice_();
this.dataModel.splice(index, 1);
this.refresh(selectedDevice);
this.updateListVisibility_();
},
/**
* If the list has an associated empty list placholder then update the
* visibility of the list and placeholder.
* @private
*/
updateListVisibility_: function() {
var empty = this.dataModel.length == 0;
var listPlaceHolderID = this.id + '-empty-placeholder';
if ($(listPlaceHolderID)) {
if (this.hidden != empty) {
this.hidden = empty;
$(listPlaceHolderID).hidden = !empty;
this.refresh();
}
}
},
};
cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR);
cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR);
cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
cr.defineProperty(BluetoothListItem, 'notconnectable',
cr.PropertyKind.BOOL_ATTR);
return {
BluetoothListItem: BluetoothListItem,
BluetoothDeviceList: BluetoothDeviceList,
Constants: Constants
};
});