diff --git a/ui/accessible/ui_accessible_item.cpp b/ui/accessible/ui_accessible_item.cpp index 32fa54d96..b2130df03 100644 --- a/ui/accessible/ui_accessible_item.cpp +++ b/ui/accessible/ui_accessible_item.cpp @@ -198,20 +198,49 @@ QAccessibleInterface *Item::parent() const { } void *Item::interface_cast(QAccessible::InterfaceType type) { + const auto parent = _parent.get(); + const auto index = parent ? currentIndex() : -1; if (type == QAccessible::ActionInterface) { // Expose the action interface only when the owner opted in for this // child. Otherwise the Windows UIA bridge would advertise Invoke / // SetFocus / SelectionItem and report success while doing nothing. - const auto parent = _parent.get(); - const auto index = parent ? currentIndex() : -1; if (index >= 0 && parent->accessibilityChildSupportsActions(index)) { return static_cast(this); } + } else if (type == QAccessible::AttributesInterface) { + // Only a row that is one of the set reports a position, a divider + // between the rows has none - see accessibilityChildSetPosition. + if (index >= 0 + && parent->accessibilityChildSetPosition(index).position > 0) { + return static_cast(this); + } } return nullptr; } +QList Item::attributeKeys() const { + return { + QAccessible::Attribute::PositionInSet, + QAccessible::Attribute::SizeOfSet, + }; +} + +QVariant Item::attributeValue(QAccessible::Attribute key) const { + const auto parent = _parent.get(); + const auto index = parent ? currentIndex() : -1; + if (index < 0) { + return QVariant(); + } + const auto position = parent->accessibilityChildSetPosition(index); + if (key == QAccessible::Attribute::PositionInSet) { + return position.position; + } else if (key == QAccessible::Attribute::SizeOfSet) { + return position.size; + } + return QVariant(); +} + QStringList Item::actionNames() const { const auto parent = _parent.get(); const auto index = parent ? currentIndex() : -1; diff --git a/ui/accessible/ui_accessible_item.h b/ui/accessible/ui_accessible_item.h index ccaac92c4..900a239bd 100644 --- a/ui/accessible/ui_accessible_item.h +++ b/ui/accessible/ui_accessible_item.h @@ -86,7 +86,8 @@ struct SubItems { class Item final : public QAccessibleInterface - , public QAccessibleActionInterface { + , public QAccessibleActionInterface + , public QAccessibleAttributesInterface { public: Item(not_null parent, int index); @@ -131,6 +132,10 @@ class Item final QStringList keyBindingsForAction( const QString &actionName) const override; + // QAccessibleAttributesInterface. + QList attributeKeys() const override; + QVariant attributeValue(QAccessible::Attribute key) const override; + private: base::weak_qptr _parent; mutable std::unique_ptr _subitems; diff --git a/ui/rp_widget.cpp b/ui/rp_widget.cpp index 3fec5525a..8eb3a2fa4 100644 --- a/ui/rp_widget.cpp +++ b/ui/rp_widget.cpp @@ -402,6 +402,11 @@ QAccessible::Role RpWidget::accessibilityChildRole() const { return QAccessible::Role::NoRole; } +AccessibilitySetPosition RpWidget::accessibilityChildSetPosition( + int index) const { + return { index + 1, accessibilityChildCount() }; +} + QString RpWidget::accessibilityChildName(int index) const { return QString(); } diff --git a/ui/rp_widget.h b/ui/rp_widget.h index 1d10351e6..b0eb2f82a 100644 --- a/ui/rp_widget.h +++ b/ui/rp_widget.h @@ -380,6 +380,15 @@ class RpWidgetBase : public Widget, public RpWidgetWrap { // Add required fields from QAccessible::State when necessary. // Don't forget to amend the AccessibilityState::writeTo implementation. // This one allows universal initialization, like { .checkable = true }. +// The "x of y" of a child of a painted list, reported to a screen reader +// as PositionInSet / SizeOfSet: the 1-based position of the child within +// the set of its siblings and the size of that set. A zero position means +// the child is not one of the set, like a divider row between the items. +struct AccessibilitySetPosition { + int position = 0; + int size = 0; +}; + struct AccessibilityState { bool checkable : 1 = false; bool checked : 1 = false; @@ -458,6 +467,12 @@ class RpWidget : public RpWidgetBase { [[nodiscard]] virtual QAccessible::State accessibilityChildState(int index) const; void accessibilityChildStateChanged(int index, AccessibilityState changes); [[nodiscard]] virtual QAccessible::Role accessibilityChildRole() const; + + // By default every child is one of the set; a list with rows outside + // it overrides this to skip them, see AccessibilitySetPosition. + [[nodiscard]] virtual AccessibilitySetPosition accessibilityChildSetPosition( + int index) const; + [[nodiscard]] virtual QRect accessibilityChildRect(int index) const; [[nodiscard]] virtual int accessibilityChildColumnCount(int row) const; [[nodiscard]] virtual QAccessible::Role accessibilityChildSubItemRole() const;