a11y: Test and document relations better

The documentation for the RelationFlag enum was not very clear on what
was the "first" and "second" object.
And the fact that the AT-SPI backend (which these enum values originates
from) inverses "first" and "second" makes it harder to understand what
how it all fits together.

So when (with this change) Qt documents 'QAccessible::Labelled' as
    "The returned object is labelled by the origin object"

AT-SPI documents ATSPI_RELATION_LABELLED_BY as:
    "The origin object is labelled by the returned object"
(Documentation for AT-SPI is rewritten so that it shares the same
terminology)

Notice that the two objects are exchanged, which means that even if they
use the same 'Labelled' relation, the semantic gets 'inversed'.

This is already the case today, so we cannot change it. Therefore, to be
clear, the relation mapping will remain to be like this:

Qt Relation     | Maps to AT-SPI                | Qt explanation
----------------+-------------------------------+--------------------------------------------------------------------------------
Label           | ATSPI_RELATION_LABELLED_BY    | The returned object is a Label for the origin object
Labelled        | ATSPI_RELATION_LABEL_FOR      | The returned object is Labelled by the origin object

Controller      | ATSPI_RELATION_CONTROLLED_BY  | The returned object is the Controller for the origin object
Controlled      | ATSPI_RELATION_CONTROLLER_FOR | The returned object is Controlled by the origin object

This mapping can already be seen in qAccessibleRelationToAtSpiRelation()

For the record, these future relations should then be mapped to like
this:

Qt Relation     | Maps to AT-SPI                | Qt explanation
----------------+-------------------------------+--------------------------------------------------------------------------------
Described       | ATSPI_RELATION_DESCRIPTION_FOR| The returned object is described by the origin object
DescriptionFor  | ATSPI_RELATION_DESCRIBED_BY   | The returned object provides a description for the origin object

FlowsTo         | ATSPI_RELATION_FLOWS_FROM     | The returned object has content which flows logically to the origin object
FlowsFrom       | ATSPI_RELATION_FLOWS_TO       | The returned object has content which flows logically from the origin object

Change-Id: Ib245ec95564e4886dc6dbbb68abec2b23cd0e534
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
This commit is contained in:
Jan Arve Sæther 2023-02-06 13:16:45 +01:00
parent 50057fec93
commit afbfe30093
2 changed files with 98 additions and 11 deletions

View File

@ -354,12 +354,23 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\enum QAccessible::RelationFlag
This enum type defines bit flags that can be combined to indicate
the relationship between two accessible objects.
the relationship between two accessible objects. It is used by
the relations() function, which returns a list of all the related
interfaces of the calling object, together with the relations
for each object.
\value Label The first object is the label of the second object.
\value Labelled The first object is labelled by the second object.
\value Controller The first object controls the second object.
\value Controlled The first object is controlled by the second object.
Each entry in the list is a QPair where the \c second member stores
the relation type(s) between the \c returned object represented by the
\c first member and the \c origin (the caller) interface/object.
In the table below, the \c returned object refers to the object in
the returned list, and the \c origin object is the one represented
by the calling interface.
\value Label The \c returned object is the label for the \c origin object.
\value Labelled The \c returned object is labelled by the \c origin object.
\value Controller The \c returned object controls the \c origin object.
\value Controlled The \c returned object is controlled by the \c origin object.
\value AllRelations Used as a mask to specify that we are interesting in information
about all relations

View File

@ -222,6 +222,7 @@ private slots:
void accessibleName();
#if QT_CONFIG(shortcut)
void labelTest();
void relationTest();
void accelerators();
#endif
void bridgeTest();
@ -3688,6 +3689,69 @@ void tst_QAccessibility::comboBoxTest()
QTestAccessibility::clearEvents();
}
void tst_QAccessibility::relationTest()
{
auto windowHolder = std::make_unique<QWidget>();
auto window = windowHolder.get();
QString text = "Hello World";
QLabel *label = new QLabel(text, window);
setFrameless(label);
QSpinBox *spinBox = new QSpinBox(window);
label->setBuddy(spinBox);
QProgressBar *pb = new QProgressBar(window);
pb->setRange(0, 99);
connect(spinBox, SIGNAL(valueChanged(int)), pb, SLOT(setValue(int)));
window->resize(320, 200);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
#if defined(Q_OS_UNIX)
QCoreApplication::processEvents();
#endif
QTest::qWait(100);
QAccessibleInterface *acc_label = QAccessible::queryAccessibleInterface(label);
QVERIFY(acc_label);
QAccessibleInterface *acc_spinBox = QAccessible::queryAccessibleInterface(spinBox);
QVERIFY(acc_spinBox);
QAccessibleInterface *acc_pb = QAccessible::queryAccessibleInterface(pb);
QVERIFY(acc_pb);
typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
{
const QList<RelationPair> rels = acc_label->relations(QAccessible::Labelled);
QCOMPARE(rels.size(), 1);
const RelationPair relPair = rels.first();
// spinBox is Labelled by acc_label
QCOMPARE(relPair.first->object(), spinBox);
QCOMPARE(relPair.second, QAccessible::Labelled);
}
{
// Test multiple relations (spinBox have two)
const QList<RelationPair> rels = acc_spinBox->relations();
QCOMPARE(rels.size(), 2);
int visitCount = 0;
for (const auto &relPair : rels) {
if (relPair.second & QAccessible::Label) {
// label is the Label of spinBox
QCOMPARE(relPair.first->object(), label);
++visitCount;
} else if (relPair.second & QAccessible::Controlled) {
// progressbar is Controlled by the spinBox
QCOMPARE(relPair.first->object(), pb);
++visitCount;
}
}
QCOMPARE(visitCount, rels.size());
}
windowHolder.reset();
QTestAccessibility::clearEvents();
}
#if QT_CONFIG(shortcut)
void tst_QAccessibility::labelTest()
@ -3710,6 +3774,8 @@ void tst_QAccessibility::labelTest()
QAccessibleInterface *acc_label = QAccessible::queryAccessibleInterface(label);
QVERIFY(acc_label);
QAccessibleInterface *acc_lineEdit = QAccessible::queryAccessibleInterface(buddy);
QVERIFY(acc_lineEdit);
QCOMPARE(acc_label->text(QAccessible::Name), text);
QCOMPARE(acc_label->state().editable, false);
@ -3719,13 +3785,23 @@ void tst_QAccessibility::labelTest()
QCOMPARE(acc_label->state().focusable, false);
QCOMPARE(acc_label->state().readOnly, true);
QList<QPair<QAccessibleInterface *, QAccessible::Relation>> rels = acc_label->relations();
QCOMPARE(rels.size(), 1);
QAccessibleInterface *iface = rels.first().first;
QAccessible::Relation rel = rels.first().second;
QCOMPARE(rel, QAccessible::Labelled);
QCOMPARE(iface->role(), QAccessible::EditableText);
typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
{
const QList<RelationPair> rels = acc_label->relations(QAccessible::Labelled);
QCOMPARE(rels.size(), 1);
const RelationPair relPair = rels.first();
QCOMPARE(relPair.first->object(), buddy);
QCOMPARE(relPair.second, QAccessible::Labelled);
}
{
const QList<RelationPair> rels = acc_lineEdit->relations(QAccessible::Label);
QCOMPARE(rels.size(), 1);
const RelationPair relPair = rels.first();
QCOMPARE(relPair.first->object(), label);
QCOMPARE(relPair.second, QAccessible::Label);
}
windowHolder.reset();
QTestAccessibility::clearEvents();