4#include <QtGui/qtguiglobal.h>
5#if QT_CONFIG(accessibility)
7#include "qwindowsuiatextrangeprovider.h"
8#include "qwindowsuiamainprovider.h"
9#include "qwindowsuiautils.h"
10#include "qwindowscontext.h"
12#include <QtGui/qaccessible.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qstring.h>
15#include <QtCore/qvarlengtharray.h>
16#include <QtCore/private/qcomvariant_p.h>
20using namespace QWindowsUiAutomation;
23QWindowsUiaTextRangeProvider::QWindowsUiaTextRangeProvider(QAccessible::Id id,
int startOffset,
int endOffset) :
24 QWindowsUiaBaseProvider(id),
25 m_startOffset(startOffset),
26 m_endOffset(endOffset)
28 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this << startOffset << endOffset;
31QWindowsUiaTextRangeProvider::~QWindowsUiaTextRangeProvider()
33 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
36HRESULT QWindowsUiaTextRangeProvider::AddToSelection()
38 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
42HRESULT QWindowsUiaTextRangeProvider::Clone(ITextRangeProvider **pRetVal)
44 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
49 *pRetVal = makeComObject<QWindowsUiaTextRangeProvider>(id(), m_startOffset, m_endOffset).Detach();
54HRESULT QWindowsUiaTextRangeProvider::Compare(ITextRangeProvider *range, BOOL *pRetVal)
56 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
58 if (!range || !pRetVal)
61 auto *targetProvider =
static_cast<QWindowsUiaTextRangeProvider *>(range);
62 *pRetVal = ((targetProvider->m_startOffset == m_startOffset) && (targetProvider->m_endOffset == m_endOffset));
67HRESULT QWindowsUiaTextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint,
68 ITextRangeProvider *targetRange,
69 TextPatternRangeEndpoint targetEndpoint,
72 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__
73 <<
"endpoint=" << endpoint <<
"targetRange=" << targetRange
74 <<
"targetEndpoint=" << targetEndpoint <<
"this: " <<
this;
76 if (!targetRange || !pRetVal)
79 auto *targetProvider =
static_cast<QWindowsUiaTextRangeProvider *>(targetRange);
81 int point = (endpoint == TextPatternRangeEndpoint_Start) ? m_startOffset : m_endOffset;
82 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
83 targetProvider->m_startOffset : targetProvider->m_endOffset;
84 *pRetVal = point - targetPoint;
89HRESULT QWindowsUiaTextRangeProvider::ExpandToEnclosingUnit(TextUnit unit)
91 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"unit=" << unit <<
"this: " <<
this;
93 QAccessibleInterface *accessible = accessibleInterface();
95 return UIA_E_ELEMENTNOTAVAILABLE;
97 QAccessibleTextInterface *textInterface = accessible->textInterface();
99 return UIA_E_ELEMENTNOTAVAILABLE;
101 int len = textInterface->characterCount();
106 if (unit == TextUnit_Character) {
107 m_startOffset = qBound(0, m_startOffset, len - 1);
108 m_endOffset = m_startOffset + 1;
110 QString text = textInterface->text(0, len);
111 const int start = m_startOffset >= 0 && m_startOffset < len
112 ? m_startOffset : len - 1;
113 for (
int t = start; t >= 0; --t) {
114 if (!text.isEmpty() && !isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) {
119 for (
int t = m_startOffset; t < len; ++t) {
120 if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) {
131HRESULT QWindowsUiaTextRangeProvider::FindAttribute(TEXTATTRIBUTEID ,
133 ITextRangeProvider **pRetVal)
142HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTATTRIBUTEID attributeId,
145 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"attributeId=" << attributeId <<
"this: " <<
this;
149 clearVariant(pRetVal);
151 QAccessibleInterface *accessible = accessibleInterface();
153 return UIA_E_ELEMENTNOTAVAILABLE;
155 QAccessibleTextInterface *textInterface = accessible->textInterface();
157 return UIA_E_ELEMENTNOTAVAILABLE;
159 switch (attributeId) {
160 case UIA_IsReadOnlyAttributeId:
161 *pRetVal = QComVariant{ accessible->state().readOnly ?
true :
false }.release();
163 case UIA_CaretPositionAttributeId:
164 if (textInterface->cursorPosition() == 0)
165 *pRetVal = QComVariant{
static_cast<
long>(CaretPosition_BeginningOfLine) }.release();
166 else if (textInterface->cursorPosition() == textInterface->characterCount())
167 *pRetVal = QComVariant{
static_cast<
long>(CaretPosition_EndOfLine) }.release();
169 *pRetVal = QComVariant{
static_cast<
long>(CaretPosition_Unknown) }.release();
171 case UIA_StrikethroughStyleAttributeId:
173 const QString value = valueForIA2Attribute(textInterface, QStringLiteral(
"text-line-through-type"));
176 const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value);
177 *pRetVal = QComVariant{
static_cast<
long>(uiaLineStyle) }.release();
187HRESULT QWindowsUiaTextRangeProvider::GetBoundingRectangles(SAFEARRAY **pRetVal)
189 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
194 QAccessibleInterface *accessible = accessibleInterface();
196 return UIA_E_ELEMENTNOTAVAILABLE;
198 QAccessibleTextInterface *textInterface = accessible->textInterface();
200 return UIA_E_ELEMENTNOTAVAILABLE;
202 QWindow *window = windowForAccessible(accessible);
204 return UIA_E_ELEMENTNOTAVAILABLE;
206 int len = textInterface->characterCount();
207 QVarLengthArray<QRect> rectList;
209 if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset)) {
211 textInterface->textAtOffset(m_startOffset, QAccessible::LineBoundary, &start, &end);
212 while ((start >= 0) && (end >= 0)) {
213 int startRange = qMax(start, m_startOffset);
214 int endRange = qMin(end, m_endOffset);
215 if (startRange < endRange) {
217 const QRect startRect = textInterface->characterRect(startRange);
218 const QRect endRect = textInterface->characterRect(endRange - 1);
219 const QRect lineRect(qMin(startRect.x(), endRect.x()),
220 qMin(startRect.y(), endRect.y()),
221 qMax(startRect.x() + startRect.width(), endRect.x() + endRect.width()) - qMin(startRect.x(), endRect.x()),
222 qMax(startRect.y() + startRect.height(), endRect.y() + endRect.height()) - qMin(startRect.y(), endRect.y()));
223 rectList.append(lineRect);
225 if (end >= len)
break;
226 textInterface->textAfterOffset(end + 1, QAccessible::LineBoundary, &start, &end);
230 if ((*pRetVal = SafeArrayCreateVector(VT_R8, 0, 4 * rectList.size()))) {
231 for (
int i = 0; i < rectList.size(); ++i) {
234 rectToNativeUiaRect(rectList[i], window, &uiaRect);
235 double coords[4] = { uiaRect.left, uiaRect.top, uiaRect.width, uiaRect.height };
236 for (
int j = 0; j < 4; ++j) {
237 LONG idx = 4 * i + j;
238 SafeArrayPutElement(*pRetVal, &idx, &coords[j]);
246HRESULT QWindowsUiaTextRangeProvider::GetChildren(SAFEARRAY **pRetVal)
248 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
253 *pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 0);
258HRESULT QWindowsUiaTextRangeProvider::GetEnclosingElement(IRawElementProviderSimple **pRetVal)
260 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
265 QAccessibleInterface *accessible = accessibleInterface();
267 return UIA_E_ELEMENTNOTAVAILABLE;
269 *pRetVal = QWindowsUiaMainProvider::providerForAccessible(accessible).Detach();
274HRESULT QWindowsUiaTextRangeProvider::GetText(
int maxLength, BSTR *pRetVal)
276 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ << maxLength <<
"this: " <<
this;
282 QAccessibleInterface *accessible = accessibleInterface();
284 return UIA_E_ELEMENTNOTAVAILABLE;
286 QAccessibleTextInterface *textInterface = accessible->textInterface();
288 return UIA_E_ELEMENTNOTAVAILABLE;
290 int len = textInterface->characterCount();
292 if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset))
293 rangeText = textInterface->text(m_startOffset, m_endOffset);
295 if ((maxLength > -1) && (rangeText.size() > maxLength))
296 rangeText.truncate(maxLength);
297 *pRetVal = QBStr(rangeText).release();
302HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit,
int count,
int *pRetVal)
304 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"unit=" << unit <<
"count=" << count <<
"this: " <<
this;
310 QAccessibleInterface *accessible = accessibleInterface();
312 return UIA_E_ELEMENTNOTAVAILABLE;
314 QAccessibleTextInterface *textInterface = accessible->textInterface();
316 return UIA_E_ELEMENTNOTAVAILABLE;
318 int len = textInterface->characterCount();
320 if (len < 1 || count == 0)
323 if (unit == TextUnit_Character) {
325 int start = qBound(0, m_startOffset + count, len);
327 m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start;
328 *pRetVal = start - m_startOffset;
329 m_startOffset = start;
332 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal);
333 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal);
335 MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal);
336 MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal);
343HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint,
344 ITextRangeProvider *targetRange,
345 TextPatternRangeEndpoint targetEndpoint)
350 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__
351 <<
"endpoint=" << endpoint <<
"targetRange=" << targetRange <<
"targetEndpoint=" << targetEndpoint <<
"this: " <<
this;
353 auto *targetProvider =
static_cast<QWindowsUiaTextRangeProvider *>(targetRange);
355 int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ?
356 targetProvider->m_startOffset : targetProvider->m_endOffset;
359 if (endpoint == TextPatternRangeEndpoint_Start) {
360 m_startOffset = targetPoint;
361 if (m_endOffset < m_startOffset)
362 m_endOffset = m_startOffset;
364 m_endOffset = targetPoint;
365 if (m_endOffset < m_startOffset)
366 m_startOffset = m_endOffset;
372HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
373 TextUnit unit,
int count,
376 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__
377 <<
"endpoint=" << endpoint <<
"unit=" << unit <<
"count=" << count <<
"this: " <<
this;
383 QAccessibleInterface *accessible = accessibleInterface();
385 return UIA_E_ELEMENTNOTAVAILABLE;
387 QAccessibleTextInterface *textInterface = accessible->textInterface();
389 return UIA_E_ELEMENTNOTAVAILABLE;
391 int len = textInterface->characterCount();
396 if (unit == TextUnit_Character) {
397 if (endpoint == TextPatternRangeEndpoint_Start) {
398 int boundedValue = qBound(0, m_startOffset + count, len);
399 *pRetVal = boundedValue - m_startOffset;
400 m_startOffset = boundedValue;
401 m_endOffset = qBound(m_startOffset, m_endOffset, len);
403 int boundedValue = qBound(0, m_endOffset + count, len);
404 *pRetVal = boundedValue - m_endOffset;
405 m_endOffset = boundedValue;
406 m_startOffset = qBound(0, m_startOffset, m_endOffset);
409 QString text = textInterface->text(0, len);
412 if (endpoint == TextPatternRangeEndpoint_Start) {
414 for (
int t = m_startOffset; (t < len - 1) && (moved < count); ++t) {
415 if (!text.isEmpty() && isTextUnitSeparator(unit, text[t]) && !isTextUnitSeparator(unit, text[t + 1])) {
416 m_startOffset = t + 1;
420 m_endOffset = qBound(m_startOffset, m_endOffset, len);
422 const int start = m_startOffset >= 0 && m_startOffset <= len
423 ? m_startOffset : len;
424 for (
int t = start - 1; (t >= 0) && (moved > count); --t) {
425 if (!text.isEmpty() &&!isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) {
433 for (
int t = m_endOffset; (t < len) && (moved < count); ++t) {
434 if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) {
441 for (
int t = m_endOffset - 2; (t > 0) && (moved > count); --t) {
442 if (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1]))) {
448 m_startOffset = qBound(0, m_startOffset, m_endOffset);
456HRESULT QWindowsUiaTextRangeProvider::RemoveFromSelection()
458 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__;
464HRESULT QWindowsUiaTextRangeProvider::ScrollIntoView(BOOL alignToTop)
466 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
"alignToTop=" << alignToTop <<
"this: " <<
this;
468 QAccessibleInterface *accessible = accessibleInterface();
470 return UIA_E_ELEMENTNOTAVAILABLE;
472 QAccessibleTextInterface *textInterface = accessible->textInterface();
474 return UIA_E_ELEMENTNOTAVAILABLE;
476 textInterface->scrollToSubstring(m_startOffset, m_endOffset);
481HRESULT QWindowsUiaTextRangeProvider::Select()
483 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
485 QAccessibleInterface *accessible = accessibleInterface();
487 return UIA_E_ELEMENTNOTAVAILABLE;
489 QAccessibleTextInterface *textInterface = accessible->textInterface();
491 return UIA_E_ELEMENTNOTAVAILABLE;
495 textInterface->addSelection(m_startOffset, m_endOffset);
500HRESULT QWindowsUiaTextRangeProvider::FindText(BSTR , BOOL ,
502 ITextRangeProvider **pRetVal)
511HRESULT QWindowsUiaTextRangeProvider::unselect()
513 qCDebug(lcQpaUiAutomation) <<
__FUNCTION__ <<
this;
515 QAccessibleInterface *accessible = accessibleInterface();
517 return UIA_E_ELEMENTNOTAVAILABLE;
519 QAccessibleTextInterface *textInterface = accessible->textInterface();
521 return UIA_E_ELEMENTNOTAVAILABLE;
523 int selCount = textInterface->selectionCount();
525 for (
int i = selCount - 1; i >= 0; --i)
526 textInterface->removeSelection(i);
532QString QWindowsUiaTextRangeProvider::valueForIA2Attribute(QAccessibleTextInterface *textInterface,
535 Q_ASSERT(textInterface);
539 const QString attributes = textInterface->attributes(m_startOffset, &startOffset, &endOffset);
541 if (startOffset > m_startOffset || endOffset < m_endOffset)
544 for (
auto attr : QStringTokenizer{attributes, u';'})
546 const QList<QStringView> items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive);
547 if (items.count() == 2 && items[0] == key)
548 return items[1].toString();
554TextDecorationLineStyle QWindowsUiaTextRangeProvider::uiaLineStyleForIA2LineStyle(
const QString &ia2LineStyle)
556 if (ia2LineStyle == QStringLiteral(
"none"))
557 return TextDecorationLineStyle_None;
558 if (ia2LineStyle == QStringLiteral(
"single"))
559 return TextDecorationLineStyle_Single;
560 if (ia2LineStyle == QStringLiteral(
"double"))
561 return TextDecorationLineStyle_Double;
563 return TextDecorationLineStyle_Other;