1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include <QtGui/qtguiglobal.h>
41 #if QT_CONFIG(accessibility)
42
43 #include "qwinrtuiatextrangeprovider.h"
44 #include "qwinrtuiametadatacache.h"
45 #include "qwinrtuiamainprovider.h"
46 #include "qwinrtuiautils.h"
47
48 #include <QtGui/QAccessible>
49 #include <QtGui/QAccessibleInterface>
50 #include <QtCore/QLoggingCategory>
51 #include <QtCore/QString>
52 #include <QtCore/private/qeventdispatcher_winrt_p.h>
53
54 #include <memory>
55
56 QT_BEGIN_NAMESPACE
57
58 using namespace QWinRTUiAutomation;
59 using namespace Microsoft::WRL;
60 using namespace Microsoft::WRL::Wrappers;
61 using namespace ABI::Windows::Foundation;
62 using namespace ABI::Windows::UI::Xaml;
63 using namespace ABI::Windows::UI::Xaml::Automation;
64 using namespace ABI::Windows::UI::Xaml::Automation::Provider;
65 using namespace ABI::Windows::UI::Xaml::Automation::Text;
66
QWinRTUiaTextRangeProvider(QAccessible::Id id,int startOffset,int endOffset)67 QWinRTUiaTextRangeProvider::QWinRTUiaTextRangeProvider(QAccessible::Id id, int startOffset, int endOffset) :
68 QWinRTUiaBaseProvider(id),
69 m_startOffset(startOffset),
70 m_endOffset(endOffset)
71 {
72 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << startOffset << endOffset;
73 }
74
~QWinRTUiaTextRangeProvider()75 QWinRTUiaTextRangeProvider::~QWinRTUiaTextRangeProvider()
76 {
77 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
78 }
79
Clone(ITextRangeProvider ** returnValue)80 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::Clone(ITextRangeProvider **returnValue)
81 {
82 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
83 if (!returnValue)
84 return E_INVALIDARG;
85
86 ComPtr<QWinRTUiaTextRangeProvider> textRangeProvider = Make<QWinRTUiaTextRangeProvider>(id(), m_startOffset, m_endOffset);
87 textRangeProvider.CopyTo(returnValue);
88 return S_OK;
89 }
90
91 // Two ranges are considered equal if their start/end points are the same.
Compare(ITextRangeProvider * textRangeProvider,boolean * returnValue)92 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::Compare(ITextRangeProvider *textRangeProvider, boolean *returnValue)
93 {
94 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
95 if (!textRangeProvider || !returnValue)
96 return E_INVALIDARG;
97
98 QWinRTUiaTextRangeProvider *targetProvider = static_cast<QWinRTUiaTextRangeProvider *>(textRangeProvider);
99 *returnValue = ((targetProvider->m_startOffset == m_startOffset) && (targetProvider->m_endOffset == m_endOffset));
100 return S_OK;
101 }
102
103 // Compare different endpoinds between two providers.
CompareEndpoints(TextPatternRangeEndpoint endpoint,ITextRangeProvider * textRangeProvider,TextPatternRangeEndpoint targetEndpoint,INT32 * returnValue)104 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider *textRangeProvider, TextPatternRangeEndpoint targetEndpoint, INT32 *returnValue)
105 {
106 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
107
108 if (!textRangeProvider || !returnValue)
109 return E_INVALIDARG;
110
111 QWinRTUiaTextRangeProvider *targetProvider = static_cast<QWinRTUiaTextRangeProvider *>(textRangeProvider);
112
113 int point = (endpoint == TextPatternRangeEndpoint_Start) ? m_startOffset : m_endOffset;
114 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
115 targetProvider->m_startOffset : targetProvider->m_endOffset;
116 *returnValue = point - targetPoint;
117 return S_OK;
118 }
119
120 // Expands/normalizes the range for a given text unit.
ExpandToEnclosingUnit(TextUnit unit)121 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::ExpandToEnclosingUnit(TextUnit unit)
122 {
123 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "unit=" << unit << "this: " << this;
124
125 QSharedPointer<QWinRTUiaControlMetadata> metadata = QWinRTUiaMetadataCache::instance()->metadataForId(id());
126
127 int len = metadata->characterCount();
128 if (len < 1) {
129 m_startOffset = 0;
130 m_endOffset = 0;
131 } else {
132 if (unit == TextUnit_Character) {
133 m_startOffset = qBound(0, m_startOffset, len - 1);
134 m_endOffset = m_startOffset + 1;
135 } else {
136 QString text = metadata->text();
137 for (int t = m_startOffset; t >= 0; --t) {
138 if (!isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) {
139 m_startOffset = t;
140 break;
141 }
142 }
143 for (int t = m_startOffset; t < len; ++t) {
144 if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) {
145 m_endOffset = t + 1;
146 break;
147 }
148 }
149 }
150 }
151 return S_OK;
152 }
153
154 // Not supported.
FindAttribute(INT32,IInspectable *,boolean,ITextRangeProvider ** returnValue)155 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::FindAttribute(INT32 /*attributeId*/, IInspectable * /*value*/, boolean /*backward*/, ITextRangeProvider **returnValue)
156 {
157 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
158 if (!returnValue)
159 return E_INVALIDARG;
160 *returnValue = nullptr;
161 return S_OK;
162 }
163
FindText(HSTRING,boolean,boolean,ITextRangeProvider ** returnValue)164 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::FindText(HSTRING /*text*/, boolean /*backward*/, boolean /*ignoreCase*/, ITextRangeProvider **returnValue)
165 {
166 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
167 if (!returnValue)
168 return E_INVALIDARG;
169 *returnValue = nullptr;
170 return S_OK;
171 }
172
173 // Returns the value of a given attribute.
GetAttributeValue(INT32 attributeId,IInspectable ** returnValue)174 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::GetAttributeValue(INT32 attributeId, IInspectable **returnValue)
175 {
176 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "attributeId=" << attributeId;
177
178 if (!returnValue)
179 return E_INVALIDARG;
180 *returnValue = nullptr;
181
182 ComPtr<IPropertyValueStatics> propertyValueStatics;
183 if (FAILED(RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_PropertyValue).Get(), IID_PPV_ARGS(&propertyValueStatics))))
184 return E_FAIL;
185
186 QSharedPointer<QWinRTUiaControlMetadata> metadata = QWinRTUiaMetadataCache::instance()->metadataForId(id());
187
188 switch (attributeId) {
189 case AutomationTextAttributesEnum_IsReadOnlyAttribute:
190 return propertyValueStatics->CreateBoolean(metadata->state().readOnly, returnValue);
191 case AutomationTextAttributesEnum_CaretPositionAttribute:
192 if (metadata->cursorPosition() == 0)
193 return propertyValueStatics->CreateInt32(AutomationCaretPosition_BeginningOfLine, returnValue);
194 else if (metadata->cursorPosition() == metadata->characterCount())
195 return propertyValueStatics->CreateInt32(AutomationCaretPosition_EndOfLine, returnValue);
196 else
197 return propertyValueStatics->CreateInt32(AutomationCaretPosition_Unknown, returnValue);
198 default:
199 break;
200 }
201 return E_FAIL;
202 }
203
204 // Returns an array of bounding rectangles for text lines within the range.
GetBoundingRectangles(UINT32 * returnValueSize,DOUBLE ** returnValue)205 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::GetBoundingRectangles(UINT32 *returnValueSize, DOUBLE **returnValue)
206 {
207 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
208
209 if (!returnValueSize || !returnValue)
210 return E_INVALIDARG;
211 *returnValueSize = 0;
212 *returnValue = nullptr;
213
214 auto accid = id();
215 auto startOffset = m_startOffset;
216 auto endOffset = m_endOffset;
217 auto rects = std::make_shared<QVarLengthArray<QRect>>();
218
219 if (!SUCCEEDED(QEventDispatcherWinRT::runOnMainThread([accid, startOffset, endOffset, rects]() {
220 if (QAccessibleInterface *accessible = accessibleForId(accid)) {
221 if (QAccessibleTextInterface *textInterface = accessible->textInterface()) {
222 int len = textInterface->characterCount();
223 if ((startOffset >= 0) && (endOffset <= len) && (startOffset < endOffset)) {
224 int start, end;
225 textInterface->textAtOffset(startOffset, QAccessible::LineBoundary, &start, &end);
226 while ((start >= 0) && (end >= 0)) {
227 int startRange = qMax(start, startOffset);
228 int endRange = qMin(end, endOffset);
229 if (startRange < endRange) {
230 // Calculates a bounding rectangle for the line and adds it to the list.
231 const QRect startRect = textInterface->characterRect(startRange);
232 const QRect endRect = textInterface->characterRect(endRange - 1);
233 const QRect lineRect(qMin(startRect.x(), endRect.x()),
234 qMin(startRect.y(), endRect.y()),
235 qMax(startRect.x() + startRect.width(), endRect.x() + endRect.width()) - qMin(startRect.x(), endRect.x()),
236 qMax(startRect.y() + startRect.height(), endRect.y() + endRect.height()) - qMin(startRect.y(), endRect.y()));
237 rects->append(lineRect);
238 }
239 if (end >= len) break;
240 textInterface->textAfterOffset(end + 1, QAccessible::LineBoundary, &start, &end);
241 }
242 }
243 }
244 }
245 return S_OK;
246 }))) {
247 return E_FAIL;
248 }
249
250 DOUBLE *doubleArray = static_cast<DOUBLE *>(CoTaskMemAlloc(4 * rects->size() * sizeof(DOUBLE)));
251 if (!doubleArray)
252 return E_OUTOFMEMORY;
253
254 DOUBLE *dst = doubleArray;
255 for (auto rect : *rects) {
256 *dst++ = rect.left();
257 *dst++ = rect.top();
258 *dst++ = rect.width();
259 *dst++ = rect.height();
260 }
261 *returnValue = doubleArray;
262 *returnValueSize = 4 * rects->size();
263 return S_OK;
264 }
265
GetEnclosingElement(IIRawElementProviderSimple ** returnValue)266 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::GetEnclosingElement(IIRawElementProviderSimple **returnValue)
267 {
268 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
269 if (!returnValue)
270 return E_INVALIDARG;
271 return QWinRTUiaMainProvider::rawProviderForAccessibleId(id(), returnValue);
272 }
273
GetText(INT32 maxLength,HSTRING * returnValue)274 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::GetText(INT32 maxLength, HSTRING *returnValue)
275 {
276 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
277 if (!returnValue)
278 return E_INVALIDARG;
279 *returnValue = nullptr;
280
281 QSharedPointer<QWinRTUiaControlMetadata> metadata = QWinRTUiaMetadataCache::instance()->metadataForId(id());
282
283 QString rangeText = metadata->text().mid(m_startOffset, m_endOffset - m_startOffset);
284
285 if ((maxLength > -1) && (rangeText.size() > maxLength))
286 rangeText.truncate(maxLength);
287 return qHString(rangeText, returnValue);
288 }
289
Move(TextUnit unit,INT32 count,INT32 * returnValue)290 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::Move(TextUnit unit, INT32 count, INT32 *returnValue)
291 {
292 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
293 if (!returnValue)
294 return E_INVALIDARG;
295 *returnValue = 0;
296
297 QSharedPointer<QWinRTUiaControlMetadata> metadata = QWinRTUiaMetadataCache::instance()->metadataForId(id());
298
299 int len = metadata->characterCount();
300 if (len < 1)
301 return S_OK;
302
303 if (unit == TextUnit_Character) {
304 // Moves the start point, ensuring it lies within the bounds.
305 int start = qBound(0, m_startOffset + count, len - 1);
306 // If range was initially empty, leaves it as is; otherwise, normalizes it to one char.
307 m_endOffset = (m_endOffset > m_startOffset) ? start + 1 : start;
308 *returnValue = start - m_startOffset; // Returns the actually moved distance.
309 m_startOffset = start;
310 } else {
311 if (count > 0) {
312 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, returnValue);
313 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, returnValue);
314 } else {
315 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, returnValue);
316 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, returnValue);
317 }
318 }
319 return S_OK;
320 }
321
MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,TextUnit unit,INT32 count,INT32 * returnValue)322 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, INT32 count, INT32 *returnValue)
323 {
324 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
325 if (!returnValue)
326 return E_INVALIDARG;
327 *returnValue = 0;
328
329 QSharedPointer<QWinRTUiaControlMetadata> metadata = QWinRTUiaMetadataCache::instance()->metadataForId(id());
330
331 int len = metadata->characterCount();
332 if (len < 1)
333 return S_OK;
334
335 if (unit == TextUnit_Character) {
336 if (endpoint == TextPatternRangeEndpoint_Start) {
337 int boundedValue = qBound(0, m_startOffset + count, len - 1);
338 *returnValue = boundedValue - m_startOffset;
339 m_startOffset = boundedValue;
340 m_endOffset = qBound(m_startOffset, m_endOffset, len);
341 } else {
342 int boundedValue = qBound(0, m_endOffset + count, len);
343 *returnValue = boundedValue - m_endOffset;
344 m_endOffset = boundedValue;
345 m_startOffset = qBound(0, m_startOffset, m_endOffset);
346 }
347 } else {
348 QString text = metadata->text();
349 int moved = 0;
350
351 if (endpoint == TextPatternRangeEndpoint_Start) {
352 if (count > 0) {
353 for (int t = m_startOffset; (t < len - 1) && (moved < count); ++t) {
354 if (isTextUnitSeparator(unit, text[t]) && !isTextUnitSeparator(unit, text[t + 1])) {
355 m_startOffset = t + 1;
356 ++moved;
357 }
358 }
359 m_endOffset = qBound(m_startOffset, m_endOffset, len);
360 } else {
361 for (int t = m_startOffset - 1; (t >= 0) && (moved > count); --t) {
362 if (!isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) {
363 m_startOffset = t;
364 --moved;
365 }
366 }
367 }
368 } else {
369 if (count > 0) {
370 for (int t = m_endOffset; (t < len) && (moved < count); ++t) {
371 if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) {
372 m_endOffset = t + 1;
373 ++moved;
374 }
375 }
376 } else {
377 int end = 0;
378 for (int t = m_endOffset - 2; (t > 0) && (moved > count); --t) {
379 if (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1]))) {
380 end = t + 1;
381 --moved;
382 }
383 }
384 m_endOffset = end;
385 m_startOffset = qBound(0, m_startOffset, m_endOffset);
386 }
387 }
388 *returnValue = moved;
389 }
390 return S_OK;
391 }
392
MoveEndpointByRange(TextPatternRangeEndpoint endpoint,ITextRangeProvider * textRangeProvider,TextPatternRangeEndpoint targetEndpoint)393 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider *textRangeProvider, TextPatternRangeEndpoint targetEndpoint)
394 {
395 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
396 if (!textRangeProvider)
397 return E_INVALIDARG;
398
399 QWinRTUiaTextRangeProvider *targetProvider = static_cast<QWinRTUiaTextRangeProvider *>(textRangeProvider);
400
401 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
402 targetProvider->m_startOffset : targetProvider->m_endOffset;
403
404 // If the moved endpoint crosses the other endpoint, that one is moved too.
405 if (endpoint == TextPatternRangeEndpoint_Start) {
406 m_startOffset = targetPoint;
407 if (m_endOffset < m_startOffset)
408 m_endOffset = m_startOffset;
409 } else {
410 m_endOffset = targetPoint;
411 if (m_endOffset < m_startOffset)
412 m_startOffset = m_endOffset;
413 }
414 return S_OK;
415 }
416
Select()417 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::Select()
418 {
419 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
420
421 auto accid = id();
422 auto startOffset = m_startOffset;
423 auto endOffset = m_endOffset;
424
425 QEventDispatcherWinRT::runOnMainThread([accid, startOffset, endOffset]() {
426 if (QAccessibleInterface *accessible = accessibleForId(accid))
427 if (QAccessibleTextInterface *textInterface = accessible->textInterface()) {
428 // unselects all and adds a new selection
429 for (int i = textInterface->selectionCount() - 1; i >= 0; --i)
430 textInterface->removeSelection(i);
431 textInterface->addSelection(startOffset, endOffset);
432 }
433 QWinRTUiaMetadataCache::instance()->load(accid);
434 return S_OK;
435 }, 0);
436 return S_OK;
437 }
438
AddToSelection()439 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::AddToSelection()
440 {
441 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
442 return Select();
443 }
444
RemoveFromSelection()445 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::RemoveFromSelection()
446 {
447 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
448
449 auto accid = id();
450
451 QEventDispatcherWinRT::runOnMainThread([accid]() {
452 if (QAccessibleInterface *accessible = accessibleForId(accid))
453 if (QAccessibleTextInterface *textInterface = accessible->textInterface()) {
454 // unselects all
455 for (int i = textInterface->selectionCount() - 1; i >= 0; --i)
456 textInterface->removeSelection(i);
457 }
458 QWinRTUiaMetadataCache::instance()->load(accid);
459 return S_OK;
460 }, 0);
461 return S_OK;
462 }
463
ScrollIntoView(boolean)464 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::ScrollIntoView(boolean /*alignToTop*/)
465 {
466 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
467
468 auto accid = id();
469 auto startOffset = m_startOffset;
470 auto endOffset = m_endOffset;
471
472 QEventDispatcherWinRT::runOnMainThread([accid, startOffset, endOffset]() {
473 if (QAccessibleInterface *accessible = accessibleForId(accid))
474 if (QAccessibleTextInterface *textInterface = accessible->textInterface()) {
475 textInterface->scrollToSubstring(startOffset, endOffset);
476 }
477 QWinRTUiaMetadataCache::instance()->load(accid);
478 return S_OK;
479 }, 0);
480 return S_OK;
481 }
482
483 // Returns an array of children elements embedded within the range.
GetChildren(UINT32 * returnValueSize,IIRawElementProviderSimple *** returnValue)484 HRESULT STDMETHODCALLTYPE QWinRTUiaTextRangeProvider::GetChildren(UINT32 *returnValueSize, IIRawElementProviderSimple ***returnValue)
485 {
486 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
487
488 if (!returnValue)
489 return E_INVALIDARG;
490 // Not supporting any children.
491 returnValueSize = 0;
492 *returnValue = nullptr;
493 return S_OK;
494 }
495
496 QT_END_NAMESPACE
497
498 #endif // QT_CONFIG(accessibility)
499