diff options
| -rw-r--r-- | src/quick/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/quick/accessible/qaccessiblequicktableview.cpp | 32 | ||||
| -rw-r--r-- | src/quick/accessible/qaccessiblequicktableview_p.h | 39 | ||||
| -rw-r--r-- | src/quick/accessible/qquickaccessiblefactory.cpp | 4 | ||||
| -rw-r--r-- | src/quick/items/qquicktableview.cpp | 49 | ||||
| -rw-r--r-- | src/quick/items/qquicktableview_p_p.h | 6 | ||||
| -rw-r--r-- | src/quickcontrols/basic/TableViewDelegate.qml | 5 |
7 files changed, 136 insertions, 0 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index 64682edc84..c938da26a4 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -654,6 +654,7 @@ qt_internal_extend_target(Quick CONDITION QT_FEATURE_accessibility accessible/qaccessiblequickview.cpp accessible/qaccessiblequickview_p.h accessible/qaccessiblequickflickable.cpp accessible/qaccessiblequickflickable_p.h accessible/qaccessiblequicklistview.cpp accessible/qaccessiblequicklistview_p.h + accessible/qaccessiblequicktableview.cpp accessible/qaccessiblequicktableview_p.h accessible/qaccessiblequickwindowcontainer.cpp accessible/qaccessiblequickwindowcontainer_p.h accessible/qquickaccessiblefactory.cpp accessible/qquickaccessiblefactory_p.h LIBRARIES diff --git a/src/quick/accessible/qaccessiblequicktableview.cpp b/src/quick/accessible/qaccessiblequicktableview.cpp new file mode 100644 index 0000000000..63f9217b0f --- /dev/null +++ b/src/quick/accessible/qaccessiblequicktableview.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#include "qaccessiblequicktableview_p.h" + +#include <QtQuick/private/qquicktableview_p_p.h> + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) + +QAccessibleQuickTableView::QAccessibleQuickTableView(QQuickTableView *tableView) + : QAccessibleQuickFlickable(tableView) +{ +} + +// The view holds the focus, but assistive technology follows the current cell. +// Platforms that ask for the focused element, rather than taking it from the +// focus event, find the delegate of that cell here. +QAccessibleInterface *QAccessibleQuickTableView::focusChild() const +{ + if (auto *view = qobject_cast<QQuickTableView *>(object())) { + if (QQuickItem *item = QQuickTableViewPrivate::get(view)->accessibleFocusItem) + return QAccessible::queryAccessibleInterface(item); + } + return nullptr; +} + +#endif // accessibility + +QT_END_NAMESPACE diff --git a/src/quick/accessible/qaccessiblequicktableview_p.h b/src/quick/accessible/qaccessiblequicktableview_p.h new file mode 100644 index 0000000000..7b26a986d2 --- /dev/null +++ b/src/quick/accessible/qaccessiblequicktableview_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#ifndef QACCESSIBLEQUICKTABLEVIEW_H +#define QACCESSIBLEQUICKTABLEVIEW_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 "qaccessiblequickflickable_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) + +class QQuickTableView; + +class Q_QUICK_EXPORT QAccessibleQuickTableView : public QAccessibleQuickFlickable +{ +public: + QAccessibleQuickTableView(QQuickTableView *tableView); + + QAccessibleInterface *focusChild() const override; +}; + +#endif // accessibility + +QT_END_NAMESPACE + +#endif // QACCESSIBLEQUICKTABLEVIEW_H diff --git a/src/quick/accessible/qquickaccessiblefactory.cpp b/src/quick/accessible/qquickaccessiblefactory.cpp index 3245398ce2..e053645d8c 100644 --- a/src/quick/accessible/qquickaccessiblefactory.cpp +++ b/src/quick/accessible/qquickaccessiblefactory.cpp @@ -10,10 +10,12 @@ #include "qaccessiblequicktextinput_p.h" #include "qaccessiblequickflickable_p.h" #include "qaccessiblequicklistview_p.h" +#include "qaccessiblequicktableview_p.h" #include "qaccessiblequickwindowcontainer_p.h" #include <QtQuick/private/qquickflickable_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquicklistview_p.h> +#include <QtQuick/private/qquicktableview_p.h> #include <QtQuick/private/qquicktextedit_p.h> #include <QtQuick/private/qquicktextinput_p.h> #include <QtQuick/private/qquickwindowcontainer_p.h> @@ -31,6 +33,8 @@ QAccessibleInterface *qQuickAccessibleFactory(const QString &classname, QObject return new QAccessibleQuickTextInput(qobject_cast<QQuickTextInput *>(object)); if (classname == QLatin1String("QQuickListView")) return new QAccessibleQuickListView(qobject_cast<QQuickListView *>(object)); + if (classname == QLatin1String("QQuickTableView")) + return new QAccessibleQuickTableView(qobject_cast<QQuickTableView *>(object)); if (classname == QLatin1String("QQuickFlickable")) return new QAccessibleQuickFlickable(qobject_cast<QQuickFlickable *>(object)); if (classname == QLatin1String("QQuickWindowContainer")) diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index db5da6f886..7e1a6809d4 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -20,6 +20,10 @@ #include <QtQuick/private/qquickitemviewfxitem_p_p.h> #include <QtQuick/private/qquicktaphandler_p.h> +#if QT_CONFIG(accessibility) +#include <QtGui/qaccessible.h> +#endif + #include <QtCore/qtyperevision.h> /*! @@ -3023,6 +3027,12 @@ void QQuickTableViewPrivate::releaseItem(FxTableItem *fxTableItem, QQmlTableInst // the item is owned by the QML context rather than the model (e.g ObjectModel etc). auto item = fxTableItem->item; +#if QT_CONFIG(accessibility) + // A released item can come back for another cell, which needs a new focus event. + if (item == accessibleFocusItem) + accessibleFocusItem = nullptr; +#endif + if (fxTableItem->ownItem) { Q_TABLEVIEW_ASSERT(item, fxTableItem->index); delete item; @@ -3670,6 +3680,9 @@ void QQuickTableViewPrivate::processLoadRequest() syncLoadedTableFromLoadRequest(); layoutTableEdgeFromLoadRequest(); syncLoadedTableRectFromLoadedTable(); +#if QT_CONFIG(accessibility) + updateAccessibleFocus(); +#endif if (rebuildState == RebuildState::Done) { // Loading of this edge was not done as a part of a rebuild, but @@ -4614,6 +4627,9 @@ void QQuickTableViewPrivate::currentChangedInSelectionModel(const QModelIndex &c updateCurrentRowAndColumn(); setCurrentOnDelegateItem(previous, false); setCurrentOnDelegateItem(current, true); +#if QT_CONFIG(accessibility) + updateAccessibleFocus(); +#endif } void QQuickTableViewPrivate::updateCurrentRowAndColumn() @@ -4644,6 +4660,31 @@ void QQuickTableViewPrivate::setCurrentOnDelegateItem(const QModelIndex &index, setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(isCurrent), cellIndex, item, false); } +#if QT_CONFIG(accessibility) +// Like the widget item views, send a focus event for the current item while the +// view has focus. TableView is no accessible item itself, so without the event +// assistive technology does not follow the current cell. The delegate of the +// current cell can load after the current index changed, for instance when Tab +// scrolls it into view. +void QQuickTableViewPrivate::updateAccessibleFocus() +{ + Q_Q(QQuickTableView); + QQuickItem *item = nullptr; + if (QAccessible::isActive() && q->hasActiveFocus() && selectionModel) { + const int cellIndex = modelIndexToCellIndex(selectionModel->currentIndex()); + if (loadedItems.contains(cellIndex)) + item = loadedTableItem(cellAtModelIndex(cellIndex))->item; + } + if (item == accessibleFocusItem) + return; + accessibleFocusItem = item; + if (item && QQuickItemPrivate::get(item)->isAccessible) { + QAccessibleEvent event(item, QAccessible::Focus); + QAccessible::updateAccessibility(&event); + } +} +#endif + void QQuickTableViewPrivate::itemCreatedCallback(int modelIndex, QObject*) { if (blockItemCreatedCallback) @@ -5337,6 +5378,9 @@ void QQuickTableViewPrivate::init() q->setFlag(QQuickItem::ItemIsFocusScope); q->setActiveFocusOnTab(true); +#if QT_CONFIG(accessibility) + QObject::connect(q, &QQuickItem::activeFocusChanged, q, [this] { updateAccessibleFocus(); }); +#endif positionXAnimation.setTargetObject(q); positionXAnimation.setProperty(QStringLiteral("contentX")); @@ -7231,6 +7275,11 @@ void QQuickTableView::closeEditor() // have an editItem, if the model has changed (e.g been reset)! d->editIndex = QModelIndex(); } +#if QT_CONFIG(accessibility) + // Focus goes back to the view, which is no accessible item, so announce the current cell again. + d->accessibleFocusItem = nullptr; + d->updateAccessibleFocus(); +#endif } QQuickTableViewAttached *QQuickTableView::qmlAttachedProperties(QObject *obj) diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index cdc9069ed5..3462c9a5dd 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -467,6 +467,9 @@ public: int currentRow = -1; int currentColumn = -1; +#if QT_CONFIG(accessibility) + QPointer<QQuickItem> accessibleFocusItem; +#endif QHash<int, qreal> explicitColumnWidths; QHash<int, qreal> explicitRowHeights; @@ -667,6 +670,9 @@ public: void currentChangedInSelectionModel(const QModelIndex ¤t, const QModelIndex &previous); void setCurrentOnDelegateItem(const QModelIndex &index, bool isCurrent); void updateCurrentRowAndColumn(); +#if QT_CONFIG(accessibility) + void updateAccessibleFocus(); +#endif void fetchMoreData(); diff --git a/src/quickcontrols/basic/TableViewDelegate.qml b/src/quickcontrols/basic/TableViewDelegate.qml index 9735095c9b..412746619c 100644 --- a/src/quickcontrols/basic/TableViewDelegate.qml +++ b/src/quickcontrols/basic/TableViewDelegate.qml @@ -17,6 +17,10 @@ T.TableViewDelegate { highlighted: control.selected + // The content item shows the same text, and stays out of the accessibility + // tree, so that the cell is not read twice. + Accessible.name: control.model.display ?? "" + required property int column required property int row required property var model @@ -31,6 +35,7 @@ T.TableViewDelegate { } contentItem: Label { + Accessible.ignored: true clip: false text: control.model.display ?? "" elide: Text.ElideRight |
