From b5843caa484068d6fcc5f5fe8ee2dc06078500ff Mon Sep 17 00:00:00 2001 From: VodBox Date: Wed, 4 Sep 2019 08:58:53 +1200 Subject: [PATCH] UI: Various screen reader fixes This commit fixes various issues with screen readers in the main OBS interface. These were tested using NVDA on Windows 10 1903. Checkboxes or buttons which toggle, when receiving an activate signal from the screen reader would visually update, but not perform any action. This is because they're listening only for clicks. They should all now be listening for toggles instead. The screen reader would navigate through the UI in the order that elements are defined in the .ui XML, and not by their row positions. The XML has been reordered so that things should be defined in their row order. Audio track selection now says Track 1, 2, etc, rather than just the number. Various checkboxes that just say "Enable" now have accessible text that says what the enable is for (since it says "checkbox", the fact it's an enable should hopefully be clear). Type in the recording tab of output now has accessible text which says "Recording Type". All the right side buttons in hotkeys now have tooltips, and by extension, accessible text. Currently it does not yet say what hotkey the action is in relation to, but that would require more locales. --- UI/data/locale/en-US.ini | 11 +- UI/forms/OBSBasicSettings.ui | 208 +++++++++++++++++------------------ UI/hotkey-edit.cpp | 2 + UI/obs-app.cpp | 15 +++ UI/slider-ignorewheel.cpp | 80 ++++++++++++++ UI/slider-ignorewheel.hpp | 34 ++++++ UI/source-tree.cpp | 10 +- UI/volume-control.cpp | 8 +- UI/window-basic-main.cpp | 4 +- UI/window-basic-settings.cpp | 65 +++++++++-- 10 files changed, 315 insertions(+), 122 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 096c860d1..d93b16593 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -390,8 +390,8 @@ Deinterlacing.TopFieldFirst="Top Field First" Deinterlacing.BottomFieldFirst="Bottom Field First" # volume control accessibility text -VolControl.SliderUnmuted="Volume slider for '%1': %2" -VolControl.SliderMuted="Volume slider for '%1': %2 (currently muted)" +VolControl.SliderUnmuted="Volume slider for '%1':" +VolControl.SliderMuted="Volume slider for '%1': (currently muted)" VolControl.Mute="Mute '%1'" VolControl.Properties="Properties for '%1'" @@ -430,6 +430,12 @@ Basic.SourceSelect.CreateNew="Create new" Basic.SourceSelect.AddExisting="Add Existing" Basic.SourceSelect.AddVisible="Make source visible" +# source box +Basic.Main.Sources.Visibility="Visibility" +Basic.Main.Sources.VisibilityDescription="Controls the visibility of '%1' in the canvas" +Basic.Main.Sources.Lock="Lock" +Basic.Main.Sources.LockDescription="Locks the position and scale of '%1' in the canvas" + # properties window Basic.PropertiesWindow="Properties for '%1'" Basic.PropertiesWindow.AutoSelectFormat="%1 (autoselect: %2)" @@ -733,6 +739,7 @@ Basic.Settings.Output.Adv.Audio.Track6="Track 6" # basic mode 'output' settings - advanced section - recording subsection Basic.Settings.Output.Adv.Recording="Recording" +Basic.Settings.Output.Adv.Recording.RecType="Recording Type" Basic.Settings.Output.Adv.Recording.Type="Type" Basic.Settings.Output.Adv.Recording.Type.Standard="Standard" Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Custom Output (FFmpeg)" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index a10e48277..e00325843 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -371,33 +371,13 @@ - - + + - Basic.Settings.General.ScreenSnapping + Basic.Settings.General.SnapDistance - - true - - - - - - - Basic.Settings.General.CenterSnapping - - - true - - - - - - - Basic.Settings.General.SourceSnapping - - - true + + snapDistance @@ -414,13 +394,13 @@ - - + + - Basic.Settings.General.SnapDistance + Basic.Settings.General.ScreenSnapping - - snapDistance + + true @@ -440,6 +420,26 @@ + + + + Basic.Settings.General.SourceSnapping + + + true + + + + + + + Basic.Settings.General.CenterSnapping + + + true + + + @@ -568,13 +568,6 @@ 2 - - - - Basic.Settings.General.OverflowAlwaysVisible - - - @@ -588,13 +581,6 @@ - - - - Basic.Settings.General.OverflowSelectionHidden - - - @@ -602,6 +588,20 @@ + + + + Basic.Settings.General.OverflowAlwaysVisible + + + + + + + Basic.Settings.General.OverflowSelectionHidden + + + @@ -721,9 +721,6 @@ - - - @@ -734,6 +731,9 @@ + + + @@ -4636,6 +4636,16 @@ + + + + Basic.Settings.Advanced.Video.ColorSpace + + + colorSpace + + + @@ -4685,16 +4695,6 @@ - - - - Basic.Settings.Advanced.Video.ColorSpace - - - colorSpace - - - @@ -4746,6 +4746,23 @@ + + + + Basic.Settings.Advanced.AutoRemux + + + + + + + Basic.Settings.Output.ReplayBuffer.Prefix + + + simpleRBPrefix + + + @@ -4778,16 +4795,6 @@ - - - - Basic.Settings.Output.ReplayBuffer.Prefix - - - simpleRBPrefix - - - @@ -4801,13 +4808,6 @@ - - - - Basic.Settings.Advanced.AutoRemux - - - @@ -4839,6 +4839,16 @@ 2 + + + + Enable + + + true + + + @@ -4915,16 +4925,6 @@ - - - - Enable - - - true - - - @@ -4966,6 +4966,16 @@ + + + + Basic.Settings.Output.RetryDelay + + + reconnectRetryDelay + + + @@ -5018,16 +5028,6 @@ - - - - Basic.Settings.Output.RetryDelay - - - reconnectRetryDelay - - - @@ -5072,6 +5072,16 @@ + + + + Basic.Settings.Output.DynamicBitrate.TT + + + Basic.Settings.Output.DynamicBitrate.Beta + + + @@ -5102,16 +5112,6 @@ - - - - Basic.Settings.Output.DynamicBitrate.TT - - - Basic.Settings.Output.DynamicBitrate.Beta - - - diff --git a/UI/hotkey-edit.cpp b/UI/hotkey-edit.cpp index 37e5a7d4d..2ee0cc39b 100644 --- a/UI/hotkey-edit.cpp +++ b/UI/hotkey-edit.cpp @@ -293,11 +293,13 @@ void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx) auto add = new QPushButton; add->setProperty("themeID", "addIconSmall"); + add->setToolTip(QTStr("Add")); add->setFixedSize(24, 24); add->setFlat(true); auto remove = new QPushButton; remove->setProperty("themeID", "removeIconSmall"); + remove->setToolTip(QTStr("Remove")); remove->setEnabled(removeButtons.size() > 0); remove->setFixedSize(24, 24); remove->setFlat(true); diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 46967e650..5051b433d 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -34,9 +34,11 @@ #include #include #include +#include #include "qt-wrappers.hpp" #include "obs-app.hpp" +#include "slider-ignorewheel.hpp" #include "window-basic-main.hpp" #include "window-basic-settings.hpp" #include "crash-report.hpp" @@ -1720,6 +1722,17 @@ static auto ProfilerFree = [](void *) { profiler_free(); }; +QAccessibleInterface *accessibleFactory(const QString &classname, + QObject *object) +{ + if (classname == QLatin1String("VolumeSlider") && object && + object->isWidgetType()) + return new VolumeAccessibleInterface( + static_cast(object)); + + return nullptr; +} + static const char *run_program_init = "run_program_init"; static int run_program(fstream &logFile, int argc, char *argv[]) { @@ -1743,6 +1756,8 @@ static int run_program(fstream &logFile, int argc, char *argv[]) OBSApp program(argc, argv, profilerNameStore.get()); try { + QAccessible::installFactory(accessibleFactory); + bool created_log = false; program.AppInit(); diff --git a/UI/slider-ignorewheel.cpp b/UI/slider-ignorewheel.cpp index 8203c81f9..dea80c18f 100644 --- a/UI/slider-ignorewheel.cpp +++ b/UI/slider-ignorewheel.cpp @@ -1,4 +1,5 @@ #include "slider-ignorewheel.hpp" +#include "volume-control.hpp" SliderIgnoreScroll::SliderIgnoreScroll(QWidget *parent) : QSlider(parent) { @@ -20,3 +21,82 @@ void SliderIgnoreScroll::wheelEvent(QWheelEvent *event) else QSlider::wheelEvent(event); } + +VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) + : SliderIgnoreScroll(parent) +{ + fad = fader; +} + +VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, + QWidget *parent) + : SliderIgnoreScroll(orientation, parent) +{ + fad = fader; +} + +VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w) + : QAccessibleWidget(w) +{ +} + +VolumeSlider *VolumeAccessibleInterface::slider() const +{ + return qobject_cast(object()); +} + +QString VolumeAccessibleInterface::text(QAccessible::Text t) const +{ + if (slider()->isVisible()) { + switch (t) { + case QAccessible::Text::Value: + return currentValue().toString(); + default: + break; + } + } + return QAccessibleWidget::text(t); +} + +QVariant VolumeAccessibleInterface::currentValue() const +{ + QString text; + float db = obs_fader_get_db(slider()->fad); + + if (db < -96.0f) + text = "-inf dB"; + else + text = QString::number(db, 'f', 1).append(" dB"); + + return text; +} + +void VolumeAccessibleInterface::setCurrentValue(const QVariant &value) +{ + slider()->setValue(value.toInt()); +} + +QVariant VolumeAccessibleInterface::maximumValue() const +{ + return slider()->maximum(); +} + +QVariant VolumeAccessibleInterface::minimumValue() const +{ + return slider()->minimum(); +} + +QVariant VolumeAccessibleInterface::minimumStepSize() const +{ + return slider()->singleStep(); +} + +QAccessible::Role VolumeAccessibleInterface::role() const +{ + return QAccessible::Role::Slider; +} + +/**QAccessible::State VolumeAccessibleInterface::state() const +{ + return QAccessible::State:: +}**/ diff --git a/UI/slider-ignorewheel.hpp b/UI/slider-ignorewheel.hpp index f5c7e5d72..40d04487c 100644 --- a/UI/slider-ignorewheel.hpp +++ b/UI/slider-ignorewheel.hpp @@ -1,8 +1,10 @@ #pragma once +#include "obs.hpp" #include #include #include +#include class SliderIgnoreScroll : public QSlider { Q_OBJECT @@ -15,3 +17,35 @@ public: protected: virtual void wheelEvent(QWheelEvent *event) override; }; + +class VolumeSlider : public SliderIgnoreScroll { + Q_OBJECT + +public: + obs_fader_t *fad; + + VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr); + VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, + QWidget *parent = nullptr); +}; + +class VolumeAccessibleInterface : public QAccessibleWidget { + +public: + VolumeAccessibleInterface(QWidget *w); + + QVariant currentValue() const; + void setCurrentValue(const QVariant &value); + + QVariant maximumValue() const; + QVariant minimumValue() const; + + QVariant minimumStepSize() const; + +private: + VolumeSlider *slider() const; + +protected: + virtual QAccessible::Role role() const override; + virtual QString text(QAccessible::Text t) const override; +}; diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp index 4032f3a3e..083a85de5 100644 --- a/UI/source-tree.cpp +++ b/UI/source-tree.cpp @@ -62,12 +62,18 @@ SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_) vis->setFixedSize(16, 16); vis->setChecked(obs_sceneitem_visible(sceneitem)); vis->setStyleSheet("background: none"); + vis->setAccessibleName(QTStr("Basic.Main.Sources.Visibility")); + vis->setAccessibleDescription( + QTStr("Basic.Main.Sources.VisibilityDescription").arg(name)); lock = new LockedCheckBox(); lock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); lock->setFixedSize(16, 16); lock->setChecked(obs_sceneitem_locked(sceneitem)); lock->setStyleSheet("background: none"); + lock->setAccessibleName(QTStr("Basic.Main.Sources.Lock")); + lock->setAccessibleDescription( + QTStr("Basic.Main.Sources.LockDescription").arg(name)); label = new QLabel(QT_UTF8(name)); label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); @@ -107,8 +113,8 @@ SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_) obs_sceneitem_set_locked(sceneitem, checked); }; - connect(vis, &QAbstractButton::clicked, setItemVisible); - connect(lock, &QAbstractButton::clicked, setItemLocked); + connect(vis, &QAbstractButton::toggled, setItemVisible); + connect(lock, &QAbstractButton::toggled, setItemLocked); } void SourceTreeItem::paintEvent(QPaintEvent *event) diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index 35fdb0c2a..51d5bc4d5 100644 --- a/UI/volume-control.cpp +++ b/UI/volume-control.cpp @@ -89,7 +89,7 @@ void VolControl::updateText() : "VolControl.SliderUnmuted"; QString sourceName = obs_source_get_name(source); - QString accText = QTStr(accTextLookup).arg(sourceName, db); + QString accText = QTStr(accTextLookup).arg(sourceName); slider->setAccessibleName(accText); } @@ -161,7 +161,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) QHBoxLayout *meterLayout = new QHBoxLayout; volMeter = new VolumeMeter(nullptr, obs_volmeter, true); - slider = new SliderIgnoreScroll(Qt::Vertical); + slider = new VolumeSlider(obs_fader, Qt::Vertical); nameLayout->setAlignment(Qt::AlignCenter); meterLayout->setAlignment(Qt::AlignCenter); @@ -205,7 +205,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) QHBoxLayout *botLayout = new QHBoxLayout; volMeter = new VolumeMeter(nullptr, obs_volmeter, false); - slider = new SliderIgnoreScroll(Qt::Horizontal); + slider = new VolumeSlider(obs_fader, Qt::Horizontal); textLayout->setContentsMargins(0, 0, 0, 0); textLayout->addWidget(nameLabel); @@ -254,7 +254,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) QWidget::connect(slider, SIGNAL(valueChanged(int)), this, SLOT(SliderChanged(int))); - QWidget::connect(mute, SIGNAL(clicked(bool)), this, + QWidget::connect(mute, SIGNAL(toggled(bool)), this, SLOT(SetMuted(bool))); obs_fader_attach_source(obs_fader, source); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 0980dd297..9f503b32d 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1486,7 +1486,7 @@ void OBSBasic::ResetOutputs() QTStr("Basic.Main.StartReplayBuffer"), this); replayBufferButton->setCheckable(true); connect(replayBufferButton.data(), - &QPushButton::clicked, this, + &QPushButton::toggled, this, &OBSBasic::ReplayBufferClicked); replayBufferButton->setProperty("themeID", @@ -7506,7 +7506,7 @@ void OBSBasic::UpdatePause(bool activate) pause->setChecked(false); pause->setProperty("themeID", QVariant(QStringLiteral("pauseIconSmall"))); - connect(pause.data(), &QAbstractButton::clicked, this, + connect(pause.data(), &QAbstractButton::toggled, this, &OBSBasic::PauseToggled); ui->recordingLayout->addWidget(pause.data()); } else { diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 4d10e72d2..1a2d29c37 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -260,10 +260,9 @@ void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, #define COMBO_CHANGED SIGNAL(currentIndexChanged(int)) #define EDIT_CHANGED SIGNAL(textChanged(const QString &)) #define CBEDIT_CHANGED SIGNAL(editTextChanged(const QString &)) -#define CHECK_CHANGED SIGNAL(clicked(bool)) +#define CHECK_CHANGED SIGNAL(toggled(bool)) #define SCROLL_CHANGED SIGNAL(valueChanged(int)) #define DSCROLL_CHANGED SIGNAL(valueChanged(double)) -#define TOGGLE_CHANGED SIGNAL(toggled(bool)) #define GENERAL_CHANGED SLOT(GeneralChanged()) #define STREAM1_CHANGED SLOT(Stream1Changed()) @@ -726,18 +725,68 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) InitStreamPage(); LoadSettings(false); + ui->advOutTrack1->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track1")); + ui->advOutTrack2->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track2")); + ui->advOutTrack3->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track3")); + ui->advOutTrack4->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track4")); + ui->advOutTrack5->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track5")); + ui->advOutTrack6->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track6")); + + ui->advOutRecTrack1->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track1")); + ui->advOutRecTrack2->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track2")); + ui->advOutRecTrack3->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track3")); + ui->advOutRecTrack4->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track4")); + ui->advOutRecTrack5->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track5")); + ui->advOutRecTrack6->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track6")); + + ui->advOutFFTrack1->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track1")); + ui->advOutFFTrack2->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track2")); + ui->advOutFFTrack3->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track3")); + ui->advOutFFTrack4->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track4")); + ui->advOutFFTrack5->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track5")); + ui->advOutFFTrack6->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track6")); + + ui->snappingEnabled->setAccessibleName( + QTStr("Basic.Settings.General.Snapping")); + ui->systemTrayEnabled->setAccessibleName( + QTStr("Basic.Settings.General.SysTray")); + ui->label_31->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Recording.RecType")); + ui->streamDelayEnable->setAccessibleName( + QTStr("Basic.Settings.Advanced.StreamDelay")); + ui->reconnectEnable->setAccessibleName( + QTStr("Basic.Settings.Output.Reconnect")); + // Add warning checks to advanced output recording section controls - connect(ui->advOutRecTrack1, SIGNAL(clicked()), this, + connect(ui->advOutRecTrack1, SIGNAL(toggled()), this, SLOT(AdvOutRecCheckWarnings())); - connect(ui->advOutRecTrack2, SIGNAL(clicked()), this, + connect(ui->advOutRecTrack2, SIGNAL(toggled()), this, SLOT(AdvOutRecCheckWarnings())); - connect(ui->advOutRecTrack3, SIGNAL(clicked()), this, + connect(ui->advOutRecTrack3, SIGNAL(toggled()), this, SLOT(AdvOutRecCheckWarnings())); - connect(ui->advOutRecTrack4, SIGNAL(clicked()), this, + connect(ui->advOutRecTrack4, SIGNAL(toggled()), this, SLOT(AdvOutRecCheckWarnings())); - connect(ui->advOutRecTrack5, SIGNAL(clicked()), this, + connect(ui->advOutRecTrack5, SIGNAL(toggled()), this, SLOT(AdvOutRecCheckWarnings())); - connect(ui->advOutRecTrack6, SIGNAL(clicked()), this, + connect(ui->advOutRecTrack6, SIGNAL(toggled()), this, SLOT(AdvOutRecCheckWarnings())); connect(ui->advOutRecFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(AdvOutRecCheckWarnings()));