Warning, /plasma/bluedevil/src/kcm/ui/main.qml is written in an unsupported language. File is not indexed.

0001 /**
0002  * SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de>
0003  * SPDX-FileCopyrightText: 2021 Tom Zander <tom@flowee.org>
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 import QtQuick 2.2
0008 import QtQuick.Layouts 1.1
0009 import QtQuick.Controls 2.10 as QQC2
0010 
0011 import org.kde.kirigami 2.20 as Kirigami
0012 import org.kde.kirigami.delegates as KD
0013 import org.kde.kcmutils
0014 
0015 import org.kde.bluezqt 1.0 as BluezQt
0016 
0017 import org.kde.plasma.private.bluetooth
0018 
0019 import "script.js" as Script;
0020 
0021 ScrollViewKCM {
0022     id: root
0023 
0024     actions: [
0025         Kirigami.Action {
0026             id: enableAction
0027             text: i18nc("@action: button as in, 'enable Bluetooth'", "Enabled")
0028             icon.name: "network-bluetooth-symbolic"
0029             checkable: true
0030             checked: BluezQt.Manager.bluetoothOperational
0031             visible: BluezQt.Manager.rfkill.state !== BluezQt.Rfkill.Unknown
0032             onTriggered: {
0033                 root.setBluetoothEnabled(!BluezQt.Manager.bluetoothOperational)
0034             }
0035             displayComponent: QQC2.Switch {
0036                 text: enableAction.text
0037                 checked: enableAction.checked
0038                 visible: enableAction.visible
0039                 onToggled: enableAction.trigger()
0040             }
0041         },
0042         Kirigami.Action {
0043             text: i18n("Add New Device…")
0044             icon.name: "list-add-symbolic"
0045             onTriggered: kcm.runWizard()
0046             visible: BluezQt.Manager.bluetoothOperational
0047         },
0048         Kirigami.Action {
0049             text: i18n("Configure…")
0050             icon.name: "configure-symbolic"
0051             onTriggered: kcm.push("General.qml")
0052             visible: BluezQt.Manager.bluetoothOperational
0053         }
0054     ]
0055 
0056     function makeCall(call) {
0057         busyIndicator.running = true
0058         call.finished.connect(call => {
0059             busyIndicator.running = false
0060             if (call.error) {
0061                 errorMessage.text = call.errorText
0062                 errorMessage.visible = true
0063             }
0064         })
0065     }
0066 
0067     Connections {
0068         target: kcm
0069 
0070         function onErrorOccured(errorText) {
0071             errorMessage.text = errorText
0072             errorMessage.visible = true
0073         }
0074     }
0075 
0076     Component {
0077         id: forgetDialogComponent
0078 
0079         Kirigami.PromptDialog {
0080             id: dialog
0081 
0082             required property BluezQt.Adapter adapter
0083             required property BluezQt.Device device
0084             required property string name
0085 
0086             signal call(BluezQt.PendingCall pc)
0087 
0088             title: i18n("Forget this Device?")
0089             subtitle: i18n("Are you sure you want to forget \"%1\"?", dialog.name)
0090 
0091             showCloseButton: false
0092 
0093             // Need to use fully custom actions because it's not possible to override
0094             // the text and icon of a single standardbutton, and if we use just a
0095             // custom action for that one, then it's in the wrong visual position
0096             // relative to the StandardButton-provided Cancel button
0097             standardButtons: Kirigami.Dialog.NoButton
0098             customFooterActions: [
0099                 Kirigami.Action {
0100                     text: i18nc("@action:button", "Forget Device")
0101                     icon.name: "edit-delete-remove-symbolic"
0102                     onTriggered: {
0103                         dialog.accept();
0104                     }
0105                 },
0106                 Kirigami.Action {
0107                     text: i18nc("@action:button", "Cancel")
0108                     icon.name: "dialog-cancel-symbolic"
0109                     onTriggered: {
0110                         dialog.reject();
0111                     }
0112                     shortcut: StandardKey.Cancel
0113                 }
0114             ]
0115 
0116             onAccepted: call(adapter.removeDevice(device))
0117 
0118             contentData: [
0119                 Connections {
0120                     target: dialog.device
0121                     function onDeviceRemoved() {
0122                         dialog.reject();
0123                     }
0124                 },
0125                 Connections {
0126                     target: dialog.adapter
0127                     function onAdapterRemoved() {
0128                         dialog.reject();
0129                     }
0130                     function onPoweredChanged() {
0131                         if (!dialog.adapter.powered) {
0132                             dialog.reject();
0133                         }
0134                     }
0135                 }
0136             ]
0137         }
0138     }
0139 
0140     implicitHeight: Kirigami.Units.gridUnit * 28
0141     implicitWidth: Kirigami.Units.gridUnit * 28
0142 
0143     function setBluetoothEnabled(enabled) {
0144         BluezQt.Manager.bluetoothBlocked = !enabled
0145 
0146         for (var i = 0; i < BluezQt.Manager.adapters.length; ++i) {
0147             var adapter = BluezQt.Manager.adapters[i];
0148             adapter.powered = enabled;
0149         }
0150     }
0151 
0152     header: Kirigami.InlineMessage {
0153         id: errorMessage
0154         type: Kirigami.MessageType.Error
0155         showCloseButton: true
0156     }
0157 
0158     view: ListView {
0159         id: list
0160         clip: true
0161 
0162         Kirigami.PlaceholderMessage {
0163             id: noBluetoothMessage
0164             // We cannot use the adapter count here because that can be zero when
0165             // bluetooth is disabled even when there are physical devices
0166             visible: BluezQt.Manager.rfkill.state === BluezQt.Rfkill.Unknown
0167             icon.name: "edit-none-symbolic"
0168             text: i18n("No Bluetooth adapters found")
0169             width: parent.width - (Kirigami.Units.largeSpacing * 4)
0170             anchors.centerIn: parent
0171         }
0172 
0173         Kirigami.PlaceholderMessage {
0174             id: bluetoothDisabledMessage
0175             visible: BluezQt.Manager.operational && !BluezQt.Manager.bluetoothOperational && !noBluetoothMessage.visible
0176             icon.name: "network-bluetooth-inactive-symbolic"
0177             text: i18n("Bluetooth is disabled")
0178             width: parent.width - (Kirigami.Units.largeSpacing * 4)
0179             anchors.centerIn: parent
0180 
0181             helpfulAction: Kirigami.Action {
0182                 icon.name: "network-bluetooth-symbolic"
0183                 text: i18n("Enable")
0184                 onTriggered: {
0185                     root.setBluetoothEnabled(true)
0186                 }
0187             }
0188         }
0189 
0190         Kirigami.PlaceholderMessage {
0191             visible: !noBluetoothMessage.visible && !bluetoothDisabledMessage.visible && list.count === 0
0192             icon.name: "network-bluetooth-activated-symbolic"
0193             text: i18n("No devices paired")
0194             width: parent.width - (Kirigami.Units.largeSpacing * 4)
0195             anchors.centerIn: parent
0196         }
0197 
0198         model: BluezQt.Manager.bluetoothOperational ? devicesModel : null
0199 
0200         QQC2.BusyIndicator {
0201             id: busyIndicator
0202             running: false
0203             anchors.centerIn: parent
0204         }
0205 
0206 
0207         DevicesProxyModel {
0208             id: devicesModel
0209             sourceModel: BluezQt.DevicesModel { }
0210         }
0211 
0212         section.property: "Connected"
0213         section.delegate: Kirigami.ListSectionHeader {
0214             width: ListView.view.width
0215             text: section === "true" ? i18n("Connected") : i18n("Available")
0216         }
0217 
0218         delegate: QQC2.ItemDelegate {
0219             width: ListView.view.width
0220 
0221             onClicked: kcm.push("Device.qml", {device: model.Device})
0222 
0223             contentItem: RowLayout {
0224                 spacing: Kirigami.Units.smallSpacing
0225 
0226                 KD.IconTitleSubtitle {
0227                     title: model.Name
0228                     subtitle: infoText(model.Device.type, model.Device.battery, model.Device.uuids)
0229                     icon.name: model.Icon
0230                     icon.width: Kirigami.Units.iconSizes.medium
0231                     Layout.fillWidth: true
0232                 }
0233 
0234                 QQC2.ToolButton {
0235                     text: model.Connected ? i18n("Disconnect") : i18n("Connect")
0236                     icon.name: model.Connected ? "network-disconnect-symbolic" : "network-connect-symbolic"
0237                     display: QQC2.AbstractButton.IconOnly
0238                     QQC2.ToolTip.text: text
0239                     QQC2.ToolTip.visible: hovered
0240 
0241                     onClicked: {
0242                         if (model.Connected) {
0243                             root.makeCall(model.Device.disconnectFromDevice())
0244                         } else {
0245                             root.makeCall(model.Device.connectToDevice())
0246                         }
0247                     }
0248                 }
0249 
0250                 QQC2.ToolButton {
0251                     text: i18nc("@action:button %1 is the name of a Bluetooth device", "Forget \"%1\"", model.Name)
0252                     icon.name: "edit-delete-remove-symbolic"
0253                     display: QQC2.AbstractButton.IconOnly
0254                     QQC2.ToolTip.text: text
0255                     QQC2.ToolTip.visible: hovered
0256 
0257                     onClicked: {
0258                         const dialog = forgetDialogComponent.createObject(root, {
0259                             adapter: model.Adapter,
0260                             device: model.Device,
0261                             name: model.Name,
0262                         });
0263                         // Use IIFE (Immediately Invoked Function Expression) to hard-copy a reference
0264                         // to root object, to avoid it being lost if the delegate is destroyed.
0265                         dialog.call.connect((function (root) {
0266                             return (call => root.makeCall(call));
0267                         })(root));
0268                         dialog.closed.connect(() => dialog.destroy());
0269                         dialog.open();
0270                     }
0271                 }
0272             }
0273         }
0274     }
0275 
0276     function infoText(type, battery, uuids): string {
0277         const labels = [];
0278 
0279         labels.push(Script.deviceTypeToString(type));
0280 
0281         if (battery) {
0282             labels.push(i18n("%1% Battery", battery.percentage));
0283         }
0284 
0285         return labels.join(" · ");
0286     }
0287 }