diff --git a/applet/contents/ui/ListItemBase.qml b/applet/contents/ui/ListItemBase.qml index c39b545..5082ac0 100644 --- a/applet/contents/ui/ListItemBase.qml +++ b/applet/contents/ui/ListItemBase.qml @@ -1,393 +1,394 @@ /* Copyright 2014-2015 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.4 import QtQuick.Controls 1.0 import QtQuick.Layouts 1.0 import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.draganddrop 2.0 as DragAndDrop import org.kde.plasma.private.volume 0.1 import "../code/icon.js" as Icon PlasmaComponents.ListItem { id: item property alias label: textLabel.text property alias labelOpacity: textLabel.opacity property alias draggable: dragArea.enabled property alias icon: clientIcon.source property alias iconUsesPlasmaTheme: clientIcon.usesPlasmaTheme property string type checked: dropArea.containsDrag opacity: (draggedStream && draggedStream.deviceIndex == Index) ? 0.3 : 1.0 ListView.delayRemove: dragArea.dragActive Item { width: parent.width height: rowLayout.height RowLayout { id: rowLayout - width: parent.width + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: LayoutMirroring.enabled ? 0 : units.smallSpacing + anchors.leftMargin: LayoutMirroring.enabled ? units.smallSpacing : 0 spacing: units.smallSpacing PlasmaCore.IconItem { id: clientIcon - Layout.alignment: Qt.AlignHCenter + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.preferredHeight: column.height * 0.75 Layout.preferredWidth: Layout.preferredHeight source: "unknown" onSourceChanged: { if (!valid && source != "unknown") { source = "unknown"; } } DragAndDrop.DragArea { id: dragArea anchors.fill: parent delegate: parent mimeData { source: item } onDragStarted: { draggedStream = PulseObject; beginMoveStream(type == "sink-input" ? "sink" : "source"); } onDrop: { draggedStream = null; endMoveStream(); } MouseArea { anchors.fill: parent 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; } } } } } ColumnLayout { id: column spacing: 1 RowLayout { Layout.fillWidth: true PlasmaExtras.Heading { id: textLabel Layout.fillWidth: true height: undefined level: 5 opacity: 0.6 wrapMode: Text.NoWrap elide: Text.ElideRight } + SmallToolButton { id: contextMenuButton icon: "application-menu" checkable: true onClicked: contextMenu.show() tooltip: i18n("Show additional options for %1", textLabel.text) } } RowLayout { SmallToolButton { readonly property bool isPlayback: type.substring(0, 4) == "sink" icon: Icon.name(Volume, Muted, isPlayback ? "audio-volume" : "microphone-sensitivity") onClicked: Muted = !Muted tooltip: i18n("Mute %1", textLabel.text) } PlasmaComponents.Slider { id: slider // Helper properties to allow async slider updates. // 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. property int volume: Volume property bool ignoreValueChange: true property bool forceRaiseMaxVolume: false readonly property bool raiseMaxVolume: forceRaiseMaxVolume || volume >= PulseAudio.NormalVolume * 1.01 Layout.fillWidth: true minimumValue: PulseAudio.MinimalVolume maximumValue: raiseMaxVolume ? PulseAudio.MaximalVolume : PulseAudio.NormalVolume stepSize: maximumValue / (maximumValue / PulseAudio.NormalVolume * 100.0) visible: HasVolume enabled: VolumeWritable opacity: Muted ? 0.5 : 1 Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", textLabel.text) Component.onCompleted: { ignoreValueChange = false; } onVolumeChanged: { var oldIgnoreValueChange = ignoreValueChange; ignoreValueChange = true; value = Volume; ignoreValueChange = oldIgnoreValueChange; } onValueChanged: { if (!ignoreValueChange) { Volume = value; Muted = value == 0; if (!pressed) { updateTimer.restart(); } } } 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.). updateTimer.restart(); if (type == "sink") { playFeedback(Index); } } } Timer { id: updateTimer interval: 200 onTriggered: slider.value = Volume } } PlasmaComponents.Label { id: percentText readonly property real value: PulseObject.volume > slider.maximumValue ? PulseObject.volume : slider.value Layout.alignment: Qt.AlignHCenter Layout.minimumWidth: percentMetrics.advanceWidth horizontalAlignment: Qt.AlignRight text: i18nc("volume percentage", "%1%", Math.round(value / PulseAudio.NormalVolume * 100.0)) } TextMetrics { id: percentMetrics font: percentText.font text: i18nc("only used for sizing, should be widest possible string", "100%") } } + + RowLayout { + Layout.fillWidth: true + + Item { + Layout.fillWidth: true + } + + PlasmaComponents3.Button { + id: defaultButton + text: i18n("Default Device") + icon.name: "favorite" + checkable: true + checked: PulseObject.default + visible: (type == "sink" && sinkView.model.count > 1) || (type == "source" && sourceView.model.count > 1) + onClicked: PulseObject.default = true; + } + } } } DragAndDrop.DropArea { id: dropArea anchors.fill: parent enabled: draggedStream onDragEnter: { if (draggedStream.deviceIndex == Index) { event.ignore(); } } onDrop: { draggedStream.deviceIndex = Index; } } MouseArea { anchors { fill: parent leftMargin: clientIcon.width } acceptedButtons: Qt.MiddleButton onClicked: Muted = !Muted } } PlasmaComponents.ContextMenu { id: contextMenu visualParent: contextMenuButton placement: PlasmaCore.Types.BottomPosedLeftAlignedPopup onStatusChanged: { if (status == PlasmaComponents.DialogStatus.Closed) { contextMenuButton.checked = false; } } function newSeperator() { return Qt.createQmlObject("import org.kde.plasma.components 2.0 as PlasmaComponents; PlasmaComponents.MenuItem { separator: true }", contextMenu); } function newMenuItem() { return Qt.createQmlObject("import org.kde.plasma.components 2.0 as PlasmaComponents; PlasmaComponents.MenuItem {}", contextMenu); } function loadDynamicActions() { contextMenu.clearMenuItems(); - // Mute - var menuItem = newMenuItem(); - menuItem.text = i18nc("Checkable switch for (un-)muting sound output.", "Mute"); - menuItem.checkable = true; - menuItem.checked = Muted; - menuItem.clicked.connect(function() { - Muted = !Muted - }); - contextMenu.addMenuItem(menuItem); - - // Default - if (typeof PulseObject.default === "boolean") { - var menuItem = newMenuItem(); - menuItem.text = i18nc("Checkable switch to change the current default output.", "Default"); - menuItem.checkable = true; - menuItem.checked = PulseObject.default - menuItem.clicked.connect(function() { - PulseObject.default = true - }); - contextMenu.addMenuItem(menuItem); - } - // Raise max volume menuItem = newMenuItem(); menuItem.text = i18n("Raise maximum volume"); menuItem.checkable = true; menuItem.checked = slider.forceRaiseMaxVolume; menuItem.clicked.connect(function() { slider.forceRaiseMaxVolume = !slider.forceRaiseMaxVolume; if (!slider.forceRaiseMaxVolume && Volume > PulseAudio.NormalVolume) { Volume = PulseAudio.NormalVolume; } }); contextMenu.addMenuItem(menuItem); // Switch all streams of the relevant kind to this device if (type == "source") { menuItem = newMenuItem(); menuItem.text = i18n("Record all audio via this device"); menuItem.icon = "mic-on" // or "mic-ready" // or "audio-input-microphone-symbolic" menuItem.clicked.connect(function() { PulseObject.switchStreams(); }); contextMenu.addMenuItem(menuItem); } else if (type == "sink") { menuItem = newMenuItem(); menuItem.text = i18n("Play all audio via this device"); menuItem.icon = "audio-on" // or "audio-ready" // or "audio-speakers-symbolic" menuItem.clicked.connect(function() { PulseObject.switchStreams(); }); contextMenu.addMenuItem(menuItem); } // Ports if (PulseObject.ports && PulseObject.ports.length > 0) { contextMenu.addMenuItem(newSeperator()); var isMultiplePorts = (1 < PulseObject.ports.length); var menuItem = newMenuItem(); menuItem.text = i18nc("Heading for a list of ports of a device (for example built-in laptop speakers or a plug for headphones)", "Ports"); menuItem.section = true; contextMenu.addMenuItem(menuItem); for (var i = 0; i < PulseObject.ports.length; i++) { var port = PulseObject.ports[i]; var menuItem = newMenuItem(); menuItem.text = port.description; if (port.availability == Port.Unavailable) { if (port.name == "analog-output-speaker" || port.name == "analog-input-microphone-internal") { menuItem.text += i18nc("Port is unavailable", " (unavailable)"); } else { menuItem.text += i18nc("Port is unplugged", " (unplugged)"); } } menuItem.enabled = isMultiplePorts; menuItem.checkable = true; menuItem.checked = i === PulseObject.activePortIndex; var setActivePort = function(portIndex) { return function() { PulseObject.activePortIndex = portIndex; }; }; menuItem.clicked.connect(setActivePort(i)); contextMenu.addMenuItem(menuItem); } } // Choose output / input device // By choice only shown when there are at least two options if ((type == "sink-input" && sinkView.model.count > 1) || (type == "source-input" && sourceView.model.count > 1)) { contextMenu.addMenuItem(newSeperator()); var menuItem = newMenuItem(); if (type == "sink-input") { menuItem.text = i18nc("Heading for a list of possible output devices (speakers, headphones, ...) to choose", "Play audio using"); } else { menuItem.text = i18nc("Heading for a list of possible input devices (built-in microphone, headset, ...) to choose", "Record audio using"); } menuItem.section = true; contextMenu.addMenuItem(menuItem); var sModel = type == "sink-input" ? sinkView.model : sourceView.model; for (var i = 0; i < sModel.count; ++i) { var data = sModel.get(i); var menuItem = newMenuItem(); menuItem.text = data.Description; menuItem.enabled = true; menuItem.checkable = true; menuItem.checked = data.Index === PulseObject.deviceIndex; var setActiveSink = function(sinkIndex) { return function() { PulseObject.deviceIndex = sinkIndex; }; }; menuItem.clicked.connect(setActiveSink(data.Index)); contextMenu.addMenuItem(menuItem); } } } function show() { loadDynamicActions(); openRelative(); } } } diff --git a/src/kcm/package/contents/ui/DefaultDeviceButton.qml b/src/kcm/package/contents/ui/DefaultDeviceButton.qml deleted file mode 100644 index 877ccb5..0000000 --- a/src/kcm/package/contents/ui/DefaultDeviceButton.qml +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2016 David Rosca - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -import QtQuick 2.0 -import QtQuick.Controls 1.0 - -RadioButton { - property bool isDefault: true - - checked: isDefault - text: checked ? i18nd("kcm_pulseaudio", "Default") : "" - onIsDefaultChanged: checked = isDefault -} diff --git a/src/kcm/package/contents/ui/DeviceListItem.qml b/src/kcm/package/contents/ui/DeviceListItem.qml index edb3384..30d5a29 100644 --- a/src/kcm/package/contents/ui/DeviceListItem.qml +++ b/src/kcm/package/contents/ui/DeviceListItem.qml @@ -1,107 +1,103 @@ /* Copyright 2014-2015 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.0 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.plasma.private.volume 0.1 ColumnLayout { id: delegate width: parent.width RowLayout { Kirigami.Icon { Layout.alignment: Qt.AlignHCenter width: height height: inputText.height source: IconName || "audio-card" } Label { id: inputText Layout.fillWidth: true elide: Text.ElideRight text: Description } - DefaultDeviceButton { + Button { + text: i18n("Default device") + icon.name: "favorite" visible: delegate.ListView.view.count > 1 - isDefault: Default - onCheckedChanged: { - if (!checked) { - // Cannot unset default device - checked = isDefault; - } else { - Default = true; - } - } + checkable: true + checked: Default + onClicked: Default = true; } MuteButton { muted: Muted onCheckedChanged: Muted = checked } } ColumnLayout { width: parent.width RowLayout { visible: portbox.count > 1 Label { text: i18nd("kcm_pulseaudio", "Port") } ComboBox { id: portbox readonly property var ports: Ports Layout.fillWidth: true onModelChanged: currentIndex = ActivePortIndex currentIndex: ActivePortIndex onActivated: ActivePortIndex = index onPortsChanged: { var items = []; for (var i = 0; i < ports.length; ++i) { var port = ports[i]; var text = port.description; if (port.availability == Port.Unavailable) { if (port.name == "analog-output-speaker" || port.name == "analog-input-microphone-internal") { text += i18ndc("kcm_pulseaudio", "Port is unavailable", " (unavailable)"); } else { text += i18ndc("kcm_pulseaudio", "Port is unplugged", " (unplugged)"); } } items.push(text); } model = items; } } } VolumeSlider {} } ListItemSeperator { view: delegate.ListView.view } } diff --git a/src/kcm/package/contents/ui/MuteButton.qml b/src/kcm/package/contents/ui/MuteButton.qml index 71a0996..3109f0f 100644 --- a/src/kcm/package/contents/ui/MuteButton.qml +++ b/src/kcm/package/contents/ui/MuteButton.qml @@ -1,32 +1,32 @@ /* Copyright 2014-2015 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.0 import QtQuick.Controls 1.0 -ToolButton { +Button { property bool muted: true iconName: 'audio-volume-muted' tooltip: i18nd("kcm_pulseaudio", "Mute audio") checkable: true checked: muted onMutedChanged: checked = muted }