a11y uia: Support ISelectionProvider2

Support UIA's ISelectionProvider2 interface [1]
in addition to ISelectionProvider.

The ISelectionProvider2 interface inherits from the
ISelectionProvider interface.

A follow-up commit that will introduce bridging the
QAccessibleSelectionInterface, introduced in commit
9d16d5e224.

While at it, also reserve space for the amount
of children in the QList in QWindowsUiaSelectionProvider::GetSelection
before inserting them one by one, to avoid reallocations.

Sample use of the ISelectionProvider2 interface from NVDA's
Python console [2] with this commit in place:

1) start NVDA
2) run the gallery example (examples\widgets\gallery\gallery.exe)
3) click on the "Style" listbox at the top
4) press Numpad_insert+control+z to start the NVDA Python console and
   capture snapshot variables
5) query and use the interface using NVDA's Python console

    >>> import UIAHandler
    >>> iselection2 = focus.parent.UIAElement.GetCurrentPattern(10034).QueryInterface(UIAHandler.IUIAutomationSelectionPattern2)
    >>> iselection2.CurrentItemCount
    1
    >>> iselection2.CurrentFirstSelectedItem.CurrentName
    'windowsvista'
    >>> iselection2.CurrentLastSelectedItem.CurrentName
    'windowsvista'

[1] https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-iselectionprovider2
[2] https://www.nvaccess.org/files/nvda/documentation/developerGuide.html#PythonConsole

Change-Id: I43642e9e39b63c65da97af976cc322a8e5868170
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
bb10
Michael Weghorn 2023-07-14 11:50:17 +01:00
parent 90af40ccd0
commit 3bace699bf
5 changed files with 152 additions and 2 deletions

View File

@ -49,5 +49,6 @@
#define UIA_DropTargetPatternId 10031
#define UIA_TextEditPatternId 10032
#define UIA_CustomNavigationPatternId 10033
#define UIA_SelectionPattern2Id 10034
#endif

View File

@ -236,6 +236,24 @@ __CRT_UUID_DECL(ISelectionProvider, 0xfb8b03af, 0x3bdf, 0x48d4, 0xbd,0x36, 0x1a,
#endif
#endif
#ifndef __ISelectionProvider2_INTERFACE_DEFINED__
#define __ISelectionProvider2_INTERFACE_DEFINED__
DEFINE_GUID(IID_ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7);
MIDL_INTERFACE("14f68475-ee1c-44f6-a869-d239381f0fe7")
ISelectionProvider2 : public ISelectionProvider
{
public:
virtual HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
virtual HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
virtual HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
virtual HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *retVal) = 0;
};
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7)
#endif
#endif
#ifndef __ISelectionItemProvider_INTERFACE_DEFINED__
#define __ISelectionItemProvider_INTERFACE_DEFINED__

View File

