Warning, /plasma/bluedevil/src/applet/package/contents/ui/DeviceItem.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2013-2014 Jan Grulich <jgrulich@redhat.com>
0003     SPDX-FileCopyrightText: 2014-2015 David Rosca <nowrep@gmail.com>
0004     SPDX-FileCopyrightText: 2020 Nate Graham <nate@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 import QtQuick 2.15
0010 import QtQuick.Controls 2.15
0011 import QtQuick.Layouts 1.15
0012 
0013 import org.kde.plasma.components 3.0 as PlasmaComponents3
0014 import org.kde.plasma.core as PlasmaCore
0015 import org.kde.kirigami 2.20 as Kirigami
0016 import org.kde.ksvg 1.0 as KSvg
0017 import org.kde.plasma.extras 2.0 as PlasmaExtras
0018 import org.kde.plasma.private.bluetooth as PlasmaBt
0019 
0020 import org.kde.bluezqt 1.0 as BluezQt
0021 import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons
0022 
0023 PlasmaExtras.ExpandableListItem {
0024     id: expandableListItem
0025 
0026     property bool connecting: false
0027     property var currentDeviceDetails: []
0028 
0029     icon: model.Icon
0030     title: model.DeviceFullName
0031     subtitle: infoText()
0032     isBusy: connecting
0033     isDefault: model.Connected
0034     defaultActionButtonAction: Action {
0035         icon.name: model.Connected ? "network-disconnect-symbolic" : "network-connect-symbolic"
0036         text: model.Connected ? i18n("Disconnect") : i18n("Connect")
0037         onTriggered: connectToDevice()
0038     }
0039 
0040     contextualActions: [
0041         Action {
0042             id: browseFilesButton
0043             enabled: Uuids.indexOf(BluezQt.Services.ObexFileTransfer) !== -1
0044             icon.name: "folder-symbolic"
0045             text: i18n("Browse Files")
0046 
0047             onTriggered: {
0048                 const url = "obexftp://%1/".arg(Address.replace(/:/g, "-"));
0049                 Qt.openUrlExternally(url);
0050             }
0051         },
0052         Action {
0053             id: sendFileButton
0054             enabled: Uuids.indexOf(BluezQt.Services.ObexObjectPush) !== -1
0055             icon.name: "folder-download-symbolic"
0056             text: i18n("Send File")
0057 
0058             onTriggered: {
0059                 PlasmaBt.LaunchApp.launchSendFile(Ubi);
0060             }
0061         }
0062     ]
0063 
0064     customExpandedViewContent: Component {
0065         id: expandedView
0066 
0067         ColumnLayout {
0068             spacing: 0
0069 
0070             // Media Player
0071             MediaPlayerItem {
0072                 id: mediaPlayer
0073                 Layout.leftMargin: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
0074                 Layout.fillWidth: true
0075                 visible: MediaPlayer
0076             }
0077 
0078             Item {
0079                 Layout.preferredHeight: Kirigami.Units.smallSpacing
0080                 visible: mediaPlayer.visible
0081             }
0082 
0083             KSvg.SvgItem {
0084                 id: mediaPlayerSeparator
0085                 Layout.fillWidth: true
0086                 imagePath: "widgets/line"
0087                 elementId: "horizontal-line"
0088                 visible: mediaPlayer.visible
0089                     || (!mediaPlayer.visible && !(browseFilesButton.enabled || sendFileButton.enabled))
0090             }
0091 
0092             Item {
0093                 Layout.preferredHeight: Kirigami.Units.smallSpacing
0094                 visible: mediaPlayerSeparator.visible
0095             }
0096 
0097             KQuickControlsAddons.Clipboard {
0098                 id: clipboard
0099             }
0100 
0101             PlasmaExtras.Menu {
0102                 id: contextMenu
0103                 property string text
0104 
0105                 function show(item, text, x, y) {
0106                     contextMenu.text = text
0107                     visualParent = item
0108                     open(x, y)
0109                 }
0110 
0111                 PlasmaExtras.MenuItem {
0112                     text: i18n("Copy")
0113                     icon: "edit-copy-symbolic"
0114                     enabled: contextMenu.text !== ""
0115                     onClicked: clipboard.content = contextMenu.text
0116                 }
0117             }
0118 
0119             // Details
0120             MouseArea {
0121                 Layout.fillWidth: true
0122                 Layout.preferredHeight: detailsGrid.implicitHeight
0123 
0124                 acceptedButtons: Qt.RightButton
0125                 activeFocusOnTab: repeater.count > 0
0126 
0127                 Accessible.description: {
0128                     let description = [];
0129                     for (let i = 0; i < currentDeviceDetails.length; i += 2) {
0130                         description.push(currentDeviceDetails[i]);
0131                         description.push(": ");
0132                         description.push(currentDeviceDetails[i + 1]);
0133                         description.push("; ");
0134                     }
0135                     return description.join('');
0136                 }
0137 
0138                 onPressed: {
0139                     const item = detailsGrid.childAt(mouse.x, mouse.y);
0140                     if (!item || !item.isContent) {
0141                         return; // only let users copy the value on the right
0142                     }
0143 
0144                     contextMenu.show(this, item.text, mouse.x, mouse.y);
0145                 }
0146 
0147                 Loader {
0148                     anchors.fill: parent
0149                     active: parent.activeFocus
0150                     asynchronous: true
0151                     z: -1
0152 
0153                     sourceComponent: PlasmaExtras.Highlight {
0154                         hovered: true
0155                     }
0156                 }
0157 
0158                 GridLayout {
0159                     id: detailsGrid
0160                     width: parent.width
0161                     columns: 2
0162                     rowSpacing: Kirigami.Units.smallSpacing / 4
0163 
0164                     Repeater {
0165                         id: repeater
0166 
0167                         model: currentDeviceDetails.length
0168 
0169                         PlasmaComponents3.Label {
0170                             id: detailLabel
0171 
0172                             Layout.fillWidth: true
0173 
0174                             readonly property bool isContent: index % 2
0175 
0176                             horizontalAlignment: isContent ? Text.AlignLeft : Text.AlignRight
0177                             elide: isContent ? Text.ElideRight : Text.ElideNone
0178                             font: Kirigami.Theme.smallFont
0179                             opacity: isContent ? 1 : 0.6
0180                             text: isContent ? currentDeviceDetails[index] : `${currentDeviceDetails[index]}:`
0181                             textFormat: isContent ? Text.PlainText : Text.StyledText
0182                         }
0183                     }
0184                 }
0185             }
0186 
0187             Component.onCompleted: createContent()
0188         }
0189     }
0190 
0191     // Hide device details when the device for this delegate changes
0192     // This happens eg. when device connects/disconnects
0193     property QtObject __dev
0194     readonly property QtObject dev : Device
0195     onDevChanged: {
0196         if (__dev === dev) {
0197             return;
0198         }
0199         __dev = dev;
0200 
0201         if (expandedView.status === Component.Ready) {
0202             expandableListItem.collapse();
0203             expandableListItem.ListView.view.currentIndex = -1;
0204         }
0205     }
0206 
0207     function boolToString(v): string {
0208         return v ? i18n("Yes") : i18n("No");
0209     }
0210 
0211     function adapterName(a): string {
0212         const hci = devicesModel.adapterHciString(a.ubi);
0213         return (hci !== "")
0214             ? i18nc("@label %1 is human-readable adapter name, %2 is HCI", "%1 (%2)", a.name, hci)
0215             : a.name;
0216     }
0217 
0218     function createContent() {
0219         const details = [];
0220 
0221         if (Name !== RemoteName) {
0222             details.push(i18n("Remote Name"));
0223             details.push(RemoteName);
0224         }
0225 
0226         details.push(i18n("Address"));
0227         details.push(Address);
0228 
0229         details.push(i18n("Paired"));
0230         details.push(boolToString(Paired));
0231 
0232         details.push(i18n("Trusted"));
0233         details.push(boolToString(Trusted));
0234 
0235         details.push(i18n("Adapter"));
0236         details.push(adapterName(Adapter));
0237 
0238         currentDeviceDetails = details;
0239     }
0240 
0241     function infoText(): string {
0242         if (connecting) {
0243             return Connected ? i18n("Disconnecting") : i18n("Connecting");
0244         }
0245 
0246         const labels = [];
0247 
0248         if (Connected) {
0249             labels.push(i18n("Connected"));
0250         }
0251 
0252         switch (Type) {
0253         case BluezQt.Device.Headset:
0254         case BluezQt.Device.Headphones:
0255         case BluezQt.Device.OtherAudio:
0256             labels.push(i18n("Audio device"));
0257             break;
0258 
0259         case BluezQt.Device.Keyboard:
0260         case BluezQt.Device.Mouse:
0261         case BluezQt.Device.Joypad:
0262         case BluezQt.Device.Tablet:
0263             labels.push(i18n("Input device"));
0264             break;
0265 
0266         case BluezQt.Device.Phone:
0267             labels.push(i18n("Phone"));
0268             break;
0269 
0270         default:
0271             const profiles = [];
0272 
0273             if (Uuids.indexOf(BluezQt.Services.ObexFileTransfer) !== -1) {
0274                 profiles.push(i18n("File transfer"));
0275             }
0276             if (Uuids.indexOf(BluezQt.Services.ObexObjectPush) !== -1) {
0277                 profiles.push(i18n("Send file"));
0278             }
0279             if (Uuids.indexOf(BluezQt.Services.HumanInterfaceDevice) !== -1) {
0280                 profiles.push(i18n("Input"));
0281             }
0282             if (Uuids.indexOf(BluezQt.Services.AdvancedAudioDistribution) !== -1) {
0283                 profiles.push(i18n("Audio"));
0284             }
0285             if (Uuids.indexOf(BluezQt.Services.Nap) !== -1) {
0286                 profiles.push(i18n("Network"));
0287             }
0288 
0289             if (!profiles.length) {
0290                 profiles.push(i18n("Other device"));
0291             }
0292 
0293             labels.push(profiles.join(", "));
0294         }
0295 
0296         if (Battery) {
0297             labels.push(i18n("%1% Battery", Battery.percentage));
0298         }
0299 
0300         return labels.join(" ยท ");
0301     }
0302 
0303     function errorText(/*PendingCall*/ call): string {
0304         switch (call.error) {
0305         case BluezQt.PendingCall.Failed:
0306             return (call.errorText === "Host is down")
0307                 ? i18nc("Notification when the connection failed due to Failed:HostIsDown",
0308                         "The device is unreachable")
0309                 : i18nc("Notification when the connection failed due to Failed",
0310                         "Connection to the device failed");
0311 
0312         case BluezQt.PendingCall.NotReady:
0313             return i18nc("Notification when the connection failed due to NotReady",
0314                          "The device is not ready");
0315 
0316         default:
0317             return "";
0318         }
0319     }
0320 
0321     function connectToDevice() {
0322         if (connecting) {
0323             return;
0324         }
0325 
0326         connecting = true;
0327         runningActions++;
0328 
0329         // Disconnect device
0330         if (Connected) {
0331             Device.disconnectFromDevice().finished.connect(call => {
0332                 connecting = false;
0333                 runningActions--;
0334             });
0335             return;
0336         }
0337 
0338         // Connect device
0339         const /*PendingCall*/call = Device.connectToDevice();
0340         call.userData = Device;
0341 
0342         call.finished.connect(call => {
0343             connecting = false;
0344             runningActions--;
0345 
0346             if (call.error) {
0347                 const device = call.userData;
0348                 const title = i18nc("@label %1 is human-readable device name, %2 is low-level device address", "%1 (%2)", device.name, device.address);
0349                 const text = errorText(call);
0350 
0351                 PlasmaBt.Notify.connectionFailed(title, text);
0352             }
0353         });
0354     }
0355 }