Warning, /plasma/plasma-mobile/components/mobileshell/qml/volumeosd/ListItemBase.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  *  SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
0003  *  SPDX-FileCopyrightText: 2019 Sefa Eyeoglu <contact@scrumplex.net>
0004  *  SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 import QtQuick 2.15
0010 import QtQuick.Controls 2.15 as Controls
0011 import QtQuick.Layouts 1.1
0012 import QtQuick.Window 2.2
0013 
0014 import org.kde.kirigami 2.20 as Kirigami
0015 import org.kde.ksvg 1.0 as KSvg
0016 import org.kde.kquickcontrolsaddons 2.0
0017 import org.kde.plasma.core as PlasmaCore
0018 import org.kde.plasma.components 3.0 as PlasmaComponents
0019 import org.kde.plasma.private.volume 0.1
0020 
0021 import "icon.js" as Icon
0022 
0023 // adapted from https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/ui/ListItemBase.qml
0024 Controls.ItemDelegate {
0025     id: baseItem
0026     
0027     property string label
0028     property alias listIcon: clientIcon.source
0029     property string type // sink, source, source-output
0030     
0031     onClicked: {
0032         if (selectButton.visible) {
0033             model.PulseObject.default = true;
0034         }
0035     }
0036     
0037     contentItem: RowLayout {
0038         id: row
0039         spacing: Kirigami.Units.smallSpacing
0040         
0041         PlasmaComponents.RadioButton {
0042             id: selectButton
0043             Layout.alignment: Qt.AlignTop
0044             Layout.topMargin: Math.round(row.height / 2 - implicitHeight - Kirigami.Units.smallSpacing / 2) // align with text
0045             checked: model.PulseObject.hasOwnProperty("default") ? model.PulseObject.default : false
0046             visible: (baseItem.type == "sink" && sinkView.model.count > 1) || (baseItem.type == "source" && sourceView.model.count > 1)
0047             onClicked: model.PulseObject.default = true
0048         }
0049         
0050         // application icon
0051         Kirigami.Icon {
0052             id: clientIcon
0053             Layout.alignment: Qt.AlignVCenter
0054             Layout.rightMargin: Kirigami.Units.smallSpacing
0055             Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0056             Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0057             visible: type === "sink-input" || type === "source-output"
0058             source: "unknown"
0059             onSourceChanged: {
0060                 if (!valid && source != "unknown") {
0061                     source = "unknown";
0062                 }
0063             }
0064         }
0065         
0066         ColumnLayout {
0067             Layout.alignment: Qt.AlignVCenter
0068             Layout.fillWidth: true
0069             spacing: Kirigami.Units.smallSpacing
0070             
0071             RowLayout {
0072                 Layout.fillWidth: true
0073                 spacing: Kirigami.Units.smallSpacing
0074                 Layout.alignment: Qt.AlignBottom
0075                 
0076                 PlasmaComponents.Label {
0077                     id: mainLabel
0078                     text: baseItem.label
0079                     Layout.alignment: Qt.AlignBottom
0080                     Layout.fillWidth: true
0081                     elide: Text.ElideRight
0082                 }
0083                 
0084                 PlasmaComponents.ToolButton {
0085                     Layout.alignment: Qt.AlignBottom
0086                     Layout.bottomMargin: -Kirigami.Units.smallSpacing
0087                     icon.name: "application-menu"
0088                     checkable: true
0089                     checked: contextMenu.visible && contextMenu.visualParent === this
0090                     visible: contextMenu.hasContent
0091                     onClicked: {
0092                         contextMenu.visualParent = this;
0093                         contextMenu.openRelative();
0094                     }
0095                     PlasmaComponents.ToolTip {
0096                         text: i18n("Show additional options for %1", baseItem.label)
0097                     }
0098                     
0099                     ListItemMenu {
0100                         id: contextMenu
0101                         pulseObject: model.PulseObject
0102                         cardModel: paCardModel
0103                         itemType: {
0104                             switch (baseItem.type) {
0105                             case "sink":
0106                                 return ListItemMenu.Sink;
0107                             case "sink-input":
0108                                 return ListItemMenu.SinkInput;
0109                             case "source":
0110                                 return ListItemMenu.Source;
0111                             case "source-output":
0112                                 return ListItemMenu.SourceOutput;
0113                             }
0114                         }
0115                         sourceModel: {
0116                             if (baseItem.type.includes("sink")) {
0117                                 return sinkView.model;
0118                             } else if (baseItem.type.includes("source")) {
0119                                 return sourceView.model;
0120                             }
0121                         }
0122                         onVisibleChanged: window.suppressActiveClose = visible
0123                     }
0124                 }
0125             }
0126             
0127             RowLayout {
0128                 Layout.fillWidth: true
0129                 spacing: Kirigami.Units.smallSpacing
0130                 
0131                 // this slider was effectively copied from the source (linked at the top of the file)
0132                 PlasmaComponents.Slider {
0133                     id: slider
0134                     Layout.fillWidth: true
0135                     Layout.alignment: Qt.AlignTop
0136                     
0137                     // Helper properties to allow async slider updates.
0138                     // While we are sliding we must not react to value updates
0139                     // as otherwise we can easily end up in a loop where value
0140                     // changes trigger volume changes trigger value changes.
0141                     property int volume: Volume
0142                     property bool ignoreValueChange: true
0143                     readonly property bool forceRaiseMaxVolume: volume >= PulseAudio.NormalVolume * 1.01
0144                     
0145                     from: PulseAudio.MinimalVolume
0146                     to: PulseAudio.NormalVolume
0147                     stepSize: to / (to / PulseAudio.NormalVolume * 100.0)
0148                     visible: HasVolume
0149                     enabled: VolumeWritable
0150                     opacity: Muted ? 0.5 : 1
0151                     
0152                     Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", baseItem.label)
0153 
0154                     background: KSvg.FrameSvgItem {
0155                         imagePath: "widgets/slider"
0156                         prefix: "groove"
0157                         width: parent.availableWidth
0158                         height: margins.top + margins.bottom
0159                         anchors.centerIn: parent
0160                         scale: parent.mirrored ? -1 : 1
0161 
0162                         KSvg.FrameSvgItem {
0163                             imagePath: "widgets/slider"
0164                             prefix: "groove-highlight"
0165                             anchors.left: parent.left
0166                             y: (parent.height - height) / 2
0167                             width: Math.max(margins.left + margins.right, slider.handle.x * meter.volume)
0168                             height: Math.max(margins.top + margins.bottom, parent.height)
0169                             opacity: meter.available && (meter.volume > 0 || animation.running)
0170                             VolumeMonitor {
0171                                 id: meter
0172                                 target: parent.visible ? model.PulseObject : null
0173                             }
0174                             Behavior on width {
0175                                 NumberAnimation  {
0176                                     id: animation
0177                                     duration: Kirigami.Units.shortDuration
0178                                     easing.type: Easing.OutQuad
0179                                 }
0180                             }
0181                         }
0182                     }
0183 
0184                     Component.onCompleted: {
0185                         ignoreValueChange = false;
0186                     }
0187 
0188                     onVolumeChanged: {
0189                         var oldIgnoreValueChange = ignoreValueChange;
0190                         ignoreValueChange = true;
0191                         value = Volume;
0192                         ignoreValueChange = oldIgnoreValueChange;
0193                     }
0194 
0195                     onValueChanged: {
0196                         if (!ignoreValueChange) {
0197                             Volume = value;
0198                             Muted = value == 0;
0199 
0200                             if (!pressed) {
0201                                 updateTimer.restart();
0202                             }
0203                         }
0204                     }
0205 
0206                     onPressedChanged: {
0207                         if (!pressed) {
0208                             // Make sure to sync the volume once the button was
0209                             // released.
0210                             // Otherwise it might be that the slider is at v10
0211                             // whereas PA rejected the volume change and is
0212                             // still at v15 (e.g.).
0213                             updateTimer.restart();
0214                         }
0215                     }
0216 
0217                     Timer {
0218                         id: updateTimer
0219                         interval: 200
0220                         onTriggered: slider.value = Volume
0221                     }
0222                 }
0223                 PlasmaComponents.Label {
0224                     id: percentText
0225                     readonly property real value: model.PulseObject.volume > slider.to ? model.PulseObject.volume : slider.value
0226                     readonly property real displayValue: Math.round(value / PulseAudio.NormalVolume * 100.0)
0227                     Layout.alignment: Qt.AlignHCenter
0228                     Layout.minimumWidth: percentMetrics.advanceWidth
0229                     horizontalAlignment: Qt.AlignRight
0230                     text: i18nc("volume percentage", "%1%", displayValue)
0231                     color: {
0232                         if (displayValue <= 100) {
0233                             return Kirigami.Theme.textColor
0234                         } else if (displayValue > 100 && displayValue <= 125) {
0235                             return Kirigami.Theme.neutralTextColor
0236                         } else {
0237                             return Kirigami.Theme.negativeTextColor
0238                         }
0239                     }
0240                 }
0241 
0242                 TextMetrics {
0243                     id: percentMetrics
0244                     font: percentText.font
0245                     text: i18nc("only used for sizing, should be widest possible string", "100%")
0246                 }
0247             }
0248         }
0249     }
0250     
0251     function setVolumeByPercent(targetPercent) {
0252         model.PulseObject.volume = Math.round(PulseAudio.NormalVolume * (targetPercent/100));
0253     }
0254 }