diff --git a/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h b/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h index fb74042bfa..b9ea861966 100644 --- a/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h +++ b/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h @@ -16,6 +16,7 @@ // #include +#include "uiatypes_p.h" #ifndef __IUIAutomationElement_INTERFACE_DEFINED__ @@ -30,6 +31,8 @@ struct IUIAutomationFocusChangedEventHandler; struct IUIAutomationProxyFactory; struct IUIAutomationProxyFactoryEntry; struct IUIAutomationProxyFactoryMapping; +struct IUIAutomationTextRangeArray; +struct IUIAutomationTextRange; #ifndef __IAccessible_FWD_DEFINED__ #define __IAccessible_FWD_DEFINED__ struct IAccessible; @@ -225,6 +228,59 @@ __CRT_UUID_DECL(IUIAutomationTreeWalker, 0x4042c624, 0x389c, 0x4afc, 0xa6,0x30, #endif #endif + +#ifndef __IUIAutomationTextPattern_INTERFACE_DEFINED__ +#define __IUIAutomationTextPattern_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IUIAutomationTextPattern, 0x32eba289, 0x3583, 0x42c9, 0x9c,0x59, 0x3b, 0x6d, 0x9a, 0x1e, 0x9b, 0x6a); +MIDL_INTERFACE("32eba289-3583-42c9-9c59-3b6d9a1e9b6a") +IUIAutomationTextPattern : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE RangeFromPoint(POINT pt, __RPC__deref_out_opt IUIAutomationTextRange **range) = 0; + virtual HRESULT STDMETHODCALLTYPE RangeFromChild(__RPC__in_opt IUIAutomationElement *child, __RPC__deref_out_opt IUIAutomationTextRange **range) = 0; + virtual HRESULT STDMETHODCALLTYPE GetSelection(__RPC__deref_out_opt IUIAutomationTextRangeArray **ranges) = 0; + virtual HRESULT STDMETHODCALLTYPE GetVisibleRanges(__RPC__deref_out_opt IUIAutomationTextRangeArray **ranges) = 0; + virtual HRESULT STDMETHODCALLTYPE get_DocumentRange(__RPC__deref_out_opt IUIAutomationTextRange **range) = 0; + virtual HRESULT STDMETHODCALLTYPE get_SupportedTextSelection(__RPC__out enum SupportedTextSelection *supportedTextSelection) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IUIAutomationTextPattern, 0x32eba289, 0x3583, 0x42c9, 0x9c,0x59, 0x3b, 0x6d, 0x9a, 0x1e, 0x9b, 0x6a) +#endif +#endif + + +#ifndef __IUIAutomationTextRange_INTERFACE_DEFINED__ +#define __IUIAutomationTextRange_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IUIAutomationTextRange, 0xa543cc6a, 0xf4ae, 0x494b, 0x82,0x39, 0xc8, 0x14, 0x48, 0x11, 0x87, 0xa8); +MIDL_INTERFACE("a543cc6a-f4ae-494b-8239-c814481187a8") +IUIAutomationTextRange : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE Clone(__RPC__deref_out_opt IUIAutomationTextRange **clonedRange) = 0; + virtual HRESULT STDMETHODCALLTYPE Compare(__RPC__in_opt IUIAutomationTextRange *range, __RPC__out BOOL *areSame) = 0; + virtual HRESULT STDMETHODCALLTYPE CompareEndpoints(enum TextPatternRangeEndpoint srcEndPoint, __RPC__in_opt IUIAutomationTextRange *range, enum TextPatternRangeEndpoint targetEndPoint, __RPC__out int *compValue) = 0; + virtual HRESULT STDMETHODCALLTYPE ExpandToEnclosingUnit(enum TextUnit textUnit) = 0; + virtual HRESULT STDMETHODCALLTYPE FindAttribute(TEXTATTRIBUTEID attr, VARIANT val, BOOL backward, __RPC__deref_out_opt IUIAutomationTextRange **found) = 0; + virtual HRESULT STDMETHODCALLTYPE FindText(__RPC__in BSTR text, BOOL backward, BOOL ignoreCase, __RPC__deref_out_opt IUIAutomationTextRange **found) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAttributeValue(TEXTATTRIBUTEID attr, __RPC__out VARIANT *value) = 0; + virtual HRESULT STDMETHODCALLTYPE GetBoundingRectangles(__RPC__deref_out_opt SAFEARRAY * *boundingRects) = 0; + virtual HRESULT STDMETHODCALLTYPE GetEnclosingElement(__RPC__deref_out_opt IUIAutomationElement **enclosingElement) = 0; + virtual HRESULT STDMETHODCALLTYPE GetText(int maxLength, __RPC__deref_out_opt BSTR *text) = 0; + virtual HRESULT STDMETHODCALLTYPE Move(enum TextUnit unit, int count, __RPC__out int *moved) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveEndpointByUnit(enum TextPatternRangeEndpoint endpoint, enum TextUnit unit, int count, __RPC__out int *moved) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveEndpointByRange(enum TextPatternRangeEndpoint srcEndPoint, __RPC__in_opt IUIAutomationTextRange *range, enum TextPatternRangeEndpoint targetEndPoint) = 0; + virtual HRESULT STDMETHODCALLTYPE Select( void) = 0; + virtual HRESULT STDMETHODCALLTYPE AddToSelection( void) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveFromSelection( void) = 0; + virtual HRESULT STDMETHODCALLTYPE ScrollIntoView(BOOL alignToTop) = 0; + virtual HRESULT STDMETHODCALLTYPE GetChildren(__RPC__deref_out_opt IUIAutomationElementArray **children) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IUIAutomationTextRange, 0xa543cc6a, 0xf4ae, 0x494b, 0x82,0x39, 0xc8, 0x14, 0x48, 0x11, 0x87, 0xa8) +#endif +#endif + + DEFINE_GUID(CLSID_CUIAutomation, 0xff48dba4, 0x60ef, 0x4201, 0xaa,0x87, 0x54,0x10,0x3e,0xef,0x59,0x4e); #endif diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp index 9e46ffd5eb..a62a33cfe2 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp @@ -316,14 +316,14 @@ HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit, int count, int *pRetVa int len = textInterface->characterCount(); - if (len < 1) + if (len < 1 || count == 0) // MSDN: "Zero has no effect." return S_OK; if (unit == TextUnit_Character) { // Moves the start point, ensuring it lies within the bounds. - int start = qBound(0, m_startOffset + count, len - 1); + int start = qBound(0, m_startOffset + count, len); // If range was initially empty, leaves it as is; otherwise, normalizes it to one char. - m_endOffset = (m_endOffset > m_startOffset) ? start + 1 : start; + m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start; *pRetVal = start - m_startOffset; // Returns the actually moved distance. m_startOffset = start; } else { @@ -394,7 +394,7 @@ HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoin if (unit == TextUnit_Character) { if (endpoint == TextPatternRangeEndpoint_Start) { - int boundedValue = qBound(0, m_startOffset + count, len - 1); + int boundedValue = qBound(0, m_startOffset + count, len); *pRetVal = boundedValue - m_startOffset; m_startOffset = boundedValue; m_endOffset = qBound(m_startOffset, m_endOffset, len); diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp index 99136979d6..37cff128b7 100644 --- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp +++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp @@ -3966,6 +3966,7 @@ void tst_QAccessibility::bridgeTest() // For now this is a simple test to see if the bridge is working at all. // Ideally it should be extended to test all aspects of the bridge. #if defined(Q_OS_WIN) + auto guard = qScopeGuard([]() { QTestAccessibility::clearEvents(); }); QWidget window; QVBoxLayout *lay = new QVBoxLayout(&window); @@ -4103,10 +4104,105 @@ void tst_QAccessibility::bridgeTest() QCOMPARE(controlTypeId, UIA_ButtonControlTypeId); // Edit - hr = nodeList.at(2)->get_CurrentControlType(&controlTypeId); + IUIAutomationElement *uiaElement = nodeList.at(2); + hr = uiaElement->get_CurrentControlType(&controlTypeId); QVERIFY(SUCCEEDED(hr)); QCOMPARE(controlTypeId, UIA_EditControlTypeId); + // "hello world\nhow are you today?\n" + IUIAutomationTextPattern *textPattern = nullptr; + hr = uiaElement->GetCurrentPattern(UIA_TextPattern2Id, reinterpret_cast(&textPattern)); + QVERIFY(SUCCEEDED(hr)); + QVERIFY(textPattern); + + IUIAutomationTextRange *docRange = nullptr; + hr = textPattern->get_DocumentRange(&docRange); + QVERIFY(SUCCEEDED(hr)); + QVERIFY(docRange); + + IUIAutomationTextRange *textRange = nullptr; + hr = docRange->Clone(&textRange); + QVERIFY(SUCCEEDED(hr)); + QVERIFY(textRange); + int moved; + + auto rangeText = [](IUIAutomationTextRange *textRange) { + BSTR str; + QString res = "IUIAutomationTextRange::GetText() failed"; + HRESULT hr = textRange->GetText(-1, &str); + if (SUCCEEDED(hr)) { + res = QString::fromWCharArray(str); + ::SysFreeString(str); + } + return res; + }; + + // Move start endpoint past "hello " to "world" + hr = textRange->Move(TextUnit_Character, 6, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(moved, 6); + // If the range was not empty, it should be collapsed to contain a single text unit + QCOMPARE(rangeText(textRange), QString("w")); + + // Move end endpoint to end of "world" + hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, 4, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(moved, 4); + QCOMPARE(rangeText(textRange), QString("world")); + + // MSDN: "Zero has no effect". This behavior was also verified with native controls. + hr = textRange->Move(TextUnit_Character, 0, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(moved, 0); + QCOMPARE(rangeText(textRange), QString("world")); + + hr = textRange->Move(TextUnit_Character, 1, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(rangeText(textRange), QString("o")); + + // move as far towards the end as possible + hr = textRange->Move(TextUnit_Character, 999, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(rangeText(textRange), QString("")); + + hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, -1, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(rangeText(textRange), QString("\n")); + + // move one forward (last possible position again) + hr = textRange->Move(TextUnit_Character, 1, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(rangeText(textRange), QString("")); + + hr = textRange->Move(TextUnit_Character, -7, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(moved, -7); + QCOMPARE(rangeText(textRange), QString("")); + // simulate moving cursor (empty range) towards (and past) the end + QString today(" today?\n"); + for (int i = 1; i < 9; ++i) { // 9 is deliberately too much + // peek one character back + hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, -1, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(rangeText(textRange), today.mid(i - 1, 1)); + + hr = textRange->Move(TextUnit_Character, 1, &moved); + QVERIFY(SUCCEEDED(hr)); + QCOMPARE(rangeText(textRange), today.mid(i, moved)); // when we cannot move further, moved will be 0 + + // Make the range empty again + hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -moved, &moved); + QVERIFY(SUCCEEDED(hr)); + + // advance the empty range + hr = textRange->Move(TextUnit_Character, 1, &moved); + QVERIFY(SUCCEEDED(hr)); + } + docRange->Release(); + textRange->Release(); + textPattern->Release(); + + // Table hr = nodeList.at(3)->get_CurrentControlType(&controlTypeId); QVERIFY(SUCCEEDED(hr)); @@ -4124,8 +4220,6 @@ void tst_QAccessibility::bridgeTest() controlWalker->Release(); windowElement->Release(); automation->Release(); - - QTestAccessibility::clearEvents(); #endif }