aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/CMakeLists.txt1
-rw-r--r--src/quick/accessible/qaccessiblequicktableview.cpp32
-rw-r--r--src/quick/accessible/qaccessiblequicktableview_p.h39
-rw-r--r--src/quick/accessible/qquickaccessiblefactory.cpp4
-rw-r--r--src/quick/items/qquicktableview.cpp49
-rw-r--r--src/quick/items/qquicktableview_p_p.h6
-rw-r--r--src/quickcontrols/basic/TableViewDelegate.qml5
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 &current, 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