From 6685fbf80795e2cbc39799eb0ed31965d55a160b Mon Sep 17 00:00:00 2001 From: Hartmnt Date: Wed, 17 Jan 2024 18:29:03 +0000 Subject: [PATCH] FIX(a11y): Improve Accessibility for CertWizard The certificate wizard shows boxes with certificate info. These boxes are created by using labels in a QGroupBox. However, this information is not easily readable using a screen reader (especially Orca). This commit introduces the AccessibleGroupBox class which aims to solve that by dynamically setting the accessible description using layout information of the labels inside the box. Fixes #1337 --- src/mumble/CMakeLists.txt | 2 + src/mumble/Cert.cpp | 9 ++- src/mumble/Cert.h | 3 +- src/mumble/widgets/AccessibleQGroupBox.cpp | 90 ++++++++++++++++++++++ src/mumble/widgets/AccessibleQGroupBox.h | 20 +++++ 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/mumble/widgets/AccessibleQGroupBox.cpp create mode 100644 src/mumble/widgets/AccessibleQGroupBox.h diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt index e0bb39234..75a63bcf9 100644 --- a/src/mumble/CMakeLists.txt +++ b/src/mumble/CMakeLists.txt @@ -289,6 +289,8 @@ set(MUMBLE_SOURCES "XMLTools.cpp" "XMLTools.h" + "widgets/AccessibleQGroupBox.cpp" + "widgets/AccessibleQGroupBox.h" "widgets/CompletablePage.cpp" "widgets/CompletablePage.h" "widgets/EventFilters.cpp" diff --git a/src/mumble/Cert.cpp b/src/mumble/Cert.cpp index 886e87b99..580e4739a 100644 --- a/src/mumble/Cert.cpp +++ b/src/mumble/Cert.cpp @@ -18,6 +18,7 @@ #include "Utils.h" #include "Global.h" +#include #include #include #include @@ -29,7 +30,7 @@ #define SSL_STRING(x) QString::fromLatin1(x).toUtf8().data() -CertView::CertView(QWidget *p) : QGroupBox(p) { +CertView::CertView(QWidget *p) : AccessibleQGroupBox(p) { QGridLayout *grid = new QGridLayout(this); QLabel *l; @@ -65,6 +66,8 @@ CertView::CertView(QWidget *p) : QGroupBox(p) { grid->addWidget(qlExpiry, 3, 1, 1, 1); grid->setColumnStretch(1, 1); + + updateAccessibleText(); } void CertView::setCert(const QList< QSslCertificate > &cert) { @@ -116,6 +119,8 @@ void CertView::setCert(const QList< QSslCertificate > &cert) { qlIssuerName->setText((issuerName == name) ? tr("Self-signed") : issuerName); } + + updateAccessibleText(); } CertWizard::CertWizard(QWidget *p) : QWizard(p) { @@ -144,6 +149,8 @@ CertWizard::CertWizard(QWidget *p) : QWizard(p) { installEventFilter(m_overrideFilter); connect(this, &CertWizard::currentIdChanged, this, &CertWizard::showPage); + + QTimer::singleShot(0, [this] { this->showPage(0); }); } int CertWizard::nextId() const { diff --git a/src/mumble/Cert.h b/src/mumble/Cert.h index 94f7182d1..cd4b78e86 100644 --- a/src/mumble/Cert.h +++ b/src/mumble/Cert.h @@ -14,12 +14,13 @@ #include #include "Settings.h" +#include "widgets/AccessibleQGroupBox.h" #include "widgets/EventFilters.h" class QLabel; class QWidget; -class CertView : public QGroupBox { +class CertView : public AccessibleQGroupBox { private: Q_OBJECT Q_DISABLE_COPY(CertView) diff --git a/src/mumble/widgets/AccessibleQGroupBox.cpp b/src/mumble/widgets/AccessibleQGroupBox.cpp new file mode 100644 index 000000000..68de27189 --- /dev/null +++ b/src/mumble/widgets/AccessibleQGroupBox.cpp @@ -0,0 +1,90 @@ +// Copyright 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 "AccessibleQGroupBox.h" + +#include +#include +#include + +AccessibleQGroupBox::AccessibleQGroupBox(QWidget *parent) : QGroupBox(parent) { + setFocusPolicy(Qt::TabFocus); + updateAccessibleText(); +} + +void AccessibleQGroupBox::updateAccessibleText() { + QString text; + + // If we have a grid layout, we iterate over it top-to-bottom left-to-right + // and concat all the label contents. + QGridLayout *gridLayout = qobject_cast< QGridLayout * >(layout()); + if (gridLayout) { + for (int y = 0; y < gridLayout->rowCount(); y++) { + for (int x = 0; x < gridLayout->columnCount(); x++) { + QLabel *label = qobject_cast< QLabel * >(gridLayout->itemAtPosition(y, x)->widget()); + if (!label) { + continue; + } + + text += " "; + + QString content = label->text(); + if (content.trimmed().isEmpty()) { + content = tr("empty"); + } + text += content; + } + text += ","; + } + setAccessibleDescription(text); + return; + } + + // Form layouts contain rows with 1 or 2 items. It can either contain a + // "spanning role", or a label with a field. + QFormLayout *formLayout = qobject_cast< QFormLayout * >(layout()); + if (formLayout) { + for (int y = 0; y < formLayout->rowCount(); y++) { + QLabel *label; + + label = qobject_cast< QLabel * >(formLayout->itemAt(y, QFormLayout::SpanningRole)->widget()); + if (label) { + text += label->text() + ", "; + continue; + } + + label = qobject_cast< QLabel * >(formLayout->itemAt(y, QFormLayout::LabelRole)->widget()); + if (label) { + text += label->text(); + } + + label = qobject_cast< QLabel * >(formLayout->itemAt(y, QFormLayout::FieldRole)->widget()); + if (label) { + QString content = label->text(); + if (content.trimmed().isEmpty()) { + content = tr("empty"); + } + text += " " + content; + } + + text += ", "; + } + setAccessibleDescription(text); + return; + } + + // There is no layout we can easily parse, so just concat all + // child label texts. + QObjectList childObjects = children(); + for (int i = 0; i < childObjects.length(); i++) { + QLabel *label = qobject_cast< QLabel * >(childObjects[i]); + if (!label) { + continue; + } + + text += label->text() + ","; + } + setAccessibleDescription(text); +} diff --git a/src/mumble/widgets/AccessibleQGroupBox.h b/src/mumble/widgets/AccessibleQGroupBox.h new file mode 100644 index 000000000..9e9a5de75 --- /dev/null +++ b/src/mumble/widgets/AccessibleQGroupBox.h @@ -0,0 +1,20 @@ +// Copyright 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_ACCESSIBLEQGROUPBOX_H_ +#define MUMBLE_MUMBLE_ACCESSIBLEQGROUPBOX_H_ + +#include + +class AccessibleQGroupBox : public QGroupBox { + Q_OBJECT + +public: + AccessibleQGroupBox(QWidget *parent); + + void updateAccessibleText(); +}; + +#endif