@ -304,6 +304,7 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow
*pRetVal = new QWindowsUiaToggleProvider(id());
break;
case UIA_SelectionPatternId:
case UIA_SelectionPattern2Id:
// Lists of items.
if (accessible->role() == QAccessible::List
|| accessible->role() == QAccessible::PageTabList) {

View File

@ -43,7 +43,9 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::GetSelection(SAFEARRAY *
// First put selected items in a list, then build a safe array with the right size.
QList<QAccessibleInterface *> selectedList;
for (int i = 0; i < accessible->childCount(); ++i) {
const int childCount = accessible->childCount();
selectedList.reserve(childCount);
for (int i = 0; i < childCount; ++i) {
if (QAccessibleInterface *child = accessible->child(i)) {
if (accessible->role() == QAccessible::PageTabList) {
if (child->role() == QAccessible::PageTab && child->state().focused) {
@ -116,6 +118,116 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired(
return S_OK;
}
HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
{
qCDebug(lcQpaUiAutomation) << __FUNCTION__;
if (!pRetVal)
return E_INVALIDARG;
*pRetVal = nullptr;
QAccessibleInterface *accessible = accessibleInterface();
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
QAccessibleInterface *firstSelectedChild = nullptr;
int i = 0;
while (!firstSelectedChild && i < accessible->childCount()) {
if (QAccessibleInterface *child = accessible->child(i)) {
if (accessible->role() == QAccessible::PageTabList) {
if (child->role() == QAccessible::PageTab && child->state().focused)
firstSelectedChild = child;
} else if (child->state().selected) {
firstSelectedChild = child;
}
}
i++;
}
if (!firstSelectedChild)
return UIA_E_ELEMENTNOTAVAILABLE;
if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(firstSelectedChild))
{
*pRetVal = static_cast<IRawElementProviderSimple *>(childProvider);
return S_OK;
}
return S_FALSE;
}
HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
{
qCDebug(lcQpaUiAutomation) << __FUNCTION__;
if (!pRetVal)
return E_INVALIDARG;
*pRetVal = nullptr;
QAccessibleInterface *accessible = accessibleInterface();
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
QAccessibleInterface *lastSelectedChild = nullptr;
int i = accessible->childCount() - 1;
while (!lastSelectedChild && i >= 0) {
if (QAccessibleInterface *child = accessible->child(i)) {
if (accessible->role() == QAccessible::PageTabList) {
if (child->role() == QAccessible::PageTab && child->state().focused)
lastSelectedChild = child;
} else if (child->state().selected) {
lastSelectedChild = child;
}
}
i--;
}
if (!lastSelectedChild)
return UIA_E_ELEMENTNOTAVAILABLE;
if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(lastSelectedChild))
{
*pRetVal = static_cast<IRawElementProviderSimple *>(childProvider);
return S_OK;
}
return S_FALSE;
}
HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
{
qCDebug(lcQpaUiAutomation) << __FUNCTION__;
return get_FirstSelectedItem(pRetVal);
}
HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_ItemCount(__RPC__out int *pRetVal)
{
qCDebug(lcQpaUiAutomation) << __FUNCTION__;
if (!pRetVal)
return E_INVALIDARG;
*pRetVal = -1;
QAccessibleInterface *accessible = accessibleInterface();
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
int selectedCount = 0;
for (int i = 0; i < accessible->childCount(); i++) {
if (QAccessibleInterface *child = accessible->child(i)) {
if (accessible->role() == QAccessible::PageTabList) {
if (child->role() == QAccessible::PageTab && child->state().focused)
selectedCount++;
} else if (child->state().selected) {
selectedCount++;
}
}
}
*pRetVal = selectedCount;
return S_OK;
}
QT_END_NAMESPACE
#endif // QT_CONFIG(accessibility)

View File

@ -13,17 +13,35 @@ QT_BEGIN_NAMESPACE
// Implements the Selection control pattern provider. Used for Lists.
class QWindowsUiaSelectionProvider : public QWindowsUiaBaseProvider,
public QWindowsComBase<ISelectionProvider>
public QWindowsComBase<ISelectionProvider2>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionProvider)
public:
explicit QWindowsUiaSelectionProvider(QAccessible::Id id);
virtual ~QWindowsUiaSelectionProvider();
// override to support ISelectionProvider and ISelectionProvider2 at the same time
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override
{
HRESULT res = QWindowsComBase<ISelectionProvider2>::QueryInterface(id, iface);
// QWindowsComBase<ISelectionProvider2>::QueryInterface doesn't handle ISelectionProvider,
// from which ISelectionProvider2 inherits
if (res == E_NOINTERFACE)
res = qWindowsComQueryInterface<ISelectionProvider>(this, id, iface) ? S_OK : E_NOINTERFACE;
return res;
};
// ISelectionProvider
HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override;
HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(BOOL *pRetVal) override;
HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(BOOL *pRetVal) override;
// ISelectionProvider2
HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *pRetVal) override;
};
QT_END_NAMESPACE