aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleix Pol <[email protected]>2024-04-21 15:40:40 +0200
committerMitch Curtis <[email protected]>2024-05-24 20:21:59 +0000
commit7bdeea2c309150c8b49558b135232926d6a89c50 (patch)
tree14f16178efce978c302866d0dccb4157a3bdd6fc
parentf9f9480746e0f046b6ef4229fd05aad22276b8cc (diff)
QQuickAbstractButton: setAction will share its Accessible object
In cases where a button acts as a view for an action, provide the Action's accessibility information into the visual component so that it can convey the action's information into the accessibility infrastructure. This is done by creating a Accessibility proxy object in the button that will communicate the information from the action's accessibility as part of the button. If the properties of the Accessibility object in the button are modified, these values will prevail. Fixes: QTBUG-123123 Change-Id: Ibeddcc918616d717bb1704177d482ff88bfe99ef Reviewed-by: Mitch Curtis <[email protected]>
-rw-r--r--src/plugins/qmllint/quick/quicklintplugin.cpp2
-rw-r--r--src/quick/items/qquickaccessibleattached.cpp87
-rw-r--r--src/quick/items/qquickaccessibleattached_p.h26
-rw-r--r--src/quicktemplates/qquickabstractbutton.cpp8
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp2
-rw-r--r--tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp2
-rw-r--r--tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml12
-rw-r--r--tests/auto/quickcontrols/accessibility/tst_accessibility.cpp22
8 files changed, 141 insertions, 20 deletions
diff --git a/src/plugins/qmllint/quick/quicklintplugin.cpp b/src/plugins/qmllint/quick/quicklintplugin.cpp
index 15b947a10c..52d4d759e3 100644
--- a/src/plugins/qmllint/quick/quicklintplugin.cpp
+++ b/src/plugins/qmllint/quick/quicklintplugin.cpp
@@ -675,7 +675,7 @@ void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
{ { "columnWidthProvider", { "", "function" } },
{ "rowHeightProvider", { "", "function" } } });
addAttachedWarning({ "QtQuick", "Accessible" }, { { "QtQuick", "Item" } },
- "Accessible must be attached to an Item");
+ "Accessible must be attached to an Item or an Action");
addAttachedWarning({ "QtQuick", "LayoutMirroring" },
{ { "QtQuick", "Item" }, { "QtQuick", "Window" } },
"LayoutDirection attached property only works with Items and Windows");
diff --git a/src/quick/items/qquickaccessibleattached.cpp b/src/quick/items/qquickaccessibleattached.cpp
index 865fb8bf11..547ac72e73 100644
--- a/src/quick/items/qquickaccessibleattached.cpp
+++ b/src/quick/items/qquickaccessibleattached.cpp
@@ -301,15 +301,19 @@ QQuickAccessibleAttached::QQuickAccessibleAttached(QObject *parent)
: QObject(parent), m_role(QAccessible::NoRole)
{
Q_ASSERT(parent);
- if (!item()) {
- qmlWarning(parent) << "Accessible must be attached to an Item";
- return;
- }
-
// Enable accessibility for items with accessible content. This also
- // enables accessibility for the ancestors of souch items.
- item()->d_func()->setAccessible();
- QAccessibleEvent ev(item(), QAccessible::ObjectCreated);
+ // enables accessibility for the ancestors of such items.
+ auto item = qobject_cast<QQuickItem *>(parent);
+ if (item) {
+ item->d_func()->setAccessible();
+ } else {
+ const QLatin1StringView className(QQmlData::ensurePropertyCache(parent)->firstCppMetaObject()->className());
+ if (className != QLatin1StringView("QQuickAction")) {
+ qmlWarning(parent) << "Accessible must be attached to an Item or an Action";
+ return;
+ }
+ }
+ QAccessibleEvent ev(parent, QAccessible::ObjectCreated);
QAccessible::updateAccessibility(&ev);
if (const QMetaObject *pmo = parent->metaObject()) {
@@ -423,13 +427,15 @@ QQuickAccessibleAttached *QQuickAccessibleAttached::qmlAttachedProperties(QObjec
bool QQuickAccessibleAttached::ignored() const
{
- return item() ? !item()->d_func()->isAccessible : false;
+ auto item = qobject_cast<QQuickItem *>(parent());
+ return item ? !item->d_func()->isAccessible : false;
}
void QQuickAccessibleAttached::setIgnored(bool ignored)
{
- if (this->ignored() != ignored && item()) {
- item()->d_func()->isAccessible = !ignored;
+ auto item = qobject_cast<QQuickItem *>(parent());
+ if (item && this->ignored() != ignored) {
+ item->d_func()->isAccessible = !ignored;
emit ignoredChanged();
}
}
@@ -457,8 +463,13 @@ bool QQuickAccessibleAttached::doAction(const QString &actionName)
sig = &sigPreviousPage;
else if (actionName == QAccessibleActionInterface::nextPageAction())
sig = &sigNextPage;
- if (sig && isSignalConnected(*sig))
- return sig->invoke(this);
+ if (sig && isSignalConnected(*sig)) {
+ bool ret = false;
+ if (m_proxying)
+ ret = sig->invoke(m_proxying);
+ ret |= sig->invoke(this);
+ return ret;
+ }
return false;
}
@@ -497,6 +508,56 @@ QString QQuickAccessibleAttached::stripHtml(const QString &html)
#endif
}
+void QQuickAccessibleAttached::setProxying(QQuickAccessibleAttached *proxying)
+{
+ if (proxying == m_proxying)
+ return;
+
+ const QMetaObject &mo = staticMetaObject;
+ if (m_proxying) {
+ // We disconnect all signals from the proxy into this object
+ auto mo = m_proxying->metaObject();
+ auto propertyCache = QQmlData::ensurePropertyCache(m_proxying);
+ for (int signalIndex = propertyCache->signalOffset();
+ signalIndex < propertyCache->signalCount(); ++signalIndex) {
+ const QMetaMethod m = mo->method(propertyCache->signal(signalIndex)->coreIndex());
+ Q_ASSERT(m.methodType() == QMetaMethod::Signal);
+ if (m.methodType() != QMetaMethod::Signal)
+ continue;
+
+ disconnect(m_proxying, m, this, m);
+ }
+ }
+
+ m_proxying = proxying;
+
+ if (m_proxying) {
+ // We connect all signals from the proxy into this object
+ auto propertyCache = QQmlData::ensurePropertyCache(m_proxying);
+ auto mo = m_proxying->metaObject();
+ for (int signalIndex = propertyCache->signalOffset();
+ signalIndex < propertyCache->signalCount(); ++signalIndex) {
+ const QMetaMethod m = mo->method(propertyCache->signal(signalIndex)->coreIndex());
+ Q_ASSERT(m.methodType() == QMetaMethod::Signal);
+ connect(proxying, m, this, m);
+ }
+ }
+
+ // We check all properties
+ for (int prop = mo.propertyOffset(); prop < mo.propertyCount(); ++prop) {
+ const QMetaProperty p = mo.property(prop);
+ if (!p.hasNotifySignal()) {
+ continue;
+ }
+
+ const QMetaMethod signal = p.notifySignal();
+ if (signal.parameterCount() == 0)
+ signal.invoke(this);
+ else
+ signal.invoke(this, Q_ARG(bool, p.read(this).toBool()));
+ }
+}
+
QT_END_NAMESPACE
#include "moc_qquickaccessibleattached_p.cpp"
diff --git a/src/quick/items/qquickaccessibleattached_p.h b/src/quick/items/qquickaccessibleattached_p.h
index 92d1307a9a..45c5612854 100644
--- a/src/quick/items/qquickaccessibleattached_p.h
+++ b/src/quick/items/qquickaccessibleattached_p.h
@@ -30,9 +30,11 @@ QT_BEGIN_NAMESPACE
#define STATE_PROPERTY(P) \
Q_PROPERTY(bool P READ P WRITE set_ ## P NOTIFY P ## Changed FINAL) \
- bool P() const { return m_state.P ; } \
+ bool P() const { return m_proxying && !m_stateExplicitlySet.P ? m_proxying->P() : m_state.P ; } \
void set_ ## P(bool arg) \
{ \
+ if (m_proxying) \
+ m_proxying->set_##P(arg);\
m_stateExplicitlySet.P = true; \
if (m_state.P == arg) \
return; \
@@ -84,6 +86,8 @@ public:
QString name() const {
if (m_state.passwordEdit)
return QString();
+ if (m_proxying)
+ return m_proxying->name();
return m_name;
}
@@ -99,9 +103,15 @@ public:
}
void setNameImplicitly(const QString &name);
- QString description() const { return m_description; }
+ QString description() const {
+ return !m_descriptionExplicitlySet && m_proxying ? m_proxying->description() : m_description;
+ }
void setDescription(const QString &description)
{
+ if (!m_descriptionExplicitlySet && m_proxying) {
+ disconnect(m_proxying, &QQuickAccessibleAttached::descriptionChanged, this, &QQuickAccessibleAttached::descriptionChanged);
+ }
+ m_descriptionExplicitlySet = true;
if (m_description != description) {
m_description = description;
Q_EMIT descriptionChanged();
@@ -143,6 +153,13 @@ public:
if (att && (role == QAccessible::NoRole || att->role() == role)) {
break;
}
+ if (auto action = object->property("action").value<QObject *>(); action) {
+ QQuickAccessibleAttached *att = QQuickAccessibleAttached::attachedProperties(action);
+ if (att && (role == QAccessible::NoRole || att->role() == role)) {
+ object = action;
+ break;
+ }
+ }
object = object->parent();
}
return object;
@@ -154,6 +171,7 @@ public:
void availableActions(QStringList *actions) const;
Q_REVISION(6, 2) Q_INVOKABLE static QString stripHtml(const QString &html);
+ void setProxying(QQuickAccessibleAttached *proxying);
public Q_SLOTS:
void valueChanged() {
@@ -184,14 +202,14 @@ Q_SIGNALS:
void nextPageAction();
private:
- QQuickItem *item() const { return qobject_cast<QQuickItem*>(parent()); }
-
QAccessible::Role m_role;
QAccessible::State m_state;
QAccessible::State m_stateExplicitlySet;
QString m_name;
bool m_nameExplicitlySet = false;
QString m_description;
+ bool m_descriptionExplicitlySet = false;
+ QQuickAccessibleAttached* m_proxying = nullptr;
static QMetaMethod sigPress;
static QMetaMethod sigToggle;
diff --git a/src/quicktemplates/qquickabstractbutton.cpp b/src/quicktemplates/qquickabstractbutton.cpp
index 0c1af73e55..d244685011 100644
--- a/src/quicktemplates/qquickabstractbutton.cpp
+++ b/src/quicktemplates/qquickabstractbutton.cpp
@@ -18,6 +18,8 @@
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/qpa/qplatformtheme.h>
#include <QtQuick/private/qquickevents_p_p.h>
+#include <QtQuick/private/qquickevents_p_p.h>
+#include <QtQuick/private/qquickaccessibleattached_p.h>
#include <QtQml/qqmllist.h>
QT_BEGIN_NAMESPACE
@@ -889,6 +891,12 @@ void QQuickAbstractButton::setAction(QQuickAction *action)
setEnabled(action->isEnabled());
}
+#if QT_CONFIG(accessibility)
+ auto attached = qobject_cast<QQuickAccessibleAttached*>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(this, true));
+ Q_ASSERT(attached);
+ attached->setProxying(qobject_cast<QQuickAccessibleAttached*>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(action, true)));
+#endif
+
d->action = action;
if (oldText != text())
diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp
index 1ae90d62c7..6bd4d6c90a 100644
--- a/tests/auto/qml/qmllint/tst_qmllint.cpp
+++ b/tests/auto/qml/qmllint/tst_qmllint.cpp
@@ -2124,7 +2124,7 @@ void TestQmllint::quickPlugin()
Message { u"SplitView attached property only works with Items"_s },
Message { u"ScrollIndicator must be attached to a Flickable"_s },
Message { u"ScrollBar must be attached to a Flickable or ScrollView"_s },
- Message { u"Accessible must be attached to an Item"_s },
+ Message { u"Accessible must be attached to an Item or an Action"_s },
Message { u"EnterKey attached property only works with Items"_s },
Message {
u"LayoutDirection attached property only works with Items and Windows"_s },
diff --git a/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp b/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp
index e164d89217..6a282d4b41 100644
--- a/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp
+++ b/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp
@@ -161,7 +161,7 @@ void tst_QQuickAccessible::quickAttachedProperties()
// Attaching to non-item
{
QObject parent;
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML QtObject: Accessible must be attached to an Item");
+ QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML QtObject: Accessible must be attached to an Item or an Action");
QQuickAccessibleAttached *attachedObj = new QQuickAccessibleAttached(&parent);
QCOMPARE(attachedObj->ignored(), false);
diff --git a/tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml b/tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml
new file mode 100644
index 0000000000..7e392e9cc3
--- /dev/null
+++ b/tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml
@@ -0,0 +1,12 @@
+import QtQuick
+import QtQuick.Controls
+
+Button {
+ action: Action {
+ id: anAction
+ text: "Peaches"
+ Accessible.name: "Peach"
+ Accessible.description: "Show peaches some love"
+ }
+ text: Accessible.description
+}
diff --git a/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp b/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp
index 9774bf4e07..8bdd9453c8 100644
--- a/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp
+++ b/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp
@@ -31,6 +31,8 @@ private slots:
void override();
void ordering();
+
+ void actionAccessibility();
private:
QQmlEngine engine;
};
@@ -274,6 +276,26 @@ void tst_accessibility::ordering()
#endif
}
+void tst_accessibility::actionAccessibility()
+{
+#if QT_CONFIG(accessibility)
+ QQmlComponent component(&engine);
+ component.loadUrl(testFileUrl("actionAccessibility/button.qml"));
+
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY2(!object.isNull(), qPrintable(component.errorString()));
+
+ QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
+ QVERIFY(item);
+ const QString description = "Show peaches some love";
+ QCOMPARE(item->property("text"), description);
+ QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(item);
+ QVERIFY(iface);
+ QCOMPARE(iface->text(QAccessible::Name), "Peach");
+ QCOMPARE(iface->text(QAccessible::Description), description);
+#endif
+}
+
QTEST_MAIN(tst_accessibility)
#include "tst_accessibility.moc"