Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsuiatextrangeprovider.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <QtGui/qtguiglobal.h>
6#if QT_CONFIG(accessibility)
7
8#include "qwindowsuiatextrangeprovider.h"
9#include "qwindowsuiamainprovider.h"
10#include "qwindowsuiautils.h"
11#include "qwindowscontext.h"
12
13#include <QtGui/qaccessible.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/qstring.h>
16#include <QtCore/qvarlengtharray.h>
17#include <QtCore/private/qcomvariant_p.h>
18
19QT_BEGIN_NAMESPACE
20
21using namespace QWindowsUiAutomation;
22
23
24QWindowsUiaTextRangeProvider::QWindowsUiaTextRangeProvider(QAccessible::Id id, int startOffset, int endOffset) :
25 QWindowsUiaBaseProvider(id),
26 m_startOffset(startOffset),
27 m_endOffset(endOffset)
28{
29 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this << startOffset << endOffset;
30}
31
32QWindowsUiaTextRangeProvider::~QWindowsUiaTextRangeProvider()
33{
34 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
35}
36
37HRESULT QWindowsUiaTextRangeProvider::AddToSelection()
38{
39 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
40 return Select();
41}
42
43HRESULT QWindowsUiaTextRangeProvider::Clone(ITextRangeProvider **pRetVal)
44{
45 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
46
47 if (!pRetVal)
48 return E_INVALIDARG;
49
50 *pRetVal = makeComObject<QWindowsUiaTextRangeProvider>(id(), m_startOffset, m_endOffset).Detach();
51 return S_OK;
52}
53
54// Two ranges are considered equal if their start/end points are the same.
55HRESULT QWindowsUiaTextRangeProvider::Compare(ITextRangeProvider *range, BOOL *pRetVal)
56{
57 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
58
59 if (!range || !pRetVal)
60 return E_INVALIDARG;
61
62 auto *targetProvider = static_cast<QWindowsUiaTextRangeProvider *>(range);
63 *pRetVal = ((targetProvider->m_startOffset == m_startOffset) && (targetProvider->m_endOffset == m_endOffset));
64 return S_OK;
65}
66
67// Compare different endpoinds between two providers.
68HRESULT QWindowsUiaTextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint,
69 ITextRangeProvider *targetRange,
70 TextPatternRangeEndpoint targetEndpoint,
71 int *pRetVal)
72{
73 qCDebug(lcQpaUiAutomation) << __FUNCTION__
74 << "endpoint=" << endpoint << "targetRange=" << targetRange
75 << "targetEndpoint=" << targetEndpoint << "this: " << this;
76
77 if (!targetRange || !pRetVal)
78 return E_INVALIDARG;
79
80 auto *targetProvider = static_cast<QWindowsUiaTextRangeProvider *>(targetRange);
81
82 int point = (endpoint == TextPatternRangeEndpoint_Start) ? m_startOffset : m_endOffset;
83 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
84 targetProvider->m_startOffset : targetProvider->m_endOffset;
85 *pRetVal = point - targetPoint;
86 return S_OK;
87}
88
89// Expands/normalizes the range for a given text unit.
90HRESULT QWindowsUiaTextRangeProvider::ExpandToEnclosingUnit(TextUnit unit)
91{
92 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "unit=" << unit << "this: " << this;
93
94 QAccessibleInterface *accessible = accessibleInterface();
95 if (!accessible)
96 return UIA_E_ELEMENTNOTAVAILABLE;
97
98 QAccessibleTextInterface *textInterface = accessible->textInterface();
99 if (!textInterface)
100 return UIA_E_ELEMENTNOTAVAILABLE;
101
102 int len = textInterface->characterCount();
103 if (len < 1) {
104 m_startOffset = 0;
105 m_endOffset = 0;
106 } else {
107 if (unit == TextUnit_Character) {
108 m_startOffset = qBound(0, m_startOffset, len - 1);
109 m_endOffset = m_startOffset + 1;
110 } else {
111 QString text = textInterface->text(0, len);
112 const int start = m_startOffset >= 0 && m_startOffset < len
113 ? m_startOffset : len - 1;
114 for (int t = start; t >= 0; --t) {
115 if (!text.isEmpty() && !isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) {
116 m_startOffset = t;
117 break;
118 }
119 }
120 for (int t = m_startOffset; t < len; ++t) {
121 if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) {
122 m_endOffset = t + 1;
123 break;
124 }
125 }
126 }
127 }
128 return S_OK;
129}
130
131// Not supported.
132HRESULT QWindowsUiaTextRangeProvider::FindAttribute(TEXTATTRIBUTEID /* attributeId */,
133 VARIANT /* val */, BOOL /* backward */,
134 ITextRangeProvider **pRetVal)
135{
136 if (!pRetVal)
137 return E_INVALIDARG;
138 *pRetVal = nullptr;
139 return S_OK;
140}
141
142// Returns the value of a given attribute.
143HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTATTRIBUTEID attributeId,
144 VARIANT *pRetVal)
145{
146 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "attributeId=" << attributeId << "this: " << this;
147
148 if (!pRetVal)
149 return E_INVALIDARG;
150 clearVariant(pRetVal);
151
152 QAccessibleInterface *accessible = accessibleInterface();
153 if (!accessible)
154 return UIA_E_ELEMENTNOTAVAILABLE;
155
156 QAccessibleTextInterface *textInterface = accessible->textInterface();
157 if (!textInterface)
158 return UIA_E_ELEMENTNOTAVAILABLE;
159
160 switch (attributeId) {
161 case UIA_IsReadOnlyAttributeId:
162 *pRetVal = QComVariant{ accessible->state().readOnly ? true : false }.release();
163 break;
164 case UIA_CaretPositionAttributeId:
165 if (textInterface->cursorPosition() == 0)
166 *pRetVal = QComVariant{ static_cast<long>(CaretPosition_BeginningOfLine) }.release();
167 else if (textInterface->cursorPosition() == textInterface->characterCount())
168 *pRetVal = QComVariant{ static_cast<long>(CaretPosition_EndOfLine) }.release();
169 else
170 *pRetVal = QComVariant{ static_cast<long>(CaretPosition_Unknown) }.release();
171 break;
172 case UIA_StrikethroughStyleAttributeId:
173 {
174 const QString value = valueForIA2Attribute(textInterface, QStringLiteral("text-line-through-type"));
175 if (value.isEmpty())
176 break;
177 const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value);
178 *pRetVal = QComVariant{ static_cast<long>(uiaLineStyle) }.release();
179 break;
180 }
181 default:
182 break;
183 }
184 return S_OK;
185}
186
187// Returns an array of bounding rectangles for text lines within the range.
188HRESULT QWindowsUiaTextRangeProvider::GetBoundingRectangles(SAFEARRAY **pRetVal)
189{
190 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
191
192 if (!pRetVal)
193 return E_INVALIDARG;
194
195 QAccessibleInterface *accessible = accessibleInterface();
196 if (!accessible)
197 return UIA_E_ELEMENTNOTAVAILABLE;
198
199 QAccessibleTextInterface *textInterface = accessible->textInterface();
200 if (!textInterface)
201 return UIA_E_ELEMENTNOTAVAILABLE;
202
203 QWindow *window = windowForAccessible(accessible);
204 if (!window)
205 return UIA_E_ELEMENTNOTAVAILABLE;
206
207 int len = textInterface->characterCount();
208 QVarLengthArray<QRect> rectList;
209
210 if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset)) {
211 int start, end;
212 textInterface->textAtOffset(m_startOffset, QAccessible::LineBoundary, &start, &end);
213 while ((start >= 0) && (end >= 0)) {
214 int startRange = qMax(start, m_startOffset);
215 int endRange = qMin(end, m_endOffset);
216 if (startRange < endRange) {
217 // Calculates a bounding rectangle for the line and adds it to the list.
218 const QRect startRect = textInterface->characterRect(startRange);
219 const QRect endRect = textInterface->characterRect(endRange - 1);
220 const QRect lineRect(qMin(startRect.x(), endRect.x()),
221 qMin(startRect.y(), endRect.y()),
222 qMax(startRect.x() + startRect.width(), endRect.x() + endRect.width()) - qMin(startRect.x(), endRect.x()),
223 qMax(startRect.y() + startRect.height(), endRect.y() + endRect.height()) - qMin(startRect.y(), endRect.y()));
224 rectList.append(lineRect);
225 }
226 if (end >= len) break;
227 textInterface->textAfterOffset(end + 1, QAccessible::LineBoundary, &start, &end);
228 }
229 }
230
231 if ((*pRetVal = SafeArrayCreateVector(VT_R8, 0, 4 * rectList.size()))) {
232 for (int i = 0; i < rectList.size(); ++i) {
233 // Scale rect for high DPI screens.
234 UiaRect uiaRect;
235 rectToNativeUiaRect(rectList[i], window, &uiaRect);
236 double coords[4] = { uiaRect.left, uiaRect.top, uiaRect.width, uiaRect.height };
237 for (int j = 0; j < 4; ++j) {
238 LONG idx = 4 * i + j;
239 SafeArrayPutElement(*pRetVal, &idx, &coords[j]);
240 }
241 }
242 }
243 return S_OK;
244}
245
246// Returns an array of children elements embedded within the range.
247HRESULT QWindowsUiaTextRangeProvider::GetChildren(SAFEARRAY **pRetVal)
248{
249 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
250
251 if (!pRetVal)
252 return E_INVALIDARG;
253 // Not supporting any children.
254 *pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 0);
255 return S_OK;
256}
257
258// Returns a provider for the enclosing element (text to which the range belongs).
259HRESULT QWindowsUiaTextRangeProvider::GetEnclosingElement(IRawElementProviderSimple **pRetVal)
260{
261 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
262
263 if (!pRetVal)
264 return E_INVALIDARG;
265
266 QAccessibleInterface *accessible = accessibleInterface();
267 if (!accessible)
268 return UIA_E_ELEMENTNOTAVAILABLE;
269
270 *pRetVal = QWindowsUiaMainProvider::providerForAccessible(accessible).Detach();
271 return S_OK;
272}
273
274// Gets the text within the range.
275HRESULT QWindowsUiaTextRangeProvider::GetText(int maxLength, BSTR *pRetVal)
276{
277 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << maxLength << "this: " << this;
278
279 if (!pRetVal)
280 return E_INVALIDARG;
281 *pRetVal = nullptr;
282
283 QAccessibleInterface *accessible = accessibleInterface();
284 if (!accessible)
285 return UIA_E_ELEMENTNOTAVAILABLE;
286
287 QAccessibleTextInterface *textInterface = accessible->textInterface();
288 if (!textInterface)
289 return UIA_E_ELEMENTNOTAVAILABLE;
290
291 int len = textInterface->characterCount();
292 QString rangeText;
293 if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset))
294 rangeText = textInterface->text(m_startOffset, m_endOffset);
295
296 if ((maxLength > -1) && (rangeText.size() > maxLength))
297 rangeText.truncate(maxLength);
298 *pRetVal = QBStr(rangeText).release();
299 return S_OK;
300}
301
302// Moves the range a specified number of units (and normalizes it).
303HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit, int count, int *pRetVal)
304{
305 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "unit=" << unit << "count=" << count << "this: " << this;
306
307 if (!pRetVal)
308 return E_INVALIDARG;
309 *pRetVal = 0;
310
311 QAccessibleInterface *accessible = accessibleInterface();
312 if (!accessible)
313 return UIA_E_ELEMENTNOTAVAILABLE;
314
315 QAccessibleTextInterface *textInterface = accessible->textInterface();
316 if (!textInterface)
317 return UIA_E_ELEMENTNOTAVAILABLE;
318
319 int len = textInterface->characterCount();
320
321 if (len < 1 || count == 0) // MSDN: "Zero has no effect."
322 return S_OK;
323
324 if (unit == TextUnit_Character) {
325 // Moves the start point, ensuring it lies within the bounds.
326 int start = qBound(0, m_startOffset + count, len);
327 // If range was initially empty, leaves it as is; otherwise, normalizes it to one char.
328 m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start;
329 *pRetVal = start - m_startOffset; // Returns the actually moved distance.
330 m_startOffset = start;
331 } else {
332 if (count > 0) {
333 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal);
334 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal);
335 } else {
336 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal);
337 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal);
338 }
339 }
340 return S_OK;
341}
342
343// Copies the value of an end point from one range to another.
344HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint,
345 ITextRangeProvider *targetRange,
346 TextPatternRangeEndpoint targetEndpoint)
347{
348 if (!targetRange)
349 return E_INVALIDARG;
350
351 qCDebug(lcQpaUiAutomation) << __FUNCTION__
352 << "endpoint=" << endpoint << "targetRange=" << targetRange << "targetEndpoint=" << targetEndpoint << "this: " << this;
353
354 auto *targetProvider = static_cast<QWindowsUiaTextRangeProvider *>(targetRange);
355
356 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
357 targetProvider->m_startOffset : targetProvider->m_endOffset;
358
359 // If the moved endpoint crosses the other endpoint, that one is moved too.
360 if (endpoint == TextPatternRangeEndpoint_Start) {
361 m_startOffset = targetPoint;
362 if (m_endOffset < m_startOffset)
363 m_endOffset = m_startOffset;
364 } else {
365 m_endOffset = targetPoint;
366 if (m_endOffset < m_startOffset)
367 m_startOffset = m_endOffset;
368 }
369 return S_OK;
370}
371
372// Moves an endpoint an specific number of units.
373HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
374 TextUnit unit, int count,
375 int *pRetVal)
376{
377 qCDebug(lcQpaUiAutomation) << __FUNCTION__
378 << "endpoint=" << endpoint << "unit=" << unit << "count=" << count << "this: " << this;
379
380 if (!pRetVal)
381 return E_INVALIDARG;
382 *pRetVal = 0;
383
384 QAccessibleInterface *accessible = accessibleInterface();
385 if (!accessible)
386 return UIA_E_ELEMENTNOTAVAILABLE;
387
388 QAccessibleTextInterface *textInterface = accessible->textInterface();
389 if (!textInterface)
390 return UIA_E_ELEMENTNOTAVAILABLE;
391
392 int len = textInterface->characterCount();
393
394 if (len < 1)
395 return S_OK;
396
397 if (unit == TextUnit_Character) {
398 if (endpoint == TextPatternRangeEndpoint_Start) {
399 int boundedValue = qBound(0, m_startOffset + count, len);
400 *pRetVal = boundedValue - m_startOffset;
401 m_startOffset = boundedValue;
402 m_endOffset = qBound(m_startOffset, m_endOffset, len);
403 } else {
404 int boundedValue = qBound(0, m_endOffset + count, len);
405 *pRetVal = boundedValue - m_endOffset;
406 m_endOffset = boundedValue;
407 m_startOffset = qBound(0, m_startOffset, m_endOffset);
408 }
409 } else {
410 QString text = textInterface->text(0, len);
411 int moved = 0;
412
413 if (endpoint == TextPatternRangeEndpoint_Start) {
414 if (count > 0) {
415 for (int t = m_startOffset; (t < len - 1) && (moved < count); ++t) {
416 if (!text.isEmpty() && isTextUnitSeparator(unit, text[t]) && !isTextUnitSeparator(unit, text[t + 1])) {
417 m_startOffset = t + 1;
418 ++moved;
419 }
420 }
421 m_endOffset = qBound(m_startOffset, m_endOffset, len);
422 } else {
423 const int start = m_startOffset >= 0 && m_startOffset <= len
424 ? m_startOffset : len;
425 for (int t = start - 1; (t >= 0) && (moved > count); --t) {
426 if (!text.isEmpty() &&!isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) {
427 m_startOffset = t;
428 --moved;
429 }
430 }
431 }
432 } else {
433 if (count > 0) {
434 for (int t = m_endOffset; (t < len) && (moved < count); ++t) {
435 if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) {
436 m_endOffset = t + 1;
437 ++moved;
438 }
439 }
440 } else {
441 int end = 0;
442 for (int t = m_endOffset - 2; (t > 0) && (moved > count); --t) {
443 if (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1]))) {
444 end = t + 1;
445 --moved;
446 }
447 }
448 m_endOffset = end;
449 m_startOffset = qBound(0, m_startOffset, m_endOffset);
450 }
451 }
452 *pRetVal = moved;
453 }
454 return S_OK;
455}
456
457HRESULT QWindowsUiaTextRangeProvider::RemoveFromSelection()
458{
459 qCDebug(lcQpaUiAutomation) << __FUNCTION__;
460 // unselects all
461 return unselect();
462}
463
464// Scrolls the range into view.
465HRESULT QWindowsUiaTextRangeProvider::ScrollIntoView(BOOL alignToTop)
466{
467 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "alignToTop=" << alignToTop << "this: " << this;
468
469 QAccessibleInterface *accessible = accessibleInterface();
470 if (!accessible)
471 return UIA_E_ELEMENTNOTAVAILABLE;
472
473 QAccessibleTextInterface *textInterface = accessible->textInterface();
474 if (!textInterface)
475 return UIA_E_ELEMENTNOTAVAILABLE;
476
477 textInterface->scrollToSubstring(m_startOffset, m_endOffset);
478 return S_OK;
479}
480
481// Selects the range.
482HRESULT QWindowsUiaTextRangeProvider::Select()
483{
484 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
485
486 QAccessibleInterface *accessible = accessibleInterface();
487 if (!accessible)
488 return UIA_E_ELEMENTNOTAVAILABLE;
489
490 QAccessibleTextInterface *textInterface = accessible->textInterface();
491 if (!textInterface)
492 return UIA_E_ELEMENTNOTAVAILABLE;
493
494 // unselects all and adds a new selection
495 unselect();
496 textInterface->addSelection(m_startOffset, m_endOffset);
497 return S_OK;
498}
499
500// Not supported.
501HRESULT QWindowsUiaTextRangeProvider::FindText(BSTR /* text */, BOOL /* backward */,
502 BOOL /* ignoreCase */,
503 ITextRangeProvider **pRetVal)
504{
505 if (!pRetVal)
506 return E_INVALIDARG;
507 *pRetVal = nullptr;
508 return S_OK;
509}
510
511// Removes all selected ranges from the text element.
512HRESULT QWindowsUiaTextRangeProvider::unselect()
513{
514 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
515
516 QAccessibleInterface *accessible = accessibleInterface();
517 if (!accessible)
518 return UIA_E_ELEMENTNOTAVAILABLE;
519
520 QAccessibleTextInterface *textInterface = accessible->textInterface();
521 if (!textInterface)
522 return UIA_E_ELEMENTNOTAVAILABLE;
523
524 int selCount = textInterface->selectionCount();
525
526 for (int i = selCount - 1; i >= 0; --i)
527 textInterface->removeSelection(i);
528 return S_OK;
529}
530
531// helper method to retrieve the value of the given IAccessible2 text attribute,
532// or an empty string if not set
533QString QWindowsUiaTextRangeProvider::valueForIA2Attribute(QAccessibleTextInterface *textInterface,
534 const QString &key)
535{
536 Q_ASSERT(textInterface);
537
538 int startOffset;
539 int endOffset;
540 const QString attributes = textInterface->attributes(m_startOffset, &startOffset, &endOffset);
541 // don't report if attributes don't apply for the whole range
542 if (startOffset > m_startOffset || endOffset < m_endOffset)
543 return {};
544
545 for (auto attr : QStringTokenizer{attributes, u';'})
546 {
547 const QList<QStringView> items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive);
548 if (items.count() == 2 && items[0] == key)
549 return items[1].toString();
550 }
551
552 return {};
553}
554
555TextDecorationLineStyle QWindowsUiaTextRangeProvider::uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle)
556{
557 if (ia2LineStyle == QStringLiteral("none"))
558 return TextDecorationLineStyle_None;
559 if (ia2LineStyle == QStringLiteral("single"))
560 return TextDecorationLineStyle_Single;
561 if (ia2LineStyle == QStringLiteral("double"))
562 return TextDecorationLineStyle_Double;
563
564 return TextDecorationLineStyle_Other;
565}
566
567QT_END_NAMESPACE
568
569#endif // QT_CONFIG(accessibility)