diff --git a/src/mumble/Accessibility.cpp b/src/mumble/Accessibility.cpp index 4e0d9cea3bb..b49c49277ff 100644 --- a/src/mumble/Accessibility.cpp +++ b/src/mumble/Accessibility.cpp @@ -10,8 +10,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -19,6 +18,8 @@ #include +#include + namespace Mumble { namespace Accessibility { @@ -144,32 +145,15 @@ namespace Accessibility { return description; } - void setSliderSemanticValue(QSlider *slider, QString value) { - // This does currently not work with Orca... - // It is unclear whether it is unintended behavior or not - // We set the value for the slider, any potential focus proxy, - // and the QAccessibleInterface. Orca will still read the - // raw slider value. - QVariant newValue = QVariant(value); - slider->setFocus(); - - QWidget *focusProxy = slider->focusProxy(); - if (focusProxy) { - QAccessibleValueChangeEvent event(focusProxy, newValue); - QAccessible::updateAccessibility(&event); - } + void setSliderSemanticValue(SemanticSlider *slider, QString value) { + qDebug() << "setSemanticValue to " << value; + slider->m_semanticValue = value; - QAccessibleValueChangeEvent event(slider, newValue); + QAccessibleEvent event(slider, QAccessible::NameChanged); QAccessible::updateAccessibility(&event); - - QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(slider); - if (interface) { - QAccessibleValueChangeEvent event(interface, newValue); - QAccessible::updateAccessibility(&event); - } } - void setSliderSemanticValue(QSlider *slider, SliderAccesibilityMode mode, QString suffix) { + void setSliderSemanticValue(SemanticSlider *slider, SliderAccesibilityMode mode, QString suffix) { QString description = QString("%1 %2"); switch (mode) { diff --git a/src/mumble/Accessibility.h b/src/mumble/Accessibility.h index c3ed2a707e5..bdd640faf3c 100644 --- a/src/mumble/Accessibility.h +++ b/src/mumble/Accessibility.h @@ -8,6 +8,7 @@ #include "Channel.h" #include "ClientUser.h" +#include "widgets/SemanticSlider.h" #include #include @@ -30,8 +31,8 @@ namespace Accessibility { QString channelToText(const Channel *channel); QString channelToDescription(const Channel *channel); - void setSliderSemanticValue(QSlider *slider, QString value); - void setSliderSemanticValue(QSlider *slider, SliderAccesibilityMode mode, QString suffix = ""); + void setSliderSemanticValue(SemanticSlider *slider, QString value); + void setSliderSemanticValue(SemanticSlider *slider, SliderAccesibilityMode mode, QString suffix = ""); QWidget *getFirstFocusableChild(QObject *object); diff --git a/src/mumble/AudioConfigDialog.cpp b/src/mumble/AudioConfigDialog.cpp index 9b8869d1b6a..000625bb00a 100644 --- a/src/mumble/AudioConfigDialog.cpp +++ b/src/mumble/AudioConfigDialog.cpp @@ -5,6 +5,7 @@ #include "AudioConfigDialog.h" +#include "Accessibility.h" #include "AudioInput.h" #include "AudioOutput.h" #include "AudioOutputSample.h" @@ -287,8 +288,11 @@ void AudioInputDialog::save() const { } void AudioInputDialog::on_qsFrames_valueChanged(int v) { - qlFrames->setText(tr("%1 ms").arg((v == 1) ? 10 : (v - 1) * 20)); + int val = (v == 1) ? 10 : (v - 1) * 20; + qlFrames->setText(tr("%1 ms").arg(val)); updateBitrate(); + + Mumble::Accessibility::setSliderSemanticValue(qsFrames, QString("%1 %2").arg(val).arg(tr("milliseconds"))); } void AudioInputDialog::on_qsDoublePush_valueChanged(int v) { @@ -299,21 +303,29 @@ void AudioInputDialog::on_qsDoublePush_valueChanged(int v) { } void AudioInputDialog::on_qsPTTHold_valueChanged(int v) { - if (v == 0) + if (v == 0) { qlPTTHold->setText(tr("Off")); - else + Mumble::Accessibility::setSliderSemanticValue(qsPTTHold, tr("Off")); + } else { qlPTTHold->setText(tr("%1 ms").arg(v)); + Mumble::Accessibility::setSliderSemanticValue(qsPTTHold, QString("%1 %2").arg(v).arg(tr("milliseconds"))); + } } void AudioInputDialog::on_qsTransmitHold_valueChanged(int v) { float val = static_cast< float >(v * 10); val = val / 1000.0f; qlTransmitHold->setText(tr("%1 s").arg(val, 0, 'f', 2)); + Mumble::Accessibility::setSliderSemanticValue(qsTransmitHold, + QString("%1 %2").arg(val, 0, 'f', 2).arg(tr("seconds"))); } void AudioInputDialog::on_qsQuality_valueChanged(int v) { qlQuality->setText(tr("%1 kb/s").arg(static_cast< float >(v) / 1000.0f, 0, 'f', 1)); updateBitrate(); + + Mumble::Accessibility::setSliderSemanticValue( + qsQuality, QString("%1 %2").arg(static_cast< float >(v) / 1000.0f, 0, 'f', 1).arg(tr("kilobit per second"))); } void AudioInputDialog::on_qsSpeexNoiseSupStrength_valueChanged(int v) { @@ -322,8 +334,11 @@ void AudioInputDialog::on_qsSpeexNoiseSupStrength_valueChanged(int v) { if (v < 15) { qlSpeexNoiseSupStrength->setText(tr("Off")); pal.setColor(qlSpeexNoiseSupStrength->foregroundRole(), Qt::red); + Mumble::Accessibility::setSliderSemanticValue(qsSpeexNoiseSupStrength, tr("Off")); } else { qlSpeexNoiseSupStrength->setText(tr("-%1 dB").arg(v)); + Mumble::Accessibility::setSliderSemanticValue(qsSpeexNoiseSupStrength, + QString("-%1 %2").arg(v).arg(tr("decibel"))); } qlSpeexNoiseSupStrength->setPalette(pal); } @@ -332,6 +347,8 @@ void AudioInputDialog::on_qsAmp_valueChanged(int v) { v = 18000 - v + 2000; float d = 20000.0f / static_cast< float >(v); qlAmp->setText(QString::fromLatin1("%1").arg(d, 0, 'f', 2)); + + Mumble::Accessibility::setSliderSemanticValue(qsAmp, QString("%1").arg(d, 0, 'f', 2)); } void AudioInputDialog::updateBitrate() { @@ -813,6 +830,7 @@ void AudioOutputDialog::on_qcbSystem_currentIndexChanged(int) { void AudioOutputDialog::on_qsJitter_valueChanged(int v) { qlJitter->setText(tr("%1 ms").arg(v * 10)); + Mumble::Accessibility::setSliderSemanticValue(qsJitter, QString("%1 %2").arg(v * 10).arg(tr("milliseconds"))); } void AudioOutputDialog::on_qsVolume_valueChanged(int v) { @@ -824,22 +842,30 @@ void AudioOutputDialog::on_qsVolume_valueChanged(int v) { qlVolume->setPalette(pal); qlVolume->setText(tr("%1 %").arg(v)); + Mumble::Accessibility::setSliderSemanticValue(qsVolume, Mumble::Accessibility::SliderAccesibilityMode::PERCENT, + "%"); } void AudioOutputDialog::on_qsOtherVolume_valueChanged(int v) { qlOtherVolume->setText(tr("%1 %").arg(v)); + Mumble::Accessibility::setSliderSemanticValue(qsOtherVolume, Mumble::Accessibility::SliderAccesibilityMode::PERCENT, + "%"); } void AudioOutputDialog::on_qsPacketDelay_valueChanged(int v) { qlPacketDelay->setText(tr("%1 ms").arg(v)); + Mumble::Accessibility::setSliderSemanticValue(qsPacketDelay, QString("%1 %2").arg(v).arg(tr("milliseconds"))); } void AudioOutputDialog::on_qsPacketLoss_valueChanged(int v) { qlPacketLoss->setText(tr("%1 %").arg(v)); + Mumble::Accessibility::setSliderSemanticValue(qsPacketLoss, Mumble::Accessibility::SliderAccesibilityMode::PERCENT, + "%"); } void AudioOutputDialog::on_qsDelay_valueChanged(int v) { qlDelay->setText(tr("%1 ms").arg(v * 10)); + Mumble::Accessibility::setSliderSemanticValue(qsDelay, QString("%1 %2").arg(v * 10).arg(tr("milliseconds"))); } void AudioOutputDialog::on_qcbLoopback_currentIndexChanged(int v) { @@ -864,6 +890,7 @@ void AudioOutputDialog::on_qsMinDistance_valueChanged(int value) { void AudioOutputDialog::on_qsbMinimumDistance_valueChanged(double value) { QSignalBlocker blocker(qsMinDistance); qsMinDistance->setValue(value * 10); + Mumble::Accessibility::setSliderSemanticValue(qsMinDistance, QString("%1 %2").arg(value * 10).arg(tr("meters"))); // Ensure that max distance is always a least 1m larger than min distance qsMaxDistance->setValue(std::max(qsMaxDistance->value(), static_cast< int >(value * 10) + 10)); @@ -879,6 +906,7 @@ void AudioOutputDialog::on_qsMaxDistance_valueChanged(int value) { void AudioOutputDialog::on_qsbMaximumDistance_valueChanged(double value) { QSignalBlocker blocker(qsMaxDistance); qsMaxDistance->setValue(value * 10); + Mumble::Accessibility::setSliderSemanticValue(qsMaxDistance, QString("%1 %2").arg(value * 10).arg(tr("meters"))); // Ensure that min distance is always a least 1m less than max distance qsMinDistance->setValue(std::min(qsMinDistance->value(), static_cast< int >(value * 10) - 10)); @@ -887,6 +915,8 @@ void AudioOutputDialog::on_qsbMaximumDistance_valueChanged(double value) { void AudioOutputDialog::on_qsMinimumVolume_valueChanged(int value) { QSignalBlocker blocker(qsbMinimumVolume); qsbMinimumVolume->setValue(value); + Mumble::Accessibility::setSliderSemanticValue(qsMinimumVolume, + Mumble::Accessibility::SliderAccesibilityMode::PERCENT, "%"); } void AudioOutputDialog::on_qsbMinimumVolume_valueChanged(int value) { @@ -897,6 +927,7 @@ void AudioOutputDialog::on_qsbMinimumVolume_valueChanged(int value) { void AudioOutputDialog::on_qsBloom_valueChanged(int value) { QSignalBlocker blocker(qsbBloom); qsbBloom->setValue(value); + Mumble::Accessibility::setSliderSemanticValue(qsBloom, Mumble::Accessibility::SliderAccesibilityMode::PERCENT, "%"); } void AudioOutputDialog::on_qsbBloom_valueChanged(int value) { diff --git a/src/mumble/AudioInput.ui b/src/mumble/AudioInput.ui index 2b563e98f5a..50372dbbe3a 100644 --- a/src/mumble/AudioInput.ui +++ b/src/mumble/AudioInput.ui @@ -176,7 +176,7 @@ - 1 + 0 @@ -263,7 +263,7 @@ - + Time the microphone stays open after the PTT key is released @@ -303,7 +303,7 @@ - + How long to keep transmitting after silence @@ -385,7 +385,7 @@ - + Signal values below this count as silence @@ -410,7 +410,7 @@ - + Signal values above this count as voice @@ -459,7 +459,7 @@ - + How many audio frames to send per packet @@ -510,7 +510,7 @@ - + Quality of compression (peak bandwidth) @@ -594,7 +594,7 @@ - + Maximum amplification of input sound @@ -720,7 +720,7 @@ - + This controls the amount by which Speex will suppress noise. @@ -1072,6 +1072,11 @@ QComboBox
widgets/MUComboBox.h
+ + SemanticSlider + QSlider +
widgets/SemanticSlider.h
+
qcbSystem diff --git a/src/mumble/AudioOutput.ui b/src/mumble/AudioOutput.ui index 36f0c9126a0..7b67b3d3a83 100644 --- a/src/mumble/AudioOutput.ui +++ b/src/mumble/AudioOutput.ui @@ -133,7 +133,7 @@
- + Amount of data to buffer @@ -178,7 +178,7 @@ - + Volume of incoming speech @@ -217,7 +217,7 @@ - + Safety margin for jitter buffer @@ -378,7 +378,7 @@
- + Attenuation of other applications during speech @@ -445,7 +445,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -474,7 +474,7 @@ - + @@ -518,7 +518,7 @@ - + Factor for sound volume increase @@ -600,7 +600,7 @@ - + Variance in packet latency @@ -639,7 +639,7 @@ - + Packet loss for loopback mode @@ -717,6 +717,11 @@ QComboBox
widgets/MUComboBox.h
+ + SemanticSlider + QSlider +
widgets/SemanticSlider.h
+
qcbSystem diff --git a/src/mumble/AudioWizard.ui b/src/mumble/AudioWizard.ui index 3fb184117c6..848a5f046c8 100644 --- a/src/mumble/AudioWizard.ui +++ b/src/mumble/AudioWizard.ui @@ -263,7 +263,7 @@ You should hear a voice sample. Change the slider below to the lowest value whic
- + 1 @@ -407,7 +407,7 @@ Speak loudly, as when you are annoyed or excited. Decrease the volume in the sou - + 32767 @@ -550,7 +550,7 @@ Speak loudly, as when you are annoyed or excited. Decrease the volume in the sou - + 1 @@ -944,6 +944,11 @@ Mumble is under continuous development, and the development team wants to focus QComboBox
widgets/MUComboBox.h
+ + SemanticSlider + QSlider +
widgets/SemanticSlider.h
+
qcbInput diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt index d27031639b0..3a3ab0aa679 100644 --- a/src/mumble/CMakeLists.txt +++ b/src/mumble/CMakeLists.txt @@ -307,6 +307,8 @@ set(MUMBLE_SOURCES "widgets/SearchDialogItemDelegate.h" "widgets/SearchDialogTree.cpp" "widgets/SearchDialogTree.h" + "widgets/SemanticSlider.cpp" + "widgets/SemanticSlider.h" "${SHARED_SOURCE_DIR}/ACL.cpp" diff --git a/src/mumble/Log.cpp b/src/mumble/Log.cpp index 226df1b58ec..5d7736ec83e 100644 --- a/src/mumble/Log.cpp +++ b/src/mumble/Log.cpp @@ -5,6 +5,7 @@ #include "Log.h" +#include "Accessibility.h" #include "AudioOutput.h" #include "AudioOutputSample.h" #include "AudioOutputToken.h" @@ -370,14 +371,18 @@ void LogConfig::browseForAudioFile() { void LogConfig::on_qsNotificationVolume_valueChanged(int value) { qsbNotificationVolume->setValue(value); + Mumble::Accessibility::setSliderSemanticValue(qsNotificationVolume, QString("%1 %2").arg(value).arg(tr("decibel"))); } void LogConfig::on_qsCueVolume_valueChanged(int value) { qsbCueVolume->setValue(value); + Mumble::Accessibility::setSliderSemanticValue(qsCueVolume, QString("%1 %2").arg(value).arg(tr("decibel"))); } void LogConfig::on_qsTTSVolume_valueChanged(int value) { qsbTTSVolume->setValue(value); + Mumble::Accessibility::setSliderSemanticValue(qsTTSVolume, Mumble::Accessibility::SliderAccesibilityMode::PERCENT, + "%"); } void LogConfig::on_qsbNotificationVolume_valueChanged(int value) { diff --git a/src/mumble/Log.ui b/src/mumble/Log.ui index fa5da2aed4c..5bf0e46fd78 100644 --- a/src/mumble/Log.ui +++ b/src/mumble/Log.ui @@ -201,7 +201,7 @@
- + Volume adjustment for audio cues @@ -267,7 +267,7 @@ - + Volume adjustment for notification sounds @@ -301,7 +301,7 @@ - + Volume of Text-To-Speech Engine @@ -497,6 +497,13 @@ The setting only applies for new messages, the already shown ones will retain th + + + SemanticSlider + QSlider +
widgets/SemanticSlider.h
+
+
qtwMessages qcbEnableTTS diff --git a/src/mumble/MainWindow.cpp b/src/mumble/MainWindow.cpp index 2deaf3b0cd3..5647059785e 100644 --- a/src/mumble/MainWindow.cpp +++ b/src/mumble/MainWindow.cpp @@ -66,6 +66,7 @@ # include "AppNap.h" #endif +#include #include #include #include @@ -80,6 +81,8 @@ #include #include +#include "widgets/SemanticSlider.h" + #ifdef Q_OS_WIN # include #endif @@ -197,6 +200,8 @@ MainWindow::MainWindow(QWidget *p) QObject::connect(this, &MainWindow::serverSynchronized, Global::get().pluginManager, &PluginManager::on_serverSynchronized); + + QAccessible::installFactory(AccessibleSlider::semanticSliderFactory); } void MainWindow::createActions() { diff --git a/src/mumble/widgets/SemanticSlider.cpp b/src/mumble/widgets/SemanticSlider.cpp new file mode 100644 index 00000000000..7a89035d819 --- /dev/null +++ b/src/mumble/widgets/SemanticSlider.cpp @@ -0,0 +1,78 @@ +// Copyright 2023-2024 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#include "SemanticSlider.h" + +#include + +SemanticSlider::SemanticSlider(QWidget *parent) +: QSlider(parent), m_semanticValue("") +{ +} + +AccessibleAbstractSlider::AccessibleAbstractSlider(QWidget *w, QAccessible::Role r) + : QAccessibleWidget(w, r) +{ + Q_ASSERT(qobject_cast(w)); +} + +void *AccessibleAbstractSlider::interface_cast(QAccessible::InterfaceType t) +{ + return QAccessibleWidget::interface_cast(t); +} + +QAbstractSlider *AccessibleAbstractSlider::abstractSlider() const +{ + return static_cast(object()); +} + +QString AccessibleAbstractSlider::text(QAccessible::Text t) const +{ + if (t == QAccessible::Name) { + qDebug() << "send parent name " << QString::number(abstractSlider()->value()); + return QAccessibleWidget::text(t) + " " + QString::number(abstractSlider()->value()); + } + + return QAccessibleWidget::text(t); +} + +AccessibleSlider::AccessibleSlider(QWidget *w) +: AccessibleAbstractSlider(w) +{ + Q_ASSERT(slider()); + addControllingSignal(QLatin1String("valueChanged(int)")); +} + +SemanticSlider *AccessibleSlider::slider() const +{ + return qobject_cast(object()); +} + +QString AccessibleSlider::text(QAccessible::Text t) const +{ + QString semanticValue = slider()->m_semanticValue; + if (t == QAccessible::Name && !semanticValue.isEmpty()) { + qDebug() << "send modified name"; + qDebug() << (AccessibleAbstractSlider::text(t) + " " + slider()->m_semanticValue); + return AccessibleAbstractSlider::text(t) + " " + slider()->m_semanticValue; + } + + return AccessibleAbstractSlider::text(t); +} + +QAccessibleInterface *AccessibleSlider::semanticSliderFactory(const QString &classname, QObject *object) { + QAccessibleInterface *interface = nullptr; + + if(object && object->isWidgetType()) { + if (classname == QLatin1String("SemanticSlider")) { + interface = new AccessibleSlider(static_cast(object)); + qDebug() << "created interface"; + } else { + qDebug() << "not interfaced"; + } + } + + return interface; +} diff --git a/src/mumble/widgets/SemanticSlider.h b/src/mumble/widgets/SemanticSlider.h new file mode 100644 index 00000000000..f0ce0f594c6 --- /dev/null +++ b/src/mumble/widgets/SemanticSlider.h @@ -0,0 +1,53 @@ +// Copyright 2023-2024 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#ifndef MUMBLE_MUMBLE_WIDGETS_SEMANTICSLIDER_H_ +#define MUMBLE_MUMBLE_WIDGETS_SEMANTICSLIDER_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +class SemanticSlider: public QSlider +{ + Q_OBJECT; +public: + SemanticSlider(QWidget *parent = nullptr); + + QString m_semanticValue; +}; + +class AccessibleAbstractSlider: public QAccessibleWidget +{ +public: + explicit AccessibleAbstractSlider(QWidget *w, QAccessible::Role r = QAccessible::Slider); + void *interface_cast(QAccessible::InterfaceType t) override; + QString text(QAccessible::Text t) const override; + +protected: + QAbstractSlider *abstractSlider() const; +}; + +class AccessibleSlider : public AccessibleAbstractSlider +{ +public: + explicit AccessibleSlider(QWidget *w); + QString text(QAccessible::Text t) const override; + + static QAccessibleInterface *semanticSliderFactory(const QString &classname, QObject *object); + +protected: + SemanticSlider *slider() const; +}; + +#endif