tdf#158030 qt a11y: Implement new QAccessibleAttributesInterface
Implement the new `QAccessibleAttributesInterface`
just added upstream to Qt in qtbase commit [1]
commit fb5ffe862688a87cfc136113e067bcba0c49a7ae
Author: Michael Weghorn <m.weghorn@posteo.de>
AuthorDate: Fri Nov 10 18:25:02 2023 +0100
Commit: Volker Hilsheimer <volker.hilsheimer@qt.io>
CommitDate: Thu Feb 29 04:44:22 2024 +0000
a11y: Add new QAccessibleAttributesInterface
, see also QTBUG-119057. [2]
This API is available with Qt >= 6.8.
That interface makes it possible to report
object attributes that are bridged to the platform a11y
layers.
Use it to bridge the attributes retrieved from
the `XAccessibleExtendedAttributes` UNO interface.
Together with the pending upstream qtbase change that
implements the AT-SPI bridge for Linux [3], the "level"
AT-SPI object attribute for headings in Writer
is correctly reported to AT-SPI, making the
Orca screen reader announce "Heading level N" as expected.
For now, map not explicitly handled attributes
as key-value pairs to Qt via the special
`QAccessible::Attribute::Custom` attribute,
which causes them to be mapped to AT-SPI unchanged, and
can e.g. be used for testing the tdf#158030 scenario.
For common attributes - like those specified in the
Core Accessibility API Mappings specification [4] -
suggesting to add new enum values to the
`QAccessible::Attribute` enum to upstream Qt
and using those instead should be considered for the future.
Related commit for gtk4:
commit 3aca2d9776a871f15009a1aa70628ba3a03ee147
Author: Michael Weghorn <m.weghorn@posteo.de>
Date: Thu Nov 9 15:31:57 2023 +0100
gtk4 a11y: Handle the "level" object attribute
[1] https://code.qt.io/cgit/qt/qtbase.git/commit/?id=fb5ffe862688a87cfc136113e067bcba0c49a7ae
[2] https://bugreports.qt.io/browse/QTBUG-119057
[3] https://codereview.qt-project.org/c/qt/qtbase/+/517526
[4] https://www.w3.org/TR/core-aam-1.2/
Change-Id: Ibe357bfd72bb2dc6e44ad941e62737d5cac21e1c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159309
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
diff --git a/vcl/inc/qt5/QtAccessibleWidget.hxx b/vcl/inc/qt5/QtAccessibleWidget.hxx
index 8d71ecd..46d7be2 100644
--- a/vcl/inc/qt5/QtAccessibleWidget.hxx
+++ b/vcl/inc/qt5/QtAccessibleWidget.hxx
@@ -38,6 +38,9 @@ class QtWidget;
class QtAccessibleWidget final : public QAccessibleInterface,
public QAccessibleActionInterface,
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
public QAccessibleAttributesInterface,
#endif
public QAccessibleTextInterface,
public QAccessibleEditableTextInterface,
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
@@ -85,6 +88,15 @@ public:
void doAction(const QString& actionName) override;
QStringList keyBindingsForAction(const QString& actionName) const override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
// helper method for QAccessibleAttributesInterface
QHash<QAccessible::Attribute, QVariant> attributes() const;
// QAccessibleAttributesInterface
QList<QAccessible::Attribute> attributeKeys() const override;
QVariant attributeValue(QAccessible::Attribute key) const override;
#endif
// QAccessibleTextInterface
void addSelection(int startOffset, int endOffset) override;
QString attributes(int offset, int* startOffset, int* endOffset) const override;
diff --git a/vcl/qt5/QtAccessibleWidget.cxx b/vcl/qt5/QtAccessibleWidget.cxx
index 7eadc33..790e2009 100644
--- a/vcl/qt5/QtAccessibleWidget.cxx
+++ b/vcl/qt5/QtAccessibleWidget.cxx
@@ -39,6 +39,7 @@
#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>
#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
@@ -742,6 +743,10 @@ void* QtAccessibleWidget::interface_cast(QAccessible::InterfaceType t)
if (t == QAccessible::SelectionInterface && accessibleProvidesInterface<XAccessibleSelection>())
return static_cast<QAccessibleSelectionInterface*>(this);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
if (t == QAccessible::AttributesInterface)
return static_cast<QAccessibleAttributesInterface*>(this);
#endif
return nullptr;
}
@@ -855,6 +860,78 @@ QStringList QtAccessibleWidget::keyBindingsForAction(const QString& actionName)
return keyBindings;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
// QAccessibleAttributesInterface helpers
namespace
{
void lcl_insertAttribute(QHash<QAccessible::Attribute, QVariant>& rQtAttrs, const OUString& rName,
const OUString& rValue)
{
if (rName == u"level"_ustr)
{
rQtAttrs.insert(QAccessible::Attribute::Level,
QVariant::fromValue(static_cast<int>(rValue.toInt32())));
}
else
{
// for now, leave not explicitly handled attributes as they are and report
// via QAccessible::Attribute::Custom, but should consider suggesting to
// add more specific attributes on Qt side and use those instead
const QVariant aVariant = rQtAttrs.value(QAccessible::Attribute::Custom,
QVariant::fromValue(QHash<QString, QString>()));
assert((aVariant.canConvert<QHash<QString, QString>>()));
QHash<QString, QString> aAttrs = aVariant.value<QHash<QString, QString>>();
aAttrs.insert(toQString(rName), toQString(rValue));
rQtAttrs.insert(QAccessible::Attribute::Custom, QVariant::fromValue(aAttrs));
}
}
}
QHash<QAccessible::Attribute, QVariant> QtAccessibleWidget::attributes() const
{
Reference<XAccessibleContext> xContext = getAccessibleContextImpl();
if (!xContext.is())
return {};
Reference<XAccessibleExtendedAttributes> xAttributes(xContext, UNO_QUERY);
if (!xAttributes.is())
return {};
OUString sAttrs;
xAttributes->getExtendedAttributes() >>= sAttrs;
QHash<QAccessible::Attribute, QVariant> aQtAttrs;
sal_Int32 nIndex = 0;
do
{
const OUString sAttribute = sAttrs.getToken(0, ';', nIndex);
sal_Int32 nColonPos = 0;
const OUString sName = sAttribute.getToken(0, ':', nColonPos);
const OUString sValue = sAttribute.getToken(0, ':', nColonPos);
assert(nColonPos == -1
&& "Too many colons in attribute that should have \"name:value\" syntax");
if (!sName.isEmpty())
lcl_insertAttribute(aQtAttrs, sName, sValue);
} while (nIndex >= 0);
return aQtAttrs;
}
// QAccessibleAttributesInterface
QList<QAccessible::Attribute> QtAccessibleWidget::attributeKeys() const
{
const QHash<QAccessible::Attribute, QVariant> aAttributes = attributes();
return aAttributes.keys();
}
QVariant QtAccessibleWidget::attributeValue(QAccessible::Attribute eAttribute) const
{
const QHash<QAccessible::Attribute, QVariant> aAllAttributes = attributes();
return aAllAttributes.value(eAttribute);
}
#endif
// QAccessibleTextInterface
void QtAccessibleWidget::addSelection(int /* startOffset */, int /* endOffset */)
{