5#include <QtGui/qtguiglobal.h>
6#if QT_CONFIG(accessibility)
8#include "qwindowsuiatextrangeprovider.h"
9#include "qwindowsuiamainprovider.h"
10#include "qwindowsuiautils.h"
11#include "qwindowscontext.h"
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>
21using namespace QWindowsUiAutomation;
24QWindowsUiaTextRangeProvider::QWindowsUiaTextRangeProvider(QAccessible::Id id,
int startOffset,
int endOffset) :
25 QWindowsUiaBaseProvider(id),
26 m_startOffset(startOffset),
27 m_endOffset(endOffset)
29 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this << startOffset << endOffset;
32QWindowsUiaTextRangeProvider::~QWindowsUiaTextRangeProvider()
34 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
37HRESULT QWindowsUiaTextRangeProvider::AddToSelection()
39 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
43HRESULT QWindowsUiaTextRangeProvider::Clone(ITextRangeProvider **pRetVal)
45 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
50 *pRetVal = makeComObject<QWindowsUiaTextRangeProvider>(id(), m_startOffset, m_endOffset).Detach();
55HRESULT QWindowsUiaTextRangeProvider::Compare(ITextRangeProvider *range, BOOL *pRetVal)
57 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
59 if (!range || !pRetVal)
62 auto *targetProvider =
static_cast<QWindowsUiaTextRangeProvider *>(range);
63 *pRetVal = ((targetProvider->m_startOffset == m_startOffset) && (targetProvider->m_endOffset == m_endOffset));
68HRESULT QWindowsUiaTextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint,
69 ITextRangeProvider *targetRange,
70 TextPatternRangeEndpoint targetEndpoint,
73 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__
74 <<
"endpoint=" << endpoint <<
"targetRange=" << targetRange
75 <<
"targetEndpoint=" << targetEndpoint <<
"this: " <<
this;
77 if (!targetRange || !pRetVal)
80 auto *targetProvider =
static_cast<QWindowsUiaTextRangeProvider *>(targetRange);
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;
90HRESULT QWindowsUiaTextRangeProvider::ExpandToEnclosingUnit(TextUnit unit)
92 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"unit=" << unit <<
"this: " <<
this;
94 QAccessibleInterface *accessible = accessibleInterface();
96 return UIA_E_ELEMENTNOTAVAILABLE;
98 QAccessibleTextInterface *textInterface = accessible->textInterface();
100 return UIA_E_ELEMENTNOTAVAILABLE;
102 int len = textInterface->characterCount();
107 if (unit == TextUnit_Character) {
108 m_startOffset = qBound(0, m_startOffset, len - 1);
109 m_endOffset = m_startOffset + 1;
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]))) {
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])))) {
132HRESULT QWindowsUiaTextRangeProvider::FindAttribute(TEXTATTRIBUTEID ,
134 ITextRangeProvider **pRetVal)
143HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTATTRIBUTEID attributeId,
146 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"attributeId=" << attributeId <<
"this: " <<
this;
150 clearVariant(pRetVal);
152 QAccessibleInterface *accessible = accessibleInterface();
154 return UIA_E_ELEMENTNOTAVAILABLE;
156 QAccessibleTextInterface *textInterface = accessible->textInterface();
158 return UIA_E_ELEMENTNOTAVAILABLE;
160 switch (attributeId) {
161 case UIA_IsReadOnlyAttributeId:
162 *pRetVal = QComVariant{ accessible->state().readOnly ?
true :
false }.release();
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();
170 *pRetVal = QComVariant{
static_cast<
long>(CaretPosition_Unknown) }.release();
172 case UIA_StrikethroughStyleAttributeId:
174 const QString value = valueForIA2Attribute(textInterface, QStringLiteral(
"text-line-through-type"));
177 const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value);
178 *pRetVal = QComVariant{
static_cast<
long>(uiaLineStyle) }.release();
188HRESULT QWindowsUiaTextRangeProvider::GetBoundingRectangles(SAFEARRAY **pRetVal)
190 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
195 QAccessibleInterface *accessible = accessibleInterface();
197 return UIA_E_ELEMENTNOTAVAILABLE;
199 QAccessibleTextInterface *textInterface = accessible->textInterface();
201 return UIA_E_ELEMENTNOTAVAILABLE;
203 QWindow *window = windowForAccessible(accessible);
205 return UIA_E_ELEMENTNOTAVAILABLE;
207 int len = textInterface->characterCount();
208 QVarLengthArray<QRect> rectList;
210 if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset)) {
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) {
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);
226 if (end >= len)
break;
227 textInterface->textAfterOffset(end + 1, QAccessible::LineBoundary, &start, &end);
231 if ((*pRetVal = SafeArrayCreateVector(VT_R8, 0, 4 * rectList.size()))) {
232 for (
int i = 0; i < rectList.size(); ++i) {
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]);
247HRESULT QWindowsUiaTextRangeProvider::GetChildren(SAFEARRAY **pRetVal)
249 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
254 *pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 0);
259HRESULT QWindowsUiaTextRangeProvider::GetEnclosingElement(IRawElementProviderSimple **pRetVal)
261 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
266 QAccessibleInterface *accessible = accessibleInterface();
268 return UIA_E_ELEMENTNOTAVAILABLE;
270 *pRetVal = QWindowsUiaMainProvider::providerForAccessible(accessible).Detach();
275HRESULT QWindowsUiaTextRangeProvider::GetText(
int maxLength, BSTR *pRetVal)
277 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ << maxLength <<
"this: " <<
this;
283 QAccessibleInterface *accessible = accessibleInterface();
285 return UIA_E_ELEMENTNOTAVAILABLE;
287 QAccessibleTextInterface *textInterface = accessible->textInterface();
289 return UIA_E_ELEMENTNOTAVAILABLE;
291 int len = textInterface->characterCount();
293 if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset))
294 rangeText = textInterface->text(m_startOffset, m_endOffset);
296 if ((maxLength > -1) && (rangeText.size() > maxLength))
297 rangeText.truncate(maxLength);
298 *pRetVal = QBStr(rangeText).release();
303HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit,
int count,
int *pRetVal)
305 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"unit=" << unit <<
"count=" << count <<
"this: " <<
this;
311 QAccessibleInterface *accessible = accessibleInterface();
313 return UIA_E_ELEMENTNOTAVAILABLE;
315 QAccessibleTextInterface *textInterface = accessible->textInterface();
317 return UIA_E_ELEMENTNOTAVAILABLE;
319 int len = textInterface->characterCount();
321 if (len < 1 || count == 0)
324 if (unit == TextUnit_Character) {
326 int start = qBound(0, m_startOffset + count, len);
328 m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start;
329 *pRetVal = start - m_startOffset;
330 m_startOffset = start;
333 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal);
334 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal);
336 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal);
337 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal);
344HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint,
345 ITextRangeProvider *targetRange,
346 TextPatternRangeEndpoint targetEndpoint)
351 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__
352 <<
"endpoint=" << endpoint <<
"targetRange=" << targetRange <<
"targetEndpoint=" << targetEndpoint <<
"this: " <<
this;
354 auto *targetProvider =
static_cast<QWindowsUiaTextRangeProvider *>(targetRange);
356 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
357 targetProvider->m_startOffset : targetProvider->m_endOffset;
360 if (endpoint == TextPatternRangeEndpoint_Start) {
361 m_startOffset = targetPoint;
362 if (m_endOffset < m_startOffset)
363 m_endOffset = m_startOffset;
365 m_endOffset = targetPoint;
366 if (m_endOffset < m_startOffset)
367 m_startOffset = m_endOffset;
373HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
374 TextUnit unit,
int count,
377 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__
378 <<
"endpoint=" << endpoint <<
"unit=" << unit <<
"count=" << count <<
"this: " <<
this;
384 QAccessibleInterface *accessible = accessibleInterface();
386 return UIA_E_ELEMENTNOTAVAILABLE;
388 QAccessibleTextInterface *textInterface = accessible->textInterface();
390 return UIA_E_ELEMENTNOTAVAILABLE;
392 int len = textInterface->characterCount();
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);
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);
410 QString text = textInterface->text(0, len);
413 if (endpoint == TextPatternRangeEndpoint_Start) {
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;
421 m_endOffset = qBound(m_startOffset, m_endOffset, len);
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]))) {
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])))) {
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]))) {
449 m_startOffset = qBound(0, m_startOffset, m_endOffset);
457HRESULT QWindowsUiaTextRangeProvider::RemoveFromSelection()
459 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__;
465HRESULT QWindowsUiaTextRangeProvider::ScrollIntoView(BOOL alignToTop)
467 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"alignToTop=" << alignToTop <<
"this: " <<
this;
469 QAccessibleInterface *accessible = accessibleInterface();
471 return UIA_E_ELEMENTNOTAVAILABLE;
473 QAccessibleTextInterface *textInterface = accessible->textInterface();
475 return UIA_E_ELEMENTNOTAVAILABLE;
477 textInterface->scrollToSubstring(m_startOffset, m_endOffset);
482HRESULT QWindowsUiaTextRangeProvider::Select()
484 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
486 QAccessibleInterface *accessible = accessibleInterface();
488 return UIA_E_ELEMENTNOTAVAILABLE;
490 QAccessibleTextInterface *textInterface = accessible->textInterface();
492 return UIA_E_ELEMENTNOTAVAILABLE;
496 textInterface->addSelection(m_startOffset, m_endOffset);
501HRESULT QWindowsUiaTextRangeProvider::FindText(BSTR , BOOL ,
503 ITextRangeProvider **pRetVal)
512HRESULT QWindowsUiaTextRangeProvider::unselect()
514 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
516 QAccessibleInterface *accessible = accessibleInterface();
518 return UIA_E_ELEMENTNOTAVAILABLE;
520 QAccessibleTextInterface *textInterface = accessible->textInterface();
522 return UIA_E_ELEMENTNOTAVAILABLE;
524 int selCount = textInterface->selectionCount();
526 for (
int i = selCount - 1; i >= 0; --i)
527 textInterface->removeSelection(i);
533QString QWindowsUiaTextRangeProvider::valueForIA2Attribute(QAccessibleTextInterface *textInterface,
536 Q_ASSERT(textInterface);
540 const QString attributes = textInterface->attributes(m_startOffset, &startOffset, &endOffset);
542 if (startOffset > m_startOffset || endOffset < m_endOffset)
545 for (
auto attr : QStringTokenizer{attributes, u';'})
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();
555TextDecorationLineStyle QWindowsUiaTextRangeProvider::uiaLineStyleForIA2LineStyle(
const QString &ia2LineStyle)
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;
564 return TextDecorationLineStyle_Other;