From 4b1acb290dc8869d0d2d1250dc1ed415d6b6e202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Thu, 14 Jan 2021 18:46:17 +0100 Subject: a11y: Fix ordering on header, content item and footer in Page Because of the previous behavior, the footer could be read aloud by the screen reader before the content item. And even worse, the footer could be read aloud even before the header in some cases. This made it hard for visually impaired people to use the application. The Page type was used by the Dialog type, so it also affected that. Fixes: QTBUG-75042 Pick-to: 5.15 6.0 Change-Id: Ic3e8ec3f7dcf18af9262b1d35c986835c8da6900 Reviewed-by: Mitch Curtis Reviewed-by: Volker Hilsheimer --- src/imports/templates/qtquicktemplates2plugin.cpp | 3 + src/quicktemplates2/CMakeLists.txt | 3 +- src/quicktemplates2/accessible/accessible.pri | 4 ++ .../accessible/qaccessiblequickpage.cpp | 81 ++++++++++++++++++++++ .../accessible/qaccessiblequickpage_p.h | 70 +++++++++++++++++++ src/quicktemplates2/qtquicktemplates2global.cpp | 63 +++++++++++++++++ src/quicktemplates2/qtquicktemplates2global_p.h | 4 +- src/quicktemplates2/quicktemplates2.pro | 4 ++ tests/auto/accessibility/accessibility.pro | 4 +- tests/auto/accessibility/data/ordering/page.qml | 23 ++++++ tests/auto/accessibility/tst_accessibility.cpp | 30 ++++++++ 11 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 src/quicktemplates2/accessible/accessible.pri create mode 100644 src/quicktemplates2/accessible/qaccessiblequickpage.cpp create mode 100644 src/quicktemplates2/accessible/qaccessiblequickpage_p.h create mode 100644 src/quicktemplates2/qtquicktemplates2global.cpp create mode 100644 tests/auto/accessibility/data/ordering/page.qml diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index e2a15213..732cb3f8 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -71,7 +71,10 @@ QtQuickTemplates2Plugin::QtQuickTemplates2Plugin(QObject *parent) : QQmlExtensionPlugin(parent), registered(false) { volatile auto registration = &qml_register_types_QtQuick_Templates; + volatile auto initialization = &QQuickTemplates_initializeModule; + Q_UNUSED(registration) + Q_UNUSED(initialization) } QtQuickTemplates2Plugin::~QtQuickTemplates2Plugin() diff --git a/src/quicktemplates2/CMakeLists.txt b/src/quicktemplates2/CMakeLists.txt index ff9fdb5c..1493c52b 100644 --- a/src/quicktemplates2/CMakeLists.txt +++ b/src/quicktemplates2/CMakeLists.txt @@ -7,6 +7,7 @@ qt_internal_add_module(QuickTemplates2 GENERATE_METATYPES SOURCES + accessible/qaccessiblequickpage.cpp accessible/qaccessiblequickpage_p.h qquickabstractbutton.cpp qquickabstractbutton_p.h qquickabstractbutton_p_p.h qquickaction.cpp qquickaction_p.h @@ -112,7 +113,7 @@ qt_internal_add_module(QuickTemplates2 qquicktooltip.cpp qquicktooltip_p.h qquickvelocitycalculator.cpp qquickvelocitycalculator_p_p.h - qtquicktemplates2global_p.h + qtquicktemplates2global.cpp qtquicktemplates2global_p.h DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII diff --git a/src/quicktemplates2/accessible/accessible.pri b/src/quicktemplates2/accessible/accessible.pri new file mode 100644 index 00000000..0c855d34 --- /dev/null +++ b/src/quicktemplates2/accessible/accessible.pri @@ -0,0 +1,4 @@ +HEADERS += \ + $$PWD/qaccessiblequickpage_p.h \ +SOURCES += \ + $$PWD/qaccessiblequickpage.cpp \ diff --git a/src/quicktemplates2/accessible/qaccessiblequickpage.cpp b/src/quicktemplates2/accessible/qaccessiblequickpage.cpp new file mode 100644 index 00000000..90ac49f9 --- /dev/null +++ b/src/quicktemplates2/accessible/qaccessiblequickpage.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessiblequickpage_p.h" +#include "qquickpage_p.h" + +QT_BEGIN_NAMESPACE + +QAccessibleQuickPage::QAccessibleQuickPage(QQuickPage *page) + : QAccessibleQuickItem(page) +{ +} + +QAccessibleInterface *QAccessibleQuickPage::child(int index) const +{ + const QList kids = orderedChildItems(); + if (QQuickItem *item = kids.value(index)) + return QAccessible::queryAccessibleInterface(item); + return nullptr; +} + +int QAccessibleQuickPage::indexOfChild(const QAccessibleInterface *iface) const +{ + const QList kids = orderedChildItems(); + return (int)kids.indexOf(static_cast(iface->object())); +} + +QList QAccessibleQuickPage::orderedChildItems() const +{ + // Just ensures that the header is first, and footer is last. Other existing order is kept. + const QQuickPage *p = page(); + QList kids = childItems(); + const qsizetype hidx = kids.indexOf(p->header()); + if (hidx != -1) + kids.move(hidx, 0); + const qsizetype fidx = kids.indexOf(p->footer()); + if (fidx != -1) + kids.move(fidx, kids.count() - 1); + return kids; +} + +QQuickPage *QAccessibleQuickPage::page() const +{ + return static_cast(object()); +} + +QT_END_NAMESPACE + diff --git a/src/quicktemplates2/accessible/qaccessiblequickpage_p.h b/src/quicktemplates2/accessible/qaccessiblequickpage_p.h new file mode 100644 index 00000000..9b208c14 --- /dev/null +++ b/src/quicktemplates2/accessible/qaccessiblequickpage_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEQUICKPAGE_H +#define QACCESSIBLEQUICKPAGE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class QQuickPage; + +class QAccessibleQuickPage : public QAccessibleQuickItem +{ +public: + QAccessibleQuickPage(QQuickPage *page); + QAccessibleInterface *child(int index) const override; + int indexOfChild(const QAccessibleInterface *iface) const override; +private: + QQuickPage *page() const; + QList orderedChildItems() const; +}; + +QT_END_NAMESPACE + +#endif // QACCESSIBLEQUICKPAGE_H diff --git a/src/quicktemplates2/qtquicktemplates2global.cpp b/src/quicktemplates2/qtquicktemplates2global.cpp new file mode 100644 index 00000000..9b79d60c --- /dev/null +++ b/src/quicktemplates2/qtquicktemplates2global.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qquickpage_p.h" +#include "accessible/qaccessiblequickpage_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) +QAccessibleInterface *qQuickAccessibleFactory(const QString &classname, QObject *object) +{ + if (classname == u"QQuickPage") { + return new QAccessibleQuickPage(qobject_cast(object)); + } + return nullptr; +} +#endif + +void QQuickTemplates_initializeModule() +{ +#if QT_CONFIG(accessibility) + QAccessible::installFactory(&qQuickAccessibleFactory); +#endif +} + +Q_CONSTRUCTOR_FUNCTION(QQuickTemplates_initializeModule) + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qtquicktemplates2global_p.h b/src/quicktemplates2/qtquicktemplates2global_p.h index 37d81888..db848229 100644 --- a/src/quicktemplates2/qtquicktemplates2global_p.h +++ b/src/quicktemplates2/qtquicktemplates2global_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. @@ -64,6 +64,8 @@ QT_BEGIN_NAMESPACE # define Q_QUICKTEMPLATES2_PRIVATE_EXPORT #endif +Q_QUICKTEMPLATES2_PRIVATE_EXPORT void QQuickTemplates_initializeModule(); + QT_END_NAMESPACE Q_QUICKTEMPLATES2_PRIVATE_EXPORT void qml_register_types_QtQuick_Templates(); diff --git a/src/quicktemplates2/quicktemplates2.pro b/src/quicktemplates2/quicktemplates2.pro index 06fa0ee7..890845a5 100644 --- a/src/quicktemplates2/quicktemplates2.pro +++ b/src/quicktemplates2/quicktemplates2.pro @@ -10,7 +10,11 @@ DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII HEADERS += \ $$PWD/qtquicktemplates2global_p.h +SOURCES += \ + $$PWD/qtquicktemplates2global.cpp + include(quicktemplates2.pri) +include(accessible/accessible.pri) load(qt_module) QMLTYPES_FILENAME = plugins.qmltypes diff --git a/tests/auto/accessibility/accessibility.pro b/tests/auto/accessibility/accessibility.pro index d8d5bb95..4cc101fb 100644 --- a/tests/auto/accessibility/accessibility.pro +++ b/tests/auto/accessibility/accessibility.pro @@ -12,5 +12,7 @@ include (../shared/util.pri) TESTDATA = data/* OTHER_FILES += \ - data/*.qml + data/defaults\*.qml \ + data/ordering\*.qml \ + data/override*.qml diff --git a/tests/auto/accessibility/data/ordering/page.qml b/tests/auto/accessibility/data/ordering/page.qml new file mode 100644 index 00000000..8efafe32 --- /dev/null +++ b/tests/auto/accessibility/data/ordering/page.qml @@ -0,0 +1,23 @@ +import QtQuick +import QtQuick.Controls + +Page { + title: "Page" + Accessible.role: Accessible.Pane + + header: Label { + text: "Header" + } + + footer: Label { + text: "Footer" + } + + Label { + text: "Content item 1" + } + + Label { + text: "Content item 2" + } +} diff --git a/tests/auto/accessibility/tst_accessibility.cpp b/tests/auto/accessibility/tst_accessibility.cpp index 3d9a318d..f2cb8b60 100644 --- a/tests/auto/accessibility/tst_accessibility.cpp +++ b/tests/auto/accessibility/tst_accessibility.cpp @@ -60,6 +60,7 @@ private slots: void override_data(); void override(); + void ordering(); private: QQmlEngine engine; }; @@ -268,6 +269,35 @@ void tst_accessibility::override() Q_UNUSED(text); #endif } +template +void a11yDescendants(QAccessibleInterface *iface, Predicate pred) +{ + for (int i = 0; i < iface->childCount(); ++i) { + if (QAccessibleInterface *child = iface->child(i)) { + pred(child); + a11yDescendants(child, pred); + } + } +} + +void tst_accessibility::ordering() +{ + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("ordering/page.qml")); + + QScopedPointer object(component.create()); + QVERIFY2(!object.isNull(), qPrintable(component.errorString())); + +#if QT_CONFIG(accessibility) + QQuickItem *item = findItem(object.data()); + QVERIFY(item); + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(item); + QVERIFY(iface); + QStringList strings; + a11yDescendants(iface, [&](QAccessibleInterface *iface) {strings << iface->text(QAccessible::Name);}); + QCOMPARE(strings.join(QLatin1String(", ")), "Header, Content item 1, Content item 2, Footer"); +#endif +} QTEST_MAIN(tst_accessibility) -- cgit v1.2.3