a11y: Fix bug where some characters were not spoken while moving cursor
The problem occurred when we moved the cursor to the penultimate character of the string, because the boundary condition was wrong. It is important to realize that the offsets are moved *between* each character (and also before and after the whole string), just like you would move a cursor. This means that the offsets can be in the range [0, len] (closed interval) The problem could only be reproduced with JAWS. Pick-to: 6.6 6.5 6.2 Fixes: QTBUG-115156 Change-Id: I0c5f05fa391e6c7744ab22d71afe8904b49e89bc Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
This commit is contained in:
parent
1082038bd8
commit
bc8141d286
@ -16,6 +16,7 @@
|
||||
//
|
||||
|
||||
#include <unknwn.h>
|
||||
#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
|
||||
|
@ -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);
|
||||
|
@ -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<IUnknown**>(&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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user