a11y: Add new QAccessibleSelectionInterface

This adds a new QAccessibleSelectionInterface
that implements support for handling selections,
both retrieving information about the current selection
as well as modifying the selection, thus providing
the necessary foundation for exposing selections
to assistive technology.

This is similar to AT-SPI's Selection interface [1],
but uses QAccessibleInterface pointers instead of
child indices as parameters for methods.

Bridging this newly introduced interface to AT-SPI
will be done in a follow-up change.

The concept for selection handling in Microsoft's
UI Automation looks similar, so bridging this to the
corresponding UIA interfaces ISelectionProvider [2],
ISelectionItemProvider [3] and ISelectionProvider2 [4]
should also be reasonably straightforward.

At this point, this is only meant to be used for
handling selection of direct children as is the
concept in AT-SPI's Selection and presumably
in UIA as well. (The ISelectionItemProvider and
ISelectionProvider doc speaks of "child controls
of containers" etc., but doesn't explicitly mention
that this is only for direct children or whether
grand children are also allowed.)

(The proposed API itself could
also be used to support selection of objects
that are no direct children, but the practical
use of that is unclear and it would make mapping/
bridging to platform a11y layers tricky.)

[5] demonstrates a sample implementation of the
QAccessibleSelectionInterface for the Qt-based
UI variants of LibreOffice (screencast that
shows the use of the interface attached to
QTBUG-105909).

This interface is marked \preliminary until:

1. There is a working a11y bridge for macOS/VoiceOver
2. There is a working a11y bridge for Windows/UI Automation
3. There is a working a11y bridge for linux/AT-SPI
4. There is at least one implementation (e.g. QAccessibleTable)
   that implements it successfully (second candidate:
   Qt Quick TableView [6])

[1] https://lazka.github.io/pgi-docs/Atspi-2.0/classes/Selection.html
[2] https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-iselectionprovider
[3] https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-iselectionitemprovider
[4] https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-iselectionprovider2
[5] https://gerrit.libreoffice.org/c/core/+/138750
[6] https://doc-snapshots.qt.io/qt6-dev/qml-qtquick-tableview.html

[ChangeLog][QtGui][QAccessibleSelectionInterface] Added new
preliminary QAccessibleSelectionInterface that can be used to expose
selections to assistive technology.

Task-number: QTBUG-105909
Change-Id: If77beacd94fa3eb663f0fbb2373f12382bab2ee3
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
Michael Weghorn 2022-10-11 15:23:54 +02:00
parent a45a1b8674
commit 9d16d5e224
3 changed files with 134 additions and 2 deletions

View File

