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 <mitch.curtis@qt.io>
This commit is contained in:
parent
f9f9480746
commit
7bdeea2c30
|
@ -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");
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue