DeviceItem.qml 10.8 KB
Newer Older
David Rosca's avatar
David Rosca committed
1
/*
2
3
4
    SPDX-FileCopyrightText: 2013-2014 Jan Grulich <jgrulich@redhat.com>
    SPDX-FileCopyrightText: 2014-2015 David Rosca <nowrep@gmail.com>
    SPDX-FileCopyrightText: 2020 Nate Graham <nate@kde.org>
David Rosca's avatar
David Rosca committed
5

6
    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
David Rosca's avatar
David Rosca committed
7
8
9
*/

import QtQuick 2.2
10
11
12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.3

David Rosca's avatar
David Rosca committed
13
import org.kde.plasma.core 2.0 as PlasmaCore
14
import org.kde.plasma.extras 2.0 as PlasmaExtras
15
16
import org.kde.plasma.components 2.0 as PlasmaComponents // for ContextMenu/MenuItem
import org.kde.plasma.components 3.0 as PlasmaComponents3
David Rosca's avatar
David Rosca committed
17
18
import org.kde.plasma.private.bluetooth 1.0 as PlasmaBt

19
20
21
22
23
import org.kde.bluezqt 1.0 as BluezQt
import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons

PlasmaExtras.ExpandableListItem {
    id: expandableListItem
David Rosca's avatar
David Rosca committed
24
25
26
27

    property bool connecting : false
    property var currentDeviceDetails : []

28
29
30
31
    icon: model.Icon
    title: model.DeviceFullName
    subtitle: infoText()
    isBusy: connecting
32
    isDefault: model.Connected
33
34
35
36
    defaultActionButtonAction: Action {
        icon.name: model.Connected ? "network-disconnect" : "network-connect"
        text: model.Connected ? i18n("Disconnect") : i18n("Connect")
        onTriggered: connectToDevice()
David Rosca's avatar
David Rosca committed
37
    }
38
39
    customExpandedViewContent: Component {
        id: expandedView
David Rosca's avatar
David Rosca committed
40

41
42
        ColumnLayout {
            spacing: 0
David Rosca's avatar
David Rosca committed
43
44
45

            PlasmaCore.SvgItem {
                id: detailsSeparator
46
47
                Layout.fillWidth: true
                Layout.preferredHeight: lineSvg.elementSize("horizontal-line").height
David Rosca's avatar
David Rosca committed
48
49
50
51
52
53
54
                elementId: "horizontal-line"
                svg: PlasmaCore.Svg {
                    id: lineSvg
                    imagePath: "widgets/line"
                }
            }

55
56
57
58
            // Actions
            GridLayout {
                columns: 2
                rowSpacing: 0
David Rosca's avatar
David Rosca committed
59

60
                Item {
61
                    width: PlasmaCore.Units.iconSizes.medium
62
                    Layout.rowSpan: 2
David Rosca's avatar
David Rosca committed
63
64
                }

65
                PlasmaComponents3.ToolButton {
David Rosca's avatar
David Rosca committed
66
67
                    id: browseFilesButton
                    text: i18n("Browse Files")
68
                    icon.name: "folder"
David Rosca's avatar
David Rosca committed
69
70
71
                    visible: Uuids.indexOf(BluezQt.Services.ObexFileTransfer) != -1

                    onClicked: {
72
                        var url = "obexftp://%1/".arg(Address.replace(/:/g, "-"));
73
                        Qt.openUrlExternally(url);
David Rosca's avatar
David Rosca committed
74
75
76
                    }
                }

77
                PlasmaComponents3.ToolButton {
David Rosca's avatar
David Rosca committed
78
79
                    id: sendFileButton
                    text: i18n("Send File")
80
                    icon.name: "folder-download"
David Rosca's avatar
David Rosca committed
81
82
83
84
85
86
87
88
89
90
                    visible: Uuids.indexOf(BluezQt.Services.ObexObjectPush) != -1

                    onClicked: {
                        PlasmaBt.LaunchApp.runCommand("bluedevil-sendfile", ["-u", Ubi]);
                    }
                }
            }

            PlasmaCore.SvgItem {
                id: actionsSeparator
91
92
                Layout.fillWidth: true
                Layout.preferredHeight: lineSvg.elementSize("horizontal-line").height
David Rosca's avatar
David Rosca committed
93
94
95
96
97
                visible: browseFilesButton.visible || sendFileButton.visible
                elementId: "horizontal-line"
                svg: lineSvg
            }

98
            Item {
99
                height: PlasmaCore.Units.smallSpacing
100
101
            }

102
103
            // Media Player
            RowLayout {
104
                Item {
105
                    width: PlasmaCore.Units.iconSizes.medium
106
                }
107

108
                MediaPlayerItem {
109
110
111
112
113
                    id: mediaPlayer
                    Layout.fillWidth: true
                    visible: MediaPlayer
                }
            }
114

115
116
117
118
119
120
121
122
            PlasmaCore.SvgItem {
                id: mediaPlayerSeparator
                Layout.fillWidth: true
                Layout.preferredHeight: lineSvg.elementSize("horizontal-line").height
                visible: mediaPlayer.visible
                elementId: "horizontal-line"
                svg: lineSvg
            }
123

124
            Item {
125
                height: PlasmaCore.Units.smallSpacing
126
                visible: mediaPlayerSeparator.visible
127
128
            }

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
            KQuickControlsAddons.Clipboard {
                id: clipboard
            }

            PlasmaComponents.ContextMenu {
                id: contextMenu
                property string text

                function show(item, text, x, y) {
                    contextMenu.text = text
                    visualParent = item
                    open(x, y)
                }

                PlasmaComponents.MenuItem {
                    text: i18n("Copy")
                    icon: "edit-copy"
                    enabled: contextMenu.text !== ""
                    onClicked: clipboard.content = contextMenu.text
                }
            }

151
152
            // Details
            GridLayout {
153
                columns: 2
154
                rowSpacing: PlasmaCore.Units.smallSpacing / 4
David Rosca's avatar
David Rosca committed
155
156
157
158

                Repeater {
                    id: repeater

159
160
                    model: currentDeviceDetails.length

161
                    PlasmaComponents3.Label {
162
                        id: detailLabel
163
164
                        Layout.fillWidth: true
                        horizontalAlignment: index % 2 ? Text.AlignLeft : Text.AlignRight
165
                        elide: index % 2 ? Text.ElideRight : Text.ElideNone
166
                        font: PlasmaCore.Theme.smallestFont
167
                        text: index % 2 ? currentDeviceDetails[index] : currentDeviceDetails[index] + ":"
168
                        textFormat: index % 2 ? Text.PlainText : Text.StyledText
169
170
171
172
173
174
175

                        MouseArea {
                            anchors.fill: parent
                            acceptedButtons: Qt.RightButton
                            onPressed: contextMenu.show(this, detailLabel.text, mouse.x, mouse.y)
                            enabled: index % 2 === 1 // only let users copy the value on the right
                        }
David Rosca's avatar
David Rosca committed
176
177
178
179
                    }
                }
            }

180
            Component.onCompleted: createContent()
David Rosca's avatar
David Rosca committed
181
182
183
        }
    }

184
185
    // Hide device details when the device for this delegate changes
    // This happens eg. when device connects/disconnects
186
187
    property QtObject __dev
    readonly property QtObject dev : Device
188
189
190
191
192
193
    onDevChanged: {
        if (__dev == dev) {
            return;
        }
        __dev = dev;

Felix Yan's avatar
Felix Yan committed
194
        if (expandedView.status == Component.Ready) {
195
196
            expandableListItem.collapse()
            expandableListItem.ListView.view.currentIndex = -1
197
198
199
        }
    }

David Rosca's avatar
David Rosca committed
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    function boolToString(v)
    {
        if (v) {
            return i18n("Yes");
        }
        return i18n("No");
    }

    function adapterName(a)
    {
        var hci = devicesModel.adapterHciString(a.ubi);
        if (hci != "") {
            return "%1 (%2)".arg(a.name).arg(hci);
        }
        return a.name;
    }

    function createContent() {
218
        var details = [];
David Rosca's avatar
David Rosca committed
219

220
221
222
223
        if (Name != RemoteName) {
            details.push(i18n("Remote Name"));
            details.push(RemoteName);
        }
David Rosca's avatar
David Rosca committed
224

225
226
        details.push(i18n("Address"));
        details.push(Address);
David Rosca's avatar
David Rosca committed
227

228
229
        details.push(i18n("Paired"));
        details.push(boolToString(Paired));
David Rosca's avatar
David Rosca committed
230

231
232
        details.push(i18n("Trusted"));
        details.push(boolToString(Trusted));
David Rosca's avatar
David Rosca committed
233

234
235
        details.push(i18n("Adapter"));
        details.push(adapterName(Adapter));
David Rosca's avatar
David Rosca committed
236

237
        currentDeviceDetails = details;
David Rosca's avatar
David Rosca committed
238
239
240
241
242
    }

    function infoText()
    {
        if (connecting) {
243
            return Connected ? i18n("Disconnecting") : i18n("Connecting");
David Rosca's avatar
David Rosca committed
244
245
        }

246
247
        var labels = [];

248
249
250
251
        if (Connected) {
            labels.push(i18n("Connected"));
        }

David Rosca's avatar
David Rosca committed
252
        switch (Type) {
David Rosca's avatar
David Rosca committed
253
254
255
        case BluezQt.Device.Headset:
        case BluezQt.Device.Headphones:
        case BluezQt.Device.OtherAudio:
256
257
            labels.push(i18n("Audio device"));
            break;
David Rosca's avatar
David Rosca committed
258
259
260
261
262

        case BluezQt.Device.Keyboard:
        case BluezQt.Device.Mouse:
        case BluezQt.Device.Joypad:
        case BluezQt.Device.Tablet:
263
264
            labels.push(i18n("Input device"));
            break;
David Rosca's avatar
David Rosca committed
265

Nicolas Fella's avatar
Nicolas Fella committed
266
267
268
269
        case BluezQt.Device.Phone:
            labels.push(i18n("Phone"));
            break;

David Rosca's avatar
David Rosca committed
270
        default:
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
            var profiles = [];

            if (Uuids.indexOf(BluezQt.Services.ObexFileTransfer) != -1) {
                profiles.push(i18n("File transfer"));
            }
            if (Uuids.indexOf(BluezQt.Services.ObexObjectPush) != -1) {
                profiles.push(i18n("Send file"));
            }
            if (Uuids.indexOf(BluezQt.Services.HumanInterfaceDevice) != -1) {
                profiles.push(i18n("Input"));
            }
            if (Uuids.indexOf(BluezQt.Services.AdvancedAudioDistribution) != -1) {
                profiles.push(i18n("Audio"));
            }
            if (Uuids.indexOf(BluezQt.Services.Nap) != -1) {
                profiles.push(i18n("Network"));
            }
David Rosca's avatar
David Rosca committed
288

289
290
291
            if (!profiles.length) {
                profiles.push(i18n("Other device"));
            }
David Rosca's avatar
David Rosca committed
292

293
            labels.push(profiles.join(", "));
David Rosca's avatar
David Rosca committed
294
295
        }

296
297
        if (Battery) {
            labels.push(i18n("%1% Battery", Battery.percentage));
David Rosca's avatar
David Rosca committed
298
299
        }

300
        return labels.join(" · ");
David Rosca's avatar
David Rosca committed
301
302
303
304
305
306
307
308
309
310
311
    }

    function connectToDevice()
    {
        if (connecting) {
            return;
        }

        connecting = true;
        runningActions++;

312
313
        // Disconnect device
        if (Connected) {
David Rosca's avatar
David Rosca committed
314
            Device.disconnectFromDevice().finished.connect(function(call) {
315
316
317
318
319
320
321
                connecting = false;
                runningActions--;
            });
            return;
        }

        // Connect device
David Rosca's avatar
David Rosca committed
322
        var call = Device.connectToDevice();
323
324
325
        call.userData = Device;

        call.finished.connect(function(call) {
David Rosca's avatar
David Rosca committed
326
327
328
329
330
            connecting = false;
            runningActions--;

            if (call.error) {
                var text = "";
331
332
                var device = call.userData;
                var title = "%1 (%2)".arg(device.name).arg(device.address);
David Rosca's avatar
David Rosca committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

                switch (call.error) {
                case BluezQt.PendingCall.Failed:
                    if (call.errorText == "Host is down") {
                        text = i18nc("Notification when the connection failed due to Failed:HostIsDown",
                                     "The device is unreachable");
                    } else {
                        text = i18nc("Notification when the connection failed due to Failed",
                                     "Connection to the device failed");
                    }
                    break;

                case BluezQt.PendingCall.NotReady:
                    text = i18nc("Notification when the connection failed due to NotReady",
                                 "The device is not ready");
                    break;

                default:
                    return;
                }

                PlasmaBt.Notify.connectionFailed(title, text);
            }
        });
    }
}