ListItemBase.qml 15.8 KB
Newer Older
1
/*
2
3
    SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
    SPDX-FileCopyrightText: 2019 Sefa Eyeoglu <contact@scrumplex.net>
4

5
    SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6
7
*/

8
import QtQuick 2.4
9
10
11
12
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0

import org.kde.kquickcontrolsaddons 2.0
13
import org.kde.plasma.components 2.0 as PlasmaComponents // for ListItem
14
import org.kde.plasma.components 3.0 as PlasmaComponents3
15
import org.kde.plasma.core 2.0 as PlasmaCore
16
import org.kde.plasma.extras 2.0 as PlasmaExtras
17
import org.kde.draganddrop 2.0 as DragAndDrop
David Rosca's avatar
David Rosca committed
18
19
import org.kde.plasma.private.volume 0.1

20
21
import "../code/icon.js" as Icon

22
23
24
PlasmaComponents.ListItem {
    id: item

25
    property alias label: defaultButton.text
26
    property alias draggable: dragArea.enabled
David Rosca's avatar
David Rosca committed
27
    property alias icon: clientIcon.source
28
    property alias iconUsesPlasmaTheme: clientIcon.usesPlasmaTheme
29
    property string type
30
    property string fullNameToShowOnHover: ""
31

32
33
    checked: dropArea.containsDrag
    opacity: (draggedStream && draggedStream.deviceIndex == Index) ? 0.3 : 1.0
34
    separatorVisible: false
35

36
37
    ListView.delayRemove: dragArea.dragActive

38
39
    Item {
        width: parent.width
40
        height: column.height
41

42
        RowLayout {
43
            id: controlsRow
44
            spacing: PlasmaCore.Units.smallSpacing
45
46
            anchors.left: parent.left
            anchors.right: parent.right
47

David Rosca's avatar
David Rosca committed
48
            PlasmaCore.IconItem {
49
                id: clientIcon
50
                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
51
52
                Layout.preferredHeight: column.height * 0.75
                Layout.preferredWidth: Layout.preferredHeight
53
                source: "unknown"
54
                visible: type === "sink-input" || type === "source-input"
55
56
57
58
59
60

                onSourceChanged: {
                    if (!valid && source != "unknown") {
                        source = "unknown";
                    }
                }
61
62
63
64
65
66
67
68
69
70
71

                DragAndDrop.DragArea {
                    id: dragArea
                    anchors.fill: parent
                    delegate: parent

                    mimeData {
                        source: item
                    }

                    onDragStarted: {
72
                        draggedStream = model.PulseObject;
73
                        beginMoveStream(type == "sink-input" ? "sink" : "source");
74
75
76
77
                    }

                    onDrop: {
                        draggedStream = null;
78
                        endMoveStream();
79
80
81
82
                    }

                    MouseArea {
                        anchors.fill: parent
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
83
84
85
86
87
88
89
                        cursorShape: dragArea.enabled ? (pressed && pressedButtons === Qt.LeftButton ? Qt.ClosedHandCursor : Qt.OpenHandCursor) : undefined
                        acceptedButtons: Qt.LeftButton | Qt.MiddleButton
                        onClicked: {
                            if (mouse.button === Qt.MiddleButton) {
                                Muted = !Muted;
                            }
                        }
90
91
                    }
                }
92
            }
93

94
95
            ColumnLayout {
                id: column
96
                spacing: 0
97

98
                RowLayout {
99
                    Layout.minimumHeight: contextMenuButton.implicitHeight
100

101
102
                    PlasmaComponents3.RadioButton {
                        id: defaultButton
103
104
                        // Maximum width of the button need to match the text. Empty area must not change the default device.
                        Layout.maximumWidth: controlsRow.width - Layout.leftMargin - Layout.rightMargin
105
                                              - (contextMenuButton.visible ? contextMenuButton.implicitWidth + PlasmaCore.Units.smallSpacing * 2 : 0)
106
107
                        Layout.leftMargin: LayoutMirroring.enabled ? 0 : Math.round((muteButton.width - defaultButton.indicator.width) / 2)
                        Layout.rightMargin: LayoutMirroring.enabled ? Math.round((muteButton.width - defaultButton.indicator.width) / 2) : 0
108
                        spacing: PlasmaCore.Units.smallSpacing + Math.round((muteButton.width - defaultButton.indicator.width) / 2)
109
                        checked: model.PulseObject.hasOwnProperty("default") ? model.PulseObject.default : false
110
                        visible: (type == "sink" && sinkView.model.count > 1) || (type == "source" && sourceView.model.count > 1)
111
                        onClicked: model.PulseObject.default = true;
112
113
                    }

114
                    RowLayout {
115
                        Layout.fillWidth: true
116
                        visible: !defaultButton.visible
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

                        // User-friendly name
                        PlasmaComponents3.Label {
                            Layout.fillWidth: !longDescription.visible
                            text: defaultButton.text
                            elide: Text.ElideRight

                            MouseArea {
                                id: labelHoverHandler

                                // Only want to handle hover for the width of
                                // the actual text item itself
                                anchors.left: parent.left
                                anchors.top: parent.top
                                width: parent.contentWidth
                                height: parent.contentHeight

134
                                enabled: item.fullNameToShowOnHover.length > 0
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
                                hoverEnabled: true
                                acceptedButtons: Qt.NoButton
                            }
                        }
                        // Possibly not user-friendly description; only show on hover
                        PlasmaComponents3.Label {
                            id: longDescription

                            Layout.fillWidth: true
                            visible: opacity > 0
                            opacity: labelHoverHandler.containsMouse ? 1 : 0
                            Behavior on opacity {
                                NumberAnimation {
                                    duration: PlasmaCore.Units.shortDuration
                                    easing.type: Easing.InOutQuad
                                }
                            }

                            // Not a word puzzle because this is not a translated string
                            text: "(" + item.fullNameToShowOnHover + ")"
                            elide: Text.ElideRight
                        }
157
158
159
160
                    }

                    Item {
                        Layout.fillWidth: true
161
                        visible: contextMenuButton.visible
162
163
                    }

164
                    SmallToolButton {
165
                        id: contextMenuButton
166
                        icon.name: "application-menu"
167
                        checked: contextMenu.visible && contextMenu.visualParent === this
168
169
                        onClicked: {
                            contextMenu.visualParent = this;
170
                            contextMenu.openRelative();
171
                        }
172
173
                        visible: contextMenu.hasContent

174
175
176
                        PlasmaComponents3.ToolTip {
                            text: i18n("Show additional options for %1", defaultButton.text)
                        }
177
                    }
178
                }
179

180
                RowLayout {
181
                    SmallToolButton {
182
                        id: muteButton
183
                        readonly property bool isPlayback: type.substring(0, 4) == "sink"
184
                        icon.name: Icon.name(Volume, Muted, isPlayback ? "audio-volume" : "microphone-sensitivity")
185
                        onClicked: Muted = !Muted
Sefa Eyeoglu's avatar
Sefa Eyeoglu committed
186
                        checked: Muted
187
188
189
190

                        PlasmaComponents3.ToolTip {
                            text: i18n("Mute %1", defaultButton.text)
                        }
191
                    }
192

193
                    PlasmaComponents3.Slider {
194
195
                        id: slider

196
                        // Helper properties to allow async slider updates.
197
198
199
                        // While we are sliding we must not react to value updates
                        // as otherwise we can easily end up in a loop where value
                        // changes trigger volume changes trigger value changes.
200
                        property int volume: Volume
201
                        property bool ignoreValueChange: true
202
203
                        readonly property bool forceRaiseMaxVolume: (raiseMaximumVolumeCheckbox.checked && (type === "sink" || type === "source"))
                                                                    || volume >= PulseAudio.NormalVolume * 1.01
204
205

                        Layout.fillWidth: true
206
207
208
                        from: PulseAudio.MinimalVolume
                        to: forceRaiseMaxVolume ? PulseAudio.MaximalVolume : PulseAudio.NormalVolume
                        stepSize: to / (to / PulseAudio.NormalVolume * 100.0)
209
                        visible: HasVolume
210
211
                        enabled: VolumeWritable
                        opacity: Muted ? 0.5 : 1
212

213
                        Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", defaultButton.text)
214

215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
                        background:  PlasmaCore.FrameSvgItem {
                            imagePath: "widgets/slider"
                            prefix: "groove"
                            width: parent.availableWidth
                            height: margins.top + margins.bottom
                            anchors.centerIn: parent
                            scale: parent.mirrored ? -1 : 1

                            PlasmaCore.FrameSvgItem {
                                imagePath: "widgets/slider"
                                prefix: "groove-highlight"
                                anchors.left: parent.left
                                y: (parent.height - height) / 2
                                width: Math.max(margins.left + margins.right, slider.handle.x * meter.volume)
                                height: Math.max(margins.top + margins.bottom, parent.height)
                                opacity: meter.available && (meter.volume > 0 || animation.running)
                                VolumeMonitor {
                                    id: meter
233
                                    target: parent.visible ? model.PulseObject : null
234
235
236
237
238
239
240
241
242
243
244
                                }
                                Behavior on width {
                                    NumberAnimation  {
                                        id: animation
                                        duration: PlasmaCore.Units.shortDuration
                                        easing.type: Easing.OutQuad
                                    }
                                }
                            }
                        }

245
246
247
248
                        Component.onCompleted: {
                            ignoreValueChange = false;
                        }

249
                        onVolumeChanged: {
250
                            var oldIgnoreValueChange = ignoreValueChange;
251
                            ignoreValueChange = true;
252
                            value = Volume;
253
                            ignoreValueChange = oldIgnoreValueChange;
254
                        }
255

256
                        onValueChanged: {
257
                            if (!ignoreValueChange) {
258
                                Volume = value;
259
                                Muted = value == 0;
260
261
262
263

                                if (!pressed) {
                                    updateTimer.restart();
                                }
264
                            }
265
266
                        }

267
268
269
270
271
272
273
                        onPressedChanged: {
                            if (!pressed) {
                                // Make sure to sync the volume once the button was
                                // released.
                                // Otherwise it might be that the slider is at v10
                                // whereas PA rejected the volume change and is
                                // still at v15 (e.g.).
274
                                updateTimer.restart();
275
276
277
278

                                if (type == "sink") {
                                    playFeedback(Index);
                                }
279
                            }
280
                        }
281

282
283
284
                        Timer {
                            id: updateTimer
                            interval: 200
285
                            onTriggered: slider.value = Volume
286
                        }
287
                    }
288
                    PlasmaComponents3.Label {
289
                        id: percentText
290
                        readonly property real value: model.PulseObject.volume > slider.to ? model.PulseObject.volume : slider.value
291
                        readonly property real displayValue: Math.round(value / PulseAudio.NormalVolume * 100.0)
292
                        Layout.alignment: Qt.AlignHCenter
293
                        Layout.minimumWidth: percentMetrics.advanceWidth
294
                        horizontalAlignment: Qt.AlignRight
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
                        text: i18nc("volume percentage", "%1%", displayValue)
                        // Display a subtle visual indication that the volume
                        // might be dangerously high
                        // ------------------------------------------------
                        // Keep this in sync with the copies in VolumeSlider.qml
                        // and plasma-workspace:OSDItem.qml
                        color: {
                            if (displayValue <= 100) {
                                return theme.textColor
                            } else if (displayValue > 100 && displayValue <= 125) {
                                return theme.neutralTextColor
                            } else {
                                return theme.negativeTextColor
                            }
                        }
310
                    }
311
312
313
314
315
316

                    TextMetrics {
                        id: percentMetrics
                        font: percentText.font
                        text: i18nc("only used for sizing, should be widest possible string", "100%")
                    }
317
318
319
                }
            }
        }
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337

        DragAndDrop.DropArea {
            id: dropArea
            anchors.fill: parent
            enabled: draggedStream

            onDragEnter: {
                if (draggedStream.deviceIndex == Index) {
                    event.ignore();
                }
            }

            onDrop: {
                draggedStream.deviceIndex = Index;
            }
        }

        MouseArea {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
338
339
340
341
            anchors {
                fill: parent
                leftMargin: clientIcon.width
            }
342
343
344
345
            acceptedButtons: Qt.MiddleButton | Qt.RightButton
            onPressed: {
                if (mouse.button === Qt.RightButton) {
                    contextMenu.visualParent = this;
346
                    contextMenu.open(mouse.x, mouse.y);
347
348
349
350
351
352
353
                }
            }
            onClicked: {
                if (mouse.button === Qt.MiddleButton) {
                    Muted = !Muted;
                }
            }
354
        }
355
    }
356

357
    ListItemMenu {
358
        id: contextMenu
359
360
361
362
363
364
365
366
367
368
369
370
        pulseObject: model.PulseObject
        cardModel: paCardModel
        itemType: {
            switch (item.type) {
            case "sink":
                return ListItemMenu.Sink;
            case "sink-input":
                return ListItemMenu.SinkInput;
            case "source":
                return ListItemMenu.Source;
            case "source-input":
                return ListItemMenu.SourceOutput;
371
372
            }
        }
373
374
375
376
377
        sourceModel: {
            if (item.type.includes("sink")) {
                return sinkView.model;
            } else if (item.type.includes("source")) {
                return sourceView.model;
378
            }
379
380
        }
    }
381
}