diff --git a/UI/adv-audio-control.cpp b/UI/adv-audio-control.cpp index f26a558c6..862d746f7 100644 --- a/UI/adv-audio-control.cpp +++ b/UI/adv-audio-control.cpp @@ -22,7 +22,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) { QHBoxLayout *hlayout; signal_handler_t *handler = obs_source_get_signal_handler(source); - const char *sourceName = obs_source_get_name(source); + QString sourceName = QT_UTF8(obs_source_get_name(source)); float vol = obs_source_get_volume(source); uint32_t flags = obs_source_get_flags(source); uint32_t mixers = obs_source_get_audio_mixers(source); @@ -90,7 +90,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) iconLabel->setFixedSize(16, 16); iconLabel->setStyleSheet("background: none"); - nameLabel->setText(QT_UTF8(sourceName)); + nameLabel->setText(sourceName); nameLabel->setAlignment(Qt::AlignVCenter); bool isActive = obs_source_active(source); @@ -109,15 +109,21 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) volume->setSuffix(" dB"); volume->setValue(obs_mul_to_db(vol)); volume->setFixedWidth(100); + volume->setAccessibleName( + QTStr("Basic.AdvAudio.VolumeSource").arg(sourceName)); - if (volume->value() < MIN_DB) + if (volume->value() < MIN_DB) { volume->setSpecialValueText("-inf dB"); + volume->setAccessibleDescription("-inf dB"); + } percent->setMinimum(0); percent->setMaximum(2000); percent->setSuffix("%"); percent->setValue((int)(obs_source_get_volume(source) * 100.0f)); percent->setFixedWidth(100); + percent->setAccessibleName( + QTStr("Basic.AdvAudio.VolumeSource").arg(sourceName)); stackedWidget->addWidget(volume); stackedWidget->addWidget(percent); @@ -128,6 +134,8 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) SetVolumeWidget(volType); forceMono->setChecked((flags & OBS_SOURCE_FLAG_FORCE_MONO) != 0); + forceMono->setAccessibleName( + QTStr("Basic.AdvAudio.MonoSource").arg(sourceName)); forceMonoContainer->layout()->addWidget(forceMono); forceMonoContainer->layout()->setAlignment(forceMono, Qt::AlignVCenter); @@ -138,6 +146,8 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) balance->setMaximum(100); balance->setTickPosition(QSlider::TicksAbove); balance->setTickInterval(50); + balance->setAccessibleName( + QTStr("Basic.AdvAudio.BalanceSource").arg(sourceName)); const char *speakers = config_get_string(main->Config(), "Audio", "ChannelSetup"); @@ -156,6 +166,8 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) syncOffset->setSuffix(" ms"); syncOffset->setValue(int(cur_sync / NSEC_PER_MSEC)); syncOffset->setFixedWidth(100); + syncOffset->setAccessibleName( + QTStr("Basic.AdvAudio.SyncOffsetSource").arg(sourceName)); int idx; #if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO @@ -168,20 +180,34 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) int mt = (int)obs_source_get_monitoring_type(source); idx = monitoringType->findData(mt); monitoringType->setCurrentIndex(idx); + monitoringType->setAccessibleName( + QTStr("Basic.AdvAudio.MonitoringSource").arg(sourceName)); #endif mixer1->setText("1"); mixer1->setChecked(mixers & (1 << 0)); + mixer1->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track1")); mixer2->setText("2"); mixer2->setChecked(mixers & (1 << 1)); + mixer2->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track2")); mixer3->setText("3"); mixer3->setChecked(mixers & (1 << 2)); + mixer3->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track3")); mixer4->setText("4"); mixer4->setChecked(mixers & (1 << 3)); + mixer4->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track4")); mixer5->setText("5"); mixer5->setChecked(mixers & (1 << 4)); + mixer5->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track5")); mixer6->setText("6"); mixer6->setChecked(mixers & (1 << 5)); + mixer6->setAccessibleName( + QTStr("Basic.Settings.Output.Adv.Audio.Track6")); speaker_layout sl = obs_source_get_speaker_layout(source); diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 4c445023e..826b19f1b 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -408,8 +408,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'" @@ -449,6 +449,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)" @@ -784,6 +790,7 @@ Basic.Settings.Output.Adv.TwitchVodTrack="Twitch VOD Track" # 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)" @@ -919,13 +926,18 @@ Basic.AdvAudio="Advanced Audio Properties" Basic.AdvAudio.ActiveOnly="Active Sources Only" Basic.AdvAudio.Name="Name" Basic.AdvAudio.Volume="Volume" +Basic.AdvAudio.VolumeSource="Volume for '%1'" Basic.AdvAudio.Mono="Mono" +Basic.AdvAudio.MonoSource="Mono Downmix for '%1'" Basic.AdvAudio.Balance="Balance" +Basic.AdvAudio.BalanceSource="Balance for '%1'" Basic.AdvAudio.SyncOffset="Sync Offset" +Basic.AdvAudio.SyncOffsetSource="Sync Offset for '%1'" Basic.AdvAudio.Monitoring="Audio Monitoring" Basic.AdvAudio.Monitoring.None="Monitor Off" Basic.AdvAudio.Monitoring.MonitorOnly="Monitor Only (mute output)" Basic.AdvAudio.Monitoring.Both="Monitor and Output" +Basic.AdvAudio.MonitoringSource="Audio Monitoring for '%1'" Basic.AdvAudio.AudioTracks="Tracks" # basic mode 'hotkeys' settings diff --git a/UI/forms/OBSBasicFilters.ui b/UI/forms/OBSBasicFilters.ui index 572a31f9d..272dfc21e 100644 --- a/UI/forms/OBSBasicFilters.ui +++ b/UI/forms/OBSBasicFilters.ui @@ -104,6 +104,9 @@ addIconSmall + + Add + @@ -130,6 +133,9 @@ removeIconSmall + + Remove + @@ -156,6 +162,9 @@ upArrowIconSmall + + MoveUp + @@ -182,6 +191,9 @@ downArrowIconSmall + + MoveDown + @@ -280,6 +292,9 @@ addIconSmall + + Add + @@ -306,6 +321,9 @@ removeIconSmall + + Remove + 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 58dcef2f7..ab7ee45ec 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -35,10 +35,12 @@ #include #include #include +#include #include "qt-wrappers.hpp" #include "obs-app.hpp" #include "log-viewer.hpp" +#include "slider-ignorewheel.hpp" #include "window-basic-main.hpp" #include "window-basic-settings.hpp" #include "crash-report.hpp" @@ -1901,6 +1903,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[]) { @@ -1932,6 +1945,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..11023d638 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,77 @@ 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; +} 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 d6ee35b64..34994f9f4 100644 --- a/UI/source-tree.cpp +++ b/UI/source-tree.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -84,12 +85,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); diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index 7543d153e..cfc64b6c8 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); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index fd081ce10..70237ed8c 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -817,6 +817,56 @@ 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, SLOT(AdvOutRecCheckWarnings()));