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 }