@ -415,8 +415,9 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\value TableInterface For lists, tables and trees. \value TableInterface For lists, tables and trees.
\value TableCellInterface For cells in a TableInterface object. \value TableCellInterface For cells in a TableInterface object.
\value HyperlinkInterface For hyperlink nodes (usually embedded as children of text nodes) \value HyperlinkInterface For hyperlink nodes (usually embedded as children of text nodes)
\value [since 6.5] SelectionInterface For non-text objects that support selection of child objects.
\sa QAccessibleInterface::interface_cast(), QAccessibleTextInterface, QAccessibleValueInterface, QAccessibleActionInterface, QAccessibleTableInterface, QAccessibleTableCellInterface \sa QAccessibleInterface::interface_cast(), QAccessibleTextInterface, QAccessibleValueInterface, QAccessibleActionInterface, QAccessibleTableInterface, QAccessibleTableCellInterface, QAccessibleSelectionInterface
*/ */
#if QT_CONFIG(accessibility) #if QT_CONFIG(accessibility)
@ -2941,6 +2942,114 @@ QString QAccessibleActionInterface::nextPageAction()
return accessibleActionStrings()->nextPageAction; return accessibleActionStrings()->nextPageAction;
} }
/*!
\since 6.5
\class QAccessibleSelectionInterface
\inmodule QtGui
\ingroup accessibility
\preliminary
\brief The QAccessibleSelectionInterface class implements support for
selection handling.
It provides methods for both, retrieving the current selection
as well as modifying the selection.
Only selections of direct children are supported.
*/
/*!
Destroys the QAccessibleSelectionInterface.
*/
QAccessibleSelectionInterface::~QAccessibleSelectionInterface()
{
}
/*!
\fn virtual int QAccessibleSelectionInterface::selectedItemCount() const
Returns the total number of selected accessible items.
*/
/*!
\fn virtual QList<QAccessibleInterface *> QAccessibleSelectionInterface::selectedItems() const
Returns the list of selected accessible items.
*/
/*!
Returns the selected accessible item at index \a selectionIndex in the selection.
Note that the index refers to the n-th selected accessible item (i.e. the index in the current selection),
which generally differs from the index that would be passed to \l QAccessibleInterface::child()
in order to retrieve the same item.
The default implementation uses \a selectionIndex to retrieve the item from the list
of selected items retrieved by \l QAccessibleSelectionInterface::selectedItems().
In particular for implementations dealing with many selected items, reimplementing
this method in a more efficient way may be desirable for performance reasons.
*/
QAccessibleInterface* QAccessibleSelectionInterface::selectedItem(int selectionIndex) const
{
QList<QAccessibleInterface*> items = selectedItems();
if (selectionIndex < 0 || selectionIndex > items.length() -1) {
qCWarning(lcAccessibilityCore) << "Selection index" << selectionIndex << "out of range.";
return nullptr;
}
return items.at(selectionIndex);
}
/*!
Returns whether \a childItem is part of the current selection.
The default implementation checks whether \a childItem is contained
in the list of items retrieved by \l QAccessibleSelectionInterface::selectedItems.
*/
bool QAccessibleSelectionInterface::isSelected(QAccessibleInterface *childItem) const
{
return selectedItems().contains(childItem);
}
/*!
\fn virtual bool QAccessibleSelectionInterface::select(QAccessibleInterface *childItem)
Adds \a childItem to the selection.
Returns whether \a childItem has actually been added to the selection.
For implementations that only allow single selections,
this may replace the current selection.
*/
/*!
\fn virtual bool QAccessibleSelectionInterface::unselect(QAccessibleInterface *childItem)
Removes \a childItem from the selection.
Returns whether the accessible item has actually been removed from the selection.
*/
/*!
\fn virtual bool QAccessibleSelectionInterface::selectAll()
Selects all accessible child items.
Returns whether all accessible child items have actually been added to the selection.
*/
/*!
\fn virtual bool QAccessibleSelectionInterface::clear()
Unselects all accessible child items.
Returns whether all accessible child items have actually been removed from the selection,
i.e. whether the selection is empty after this method has been called.
*/
/*! \internal */ /*! \internal */
QString qAccessibleLocalizedActionDescription(const QString &actionName) QString qAccessibleLocalizedActionDescription(const QString &actionName)
{ {

View File

@ -42,6 +42,7 @@ class QAccessibleImageInterface;
class QAccessibleTableInterface; class QAccessibleTableInterface;
class QAccessibleTableCellInterface; class QAccessibleTableCellInterface;
class QAccessibleHyperlinkInterface; class QAccessibleHyperlinkInterface;
class QAccessibleSelectionInterface;
class QAccessibleTableModelChangeEvent; class QAccessibleTableModelChangeEvent;
class Q_GUI_EXPORT QAccessibleInterface class Q_GUI_EXPORT QAccessibleInterface
@ -102,6 +103,12 @@ public:
inline QAccessibleHyperlinkInterface *hyperlinkInterface() inline QAccessibleHyperlinkInterface *hyperlinkInterface()
{ return reinterpret_cast<QAccessibleHyperlinkInterface *>(interface_cast(QAccessible::HyperlinkInterface)); } { return reinterpret_cast<QAccessibleHyperlinkInterface *>(interface_cast(QAccessible::HyperlinkInterface)); }
/*!
\since 6.5
*/
inline QAccessibleSelectionInterface *selectionInterface()
{ return reinterpret_cast<QAccessibleSelectionInterface *>(interface_cast(QAccessible::SelectionInterface)); }
virtual void virtual_hook(int id, void *data); virtual void virtual_hook(int id, void *data);
virtual void *interface_cast(QAccessible::InterfaceType) virtual void *interface_cast(QAccessible::InterfaceType)
@ -265,6 +272,21 @@ public:
virtual bool isValid() const = 0; virtual bool isValid() const = 0;
}; };
class Q_GUI_EXPORT QAccessibleSelectionInterface
{
public:
virtual ~QAccessibleSelectionInterface();
virtual int selectedItemCount() const = 0;
virtual QList<QAccessibleInterface*> selectedItems() const = 0;
virtual QAccessibleInterface* selectedItem(int selectionIndex) const;
virtual bool isSelected(QAccessibleInterface *childItem) const;
virtual bool select(QAccessibleInterface *childItem) = 0;
virtual bool unselect(QAccessibleInterface *childItem) = 0;
virtual bool selectAll() = 0;
virtual bool clear() = 0;
};
class Q_GUI_EXPORT QAccessibleEvent class Q_GUI_EXPORT QAccessibleEvent
{ {
Q_DISABLE_COPY(QAccessibleEvent) Q_DISABLE_COPY(QAccessibleEvent)

View File

@ -344,7 +344,8 @@ public:
ImageInterface, ImageInterface,
TableInterface, TableInterface,
TableCellInterface, TableCellInterface,
HyperlinkInterface HyperlinkInterface,
SelectionInterface
}; };
enum TextBoundaryType { enum TextBoundaryType {