Commit 12c07e35 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

Allow "unlocking" volume slider for per-channel (e.g. left, right) volume sliders

The default is "locked" volume slider with just one slider as before.
When opening settings with different volumes in some of the channels,
they are unlocked automatically.

Furthermore, when locking channels again, all channels are set back to a uniform volume.
Normally, plasma-pa adjusts volume by keeping the difference between channels in tact,
so means of explicitly assigning all channels in one go is added.

Also, QQC2 `Slider` has an explicit `onMoved` signal which is only fired when the user
explicitly moves the slider. This should remove the need for the convoluted "ignore changes"
property dance that was present before.
parent 37214faf
......@@ -110,6 +110,26 @@ public:
}
}
template <typename PAFunction>
void setGenericVolumes(quint32 index, QVector<qint64> channelVolumes,
pa_cvolume cVolume, PAFunction pa_set_volume)
{
if (!m_context) {
return;
}
Q_ASSERT(channelVolumes.count() == cVolume.channels);
pa_cvolume newCVolume = cVolume;
for (int i = 0; i < channelVolumes.count(); ++i) {
newCVolume.values[i] = qBound<qint64>(0, channelVolumes.at(i), PA_VOLUME_MAX);
}
if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) {
qCWarning(PLASMAPA) << "pa_set_volume failed";
return;
}
}
template <typename PAFunction>
void setGenericMute(quint32 index, bool mute, PAFunction pa_set_mute)
{
......
......@@ -65,7 +65,6 @@ ColumnLayout {
Item {
Layout.fillWidth: true
visible: portbox.visible
}
Label {
......@@ -99,6 +98,41 @@ ColumnLayout {
model = items;
}
}
Button {
id: unlockChannelsButton
Accessible.name: i18n("Adjust channels individually")
icon.name: "object-unlocked"
checkable: true
onClicked: {
if (checked) {
return;
}
// When unifying channels again, set all of them to the same value
// as to not keep an odd difference when adjusting them in unison
let volumes = ChannelVolumes;
for (let i = 0, count = RawChannels.length; i < count; ++i) {
volumes[i] = Volume;
}
// NOTE "ChannelVolumes = volumes" does not work as the
// AbstractModel does not have the magic JS Array to Qt List
// conversion stuff and only sees our Array as a QJSValue
PulseObject.channelVolumes = volumes;
}
// Default to individual mode when a channel has a different volume
Component.onCompleted: {
checked = ChannelVolumes.some((volume) => {
return volume !== ChannelVolumes[0];
});
}
ToolTip {
text: unlockChannelsButton.Accessible.name
}
}
}
RowLayout {
......@@ -113,9 +147,58 @@ ColumnLayout {
toolTipText: !currentPort ? Description : currentPort.description
}
VolumeSlider {
id: volumeSlider
Layout.alignment: Qt.AlignTop
GridLayout {
columns: 2
VolumeSlider {
id: volumeSlider
Layout.columnSpan: 2
Layout.column: 0
Layout.row: 1
Layout.alignment: Qt.AlignTop
visible: !unlockChannelsButton.checked
value: Volume
onMoved: {
Volume = value;
Muted = (value === 0);
}
}
Repeater {
model: unlockChannelsButton.checked ? Channels : null
Label {
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.column: 0
Layout.row: index + 1
text: i18nc("Placeholder is channel name", "%1:", modelData)
}
}
Repeater {
id: channelSliderRepeater
model: unlockChannelsButton.checked ? RawChannels : null
VolumeSlider {
Layout.column: 1
Layout.row: index + 1
Layout.fillWidth: true
hundredPercentLabelVisible: index === channelSliderRepeater.count -1
value: ChannelVolumes[index]
onMoved: {
PulseObject.setChannelVolume(index, value);
// volumes are updated async, so we'll just assume it worked here
let newChannelVolumes = ChannelVolumes;
newChannelVolumes[index] = value;
Muted = newChannelVolumes.every((volume) => {
return volume === 0;
});
}
}
}
}
}
}
......@@ -87,6 +87,12 @@ ColumnLayout {
VolumeSlider {
id: volumeSlider
Layout.alignment: Qt.AlignTop
value: Volume
onMoved: {
Volume = value;
Muted = (value === 0);
}
}
}
}
......
......@@ -26,19 +26,22 @@ import QtQuick.Controls 2.5 as QQC2
import org.kde.plasma.private.volume 0.1
RowLayout {
Layout.bottomMargin: hundredPercentLabel.height
id: sliderRow
Layout.bottomMargin: hundredPercentLabelVisible ? hundredPercentLabel.height : 0
signal moved()
property alias value: slider.value
property bool hundredPercentLabelVisible: true
QQC2.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
Layout.fillWidth: true
value: Volume
from: PulseAudio.MinimalVolume
to: PulseAudio.MaximalVolume
// TODO: implement a way to hide tickmarks
......@@ -46,39 +49,7 @@ RowLayout {
visible: HasVolume
enabled: VolumeWritable
opacity: Muted ? 0.5 : 1
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();
}
}
onMoved: sliderRow.moved()
QQC2.Label {
id: hundredPercentLabel
......@@ -86,16 +57,11 @@ RowLayout {
z: slider.z - 1
x: (Qt.application.layoutDirection == Qt.RightToLeft ? slider.width - hundredPos : hundredPos) - width / 2
y: slider.height
visible: sliderRow.hundredPercentLabelVisible
opacity: 0.5
font.pixelSize: slider.height / 2.2
text: i18nd("kcm_pulseaudio", "100%")
}
Timer {
id: updateTimer
interval: 200
onTriggered: slider.value = Volume
}
}
QQC2.Label {
......
......@@ -71,6 +71,11 @@ void Sink::setChannelVolume(int channel, qint64 volume)
context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_sink_volume_by_index);
}
void Sink::setChannelVolumes(const QVector<qint64> &channelVolumes)
{
context()->setGenericVolumes(index(), channelVolumes, cvolume(), &pa_context_set_sink_volume_by_index);
}
bool Sink::isDefault() const
{
return context()->server()->defaultSink() == this;
......
......@@ -40,6 +40,7 @@ public:
void setMuted(bool muted) override;
void setActivePortIndex(quint32 port_index) override;
void setChannelVolume(int channel, qint64 volume) override;
void setChannelVolumes(const QVector<qint64> &channelVolumes) override;
bool isDefault() const override;
void setDefault(bool enable) override;
......
......@@ -59,4 +59,9 @@ void SinkInput::setChannelVolume(int channel, qint64 volume)
context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_sink_input_volume);
}
void SinkInput::setChannelVolumes(const QVector<qint64> &channelVolumes)
{
context()->setGenericVolumes(index(), channelVolumes, cvolume(), &pa_context_set_sink_input_volume);
}
} // QPulseAudio
......@@ -39,6 +39,7 @@ public:
void setVolume(qint64 volume) override;
void setMuted(bool muted) override;
void setChannelVolume(int channel, qint64 volume) override;
void setChannelVolumes(const QVector<qint64> &channelVolumes) override;
void setDeviceIndex(quint32 deviceIndex) override;
};
......
......@@ -63,6 +63,11 @@ void Source::setChannelVolume(int channel, qint64 volume)
context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_source_volume_by_index);
}
void Source::setChannelVolumes(const QVector<qint64> &volumes)
{
context()->setGenericVolumes(index(), volumes, cvolume(), &pa_context_set_source_volume_by_index);
}
bool Source::isDefault() const
{
return context()->server()->defaultSource() == this;
......
......@@ -37,6 +37,7 @@ public:
void setMuted(bool muted) override;
void setActivePortIndex(quint32 port_index) override;
void setChannelVolume(int channel, qint64 volume) override;
void setChannelVolumes(const QVector<qint64> &volumes) override;
bool isDefault() const override;
void setDefault(bool enable) override;
......
......@@ -59,4 +59,9 @@ void SourceOutput::setChannelVolume(int channel, qint64 volume)
context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_source_output_volume);
}
void SourceOutput::setChannelVolumes(const QVector<qint64> &channelVolumes)
{
context()->setGenericVolumes(index(), channelVolumes, cvolume(), &pa_context_set_source_output_volume);
}
} // QPulseAudio
......@@ -37,6 +37,7 @@ public:
void setVolume(qint64 volume) override;
void setMuted(bool muted) override;
void setChannelVolume(int channel, qint64 volume) override;
void setChannelVolumes(const QVector<qint64> &channelVolumes) override;
void setDeviceIndex(quint32 deviceIndex) override;
};
......
......@@ -71,9 +71,9 @@ QStringList VolumeObject::rawChannels() const
return m_rawChannels;
}
QList<qreal> VolumeObject::channelVolumes() const
QVector<qint64> VolumeObject::channelVolumes() const
{
QList<qreal> ret;
QVector<qint64> ret;
ret.reserve(m_volume.channels);
for (int i = 0; i < m_volume.channels; ++i) {
ret << m_volume.values[i];
......
......@@ -37,7 +37,7 @@ class VolumeObject : public PulseObject
Q_PROPERTY(bool volumeWritable READ isVolumeWritable NOTIFY isVolumeWritableChanged)
Q_PROPERTY(QStringList channels READ channels NOTIFY channelsChanged)
Q_PROPERTY(QStringList rawChannels READ rawChannels NOTIFY rawChannelsChanged)
Q_PROPERTY(QList<qreal> channelVolumes READ channelVolumes NOTIFY channelVolumesChanged)
Q_PROPERTY(QVector<qint64> channelVolumes READ channelVolumes WRITE setChannelVolumes NOTIFY channelVolumesChanged)
public:
explicit VolumeObject(QObject *parent);
~VolumeObject() override;
......@@ -87,7 +87,9 @@ public:
QStringList channels() const;
QStringList rawChannels() const;
QList<qreal> channelVolumes() const;
QVector<qint64> channelVolumes() const;
virtual void setChannelVolumes(const QVector<qint64> &channelVolumes) = 0;
Q_INVOKABLE virtual void setChannelVolume(int channel, qint64 volume) = 0;
Q_SIGNALS:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment