1// Copyright (C) 2016 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
4#include "qaccessiblequickitem_p.h"
5
6#include <QtGui/qtextdocument.h>
7
8#include "QtQuick/private/qquickitem_p.h"
9#include "QtQuick/private/qquicktext_p.h"
10#include <private/qquicktext_p_p.h>
11
12#include "QtQuick/private/qquicktextinput_p.h"
13#include "QtQuick/private/qquickaccessibleattached_p.h"
14#include "QtQuick/qquicktextdocument.h"
15#include "QtQuick/qquickrendercontrol.h"
16QT_BEGIN_NAMESPACE
17
18#if QT_CONFIG(accessibility)
19
20class QAccessibleHyperlink : public QAccessibleInterface, public QAccessibleHyperlinkInterface {
21public:
22 QAccessibleHyperlink(QQuickItem *parentTextItem, int linkIndex);
23
24 // check for valid pointers
25 bool isValid() const override;
26 QObject *object() const override;
27 QWindow *window() const override;
28
29 // navigation, hierarchy
30 QAccessibleInterface *parent() const override;
31 QAccessibleInterface *child(int index) const override;
32 int childCount() const override;
33 int indexOfChild(const QAccessibleInterface *iface) const override;
34 QAccessibleInterface *childAt(int x, int y) const override;
35
36 // properties and state
37 QString text(QAccessible::Text) const override;
38 void setText(QAccessible::Text, const QString &text) override;
39 QRect rect() const override;
40 QAccessible::Role role() const override;
41 QAccessible::State state() const override;
42
43 void *interface_cast(QAccessible::InterfaceType t) override;
44
45 // QAccessibleHyperlinkInterface
46 QString anchor() const override
47 {
48 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(t: textItem())->getLinks();
49 if (linkIndex < links.size())
50 return links.at(i: linkIndex).m_anchor;
51 return QString();
52 }
53
54 QString anchorTarget() const override
55 {
56 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(t: textItem())->getLinks();
57 if (linkIndex < links.size())
58 return links.at(i: linkIndex).m_anchorTarget;
59 return QString();
60 }
61
62 int startIndex() const override
63 {
64 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(t: textItem())->getLinks();
65 if (linkIndex < links.size())
66 return links.at(i: linkIndex).m_startIndex;
67 return -1;
68 }
69
70 int endIndex() const override
71 {
72 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(t: textItem())->getLinks();
73 if (linkIndex < links.size())
74 return links.at(i: linkIndex).m_endIndex;
75 return -1;
76 }
77
78private:
79 QQuickText *textItem() const { return qobject_cast<QQuickText*>(object: parentTextItem); }
80 QQuickItem *parentTextItem;
81 const int linkIndex;
82
83 friend class QAccessibleQuickItem;
84};
85
86
87QAccessibleHyperlink::QAccessibleHyperlink(QQuickItem *parentTextItem, int linkIndex)
88 : parentTextItem(parentTextItem),
89 linkIndex(linkIndex)
90{
91}
92
93
94bool QAccessibleHyperlink::isValid() const
95{
96 return textItem();
97}
98
99
100QObject *QAccessibleHyperlink::object() const
101{
102 return nullptr;
103}
104
105
106QWindow *QAccessibleHyperlink::window() const
107{
108 return textItem()->window();
109}
110
111
112/* \reimp */
113QRect QAccessibleHyperlink::rect() const
114{
115 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(t: textItem())->getLinks();
116 if (linkIndex < links.size()) {
117 const QPoint tl = itemScreenRect(item: textItem()).topLeft();
118 return links.at(i: linkIndex).rect.translated(p: tl);
119 }
120 return QRect();
121}
122
123/* \reimp */
124QAccessibleInterface *QAccessibleHyperlink::childAt(int, int) const
125{
126 return nullptr;
127}
128
129/* \reimp */
130QAccessibleInterface *QAccessibleHyperlink::parent() const
131{
132 return QAccessible::queryAccessibleInterface(textItem());
133}
134
135/* \reimp */
136QAccessibleInterface *QAccessibleHyperlink::child(int) const
137{
138 return nullptr;
139}
140
141/* \reimp */
142int QAccessibleHyperlink::childCount() const
143{
144 return 0;
145}
146
147/* \reimp */
148int QAccessibleHyperlink::indexOfChild(const QAccessibleInterface *) const
149{
150 return -1;
151}
152
153/* \reimp */
154QAccessible::State QAccessibleHyperlink::state() const
155{
156 QAccessible::State s;
157 s.selectable = true;
158 s.focusable = true;
159 s.selectableText = true;
160 s.selected = false;
161 return s;
162}
163
164/* \reimp */
165QAccessible::Role QAccessibleHyperlink::role() const
166{
167 return QAccessible::Link;
168}
169
170/* \reimp */
171QString QAccessibleHyperlink::text(QAccessible::Text t) const
172{
173 // AT servers have different behaviors:
174 // Wordpad on windows have this behavior:
175 // * Name returns the anchor target (URL)
176 // * Value returns the anchor target (URL)
177
178 // Other AT servers (e.g. MS Edge on Windows) does what seems to be more sensible:
179 // * Name returns the anchor name
180 // * Value returns the anchor target (URL)
181 if (t == QAccessible::Name)
182 return anchor();
183 if (t == QAccessible::Value)
184 return anchorTarget();
185 return QString();
186}
187
188/* \reimp */
189void QAccessibleHyperlink::setText(QAccessible::Text, const QString &)
190{
191
192}
193
194/* \reimp */
195void *QAccessibleHyperlink::interface_cast(QAccessible::InterfaceType t)
196{
197 if (t == QAccessible::HyperlinkInterface)
198 return static_cast<QAccessibleHyperlinkInterface*>(this);
199 return nullptr;
200}
201
202
203/*!
204 * \internal
205 * \brief QAccessibleQuickItem::QAccessibleQuickItem
206 * \param item
207 */
208QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
209 : QAccessibleObject(item), m_doc(textDocument())
210{
211}
212
213QWindow *QAccessibleQuickItem::window() const
214{
215 QQuickWindow *window = item()->window();
216
217 // For QQuickWidget the above window will be the offscreen QQuickWindow,
218 // which is not a part of the accessibility tree. Detect this case and
219 // return the window for the QQuickWidget instead.
220 if (window && !window->handle()) {
221 if (QQuickRenderControl *renderControl = QQuickWindowPrivate::get(c: window)->renderControl) {
222 if (QWindow *renderWindow = renderControl->renderWindow(offset: nullptr))
223 return renderWindow;
224 }
225 }
226
227 return window;
228}
229
230int QAccessibleQuickItem::childCount() const
231{
232 // see comment in QAccessibleQuickItem::child() as to why we do this
233 int cc = 0;
234 if (QQuickText *textItem = qobject_cast<QQuickText*>(object: item())) {
235 cc = QQuickTextPrivate::get(t: textItem)->getLinks().size();
236 }
237 cc += childItems().size();
238 return cc;
239}
240
241QRect QAccessibleQuickItem::rect() const
242{
243 const QRect r = itemScreenRect(item: item());
244 return r;
245}
246
247QRect QAccessibleQuickItem::viewRect() const
248{
249 // ### no window in some cases.
250 if (!item()->window()) {
251 return QRect();
252 }
253
254 QQuickWindow *window = item()->window();
255 QPoint screenPos = window->mapToGlobal(pos: QPoint(0,0));
256 return QRect(screenPos, window->size());
257}
258
259
260bool QAccessibleQuickItem::clipsChildren() const
261{
262 return static_cast<QQuickItem *>(item())->clip();
263}
264
265QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const
266{
267 if (item()->clip()) {
268 if (!rect().contains(ax: x, ay: y))
269 return nullptr;
270 }
271
272 // special case for text interfaces
273 if (QQuickText *textItem = qobject_cast<QQuickText*>(object: item())) {
274 const auto hyperLinkChildCount = QQuickTextPrivate::get(t: textItem)->getLinks().size();
275 for (auto i = 0; i < hyperLinkChildCount; i++) {
276 QAccessibleInterface *iface = child(index: i);
277 if (iface->rect().contains(ax: x,ay: y)) {
278 return iface;
279 }
280 }
281 }
282
283 // general item hit test
284 const QList<QQuickItem*> kids = accessibleUnignoredChildren(item: item(), paintOrder: true);
285 for (int i = kids.size() - 1; i >= 0; --i) {
286 QAccessibleInterface *childIface = QAccessible::queryAccessibleInterface(kids.at(i));
287 if (QAccessibleInterface *childChild = childIface->childAt(x, y))
288 return childChild;
289 if (childIface && !childIface->state().invisible) {
290 if (childIface->rect().contains(ax: x, ay: y))
291 return childIface;
292 }
293 }
294
295 return nullptr;
296}
297
298QAccessibleInterface *QAccessibleQuickItem::parent() const
299{
300 QQuickItem *parent = item()->parentItem();
301 QQuickWindow *itemWindow = item()->window();
302 QQuickItem *ci = itemWindow ? itemWindow->contentItem() : nullptr;
303 while (parent && !QQuickItemPrivate::get(item: parent)->isAccessible && parent != ci)
304 parent = parent->parentItem();
305
306 if (parent) {
307 if (parent == ci) {
308 // Jump out to the window if the parent is the root item
309 return QAccessible::queryAccessibleInterface(window());
310 } else {
311 while (parent && !parent->d_func()->isAccessible)
312 parent = parent->parentItem();
313 return QAccessible::queryAccessibleInterface(parent);
314 }
315 }
316 return nullptr;
317}
318
319QAccessibleInterface *QAccessibleQuickItem::child(int index) const
320{
321 /* Text with hyperlinks will have dedicated children interfaces representing each hyperlink.
322
323 For the pathological case when a Text node has hyperlinks in its text *and* accessible
324 quick items as children, we put the hyperlink a11y interfaces as the first children, then
325 the other interfaces follows the hyperlink children (as siblings).
326
327 For example, suppose you have two links in the text and an image as a child of the text,
328 it will have the following a11y hierarchy:
329
330 [a11y:TextInterface]
331 |
332 +- [a11y:HyperlinkInterface]
333 +- [a11y:HyperlinkInterface]
334 +- [a11y:ImageInterface]
335
336 Having this order (as opposed to having hyperlink interfaces last) will at least
337 ensure that the child id of hyperlink children is not altered when child is added/removed
338 to the text item and marked accessible.
339 In addition, hyperlink interfaces as children should be the common case, so it is preferred
340 to explore those first when iterating.
341 */
342 if (index < 0)
343 return nullptr;
344
345
346 if (QQuickText *textItem = qobject_cast<QQuickText*>(object: item())) {
347 const int hyperLinkChildCount = QQuickTextPrivate::get(t: textItem)->getLinks().size();
348 if (index < hyperLinkChildCount) {
349 auto it = m_childToId.constFind(key: index);
350 if (it != m_childToId.constEnd())
351 return QAccessible::accessibleInterface(uniqueId: it.value());
352
353 QAccessibleHyperlink *iface = new QAccessibleHyperlink(item(), index);
354 QAccessible::Id id = QAccessible::registerAccessibleInterface(iface);
355 m_childToId.insert(key: index, value: id);
356 return iface;
357 }
358 index -= hyperLinkChildCount;
359 }
360
361 QList<QQuickItem *> children = childItems();
362 if (index < children.size()) {
363 QQuickItem *child = children.at(i: index);
364 return QAccessible::queryAccessibleInterface(child);
365 }
366 return nullptr;
367}
368
369int QAccessibleQuickItem::indexOfChild(const QAccessibleInterface *iface) const
370{
371 int hyperLinkChildCount = 0;
372 if (QQuickText *textItem = qobject_cast<QQuickText*>(object: item())) {
373 hyperLinkChildCount = QQuickTextPrivate::get(t: textItem)->getLinks().size();
374 if (QAccessibleHyperlinkInterface *hyperLinkIface = const_cast<QAccessibleInterface *>(iface)->hyperlinkInterface()) {
375 // ### assumes that there is only one subclass implementing QAccessibleHyperlinkInterface
376 // Alternatively, we could simply iterate with child() and do a linear search for it
377 QAccessibleHyperlink *hyperLink = static_cast<QAccessibleHyperlink*>(hyperLinkIface);
378 if (hyperLink->textItem() == static_cast<QQuickText*>(item())) {
379 return hyperLink->linkIndex;
380 }
381 }
382 }
383 QList<QQuickItem*> kids = childItems();
384 int idx = kids.indexOf(t: static_cast<QQuickItem*>(iface->object()));
385 if (idx >= 0)
386 idx += hyperLinkChildCount;
387 return idx;
388}
389
390static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items, bool paintOrder)
391{
392 const QList<QQuickItem*> childItems = paintOrder ? QQuickItemPrivate::get(item)->paintOrderChildItems()
393 : item->childItems();
394 for (QQuickItem *child : childItems) {
395 if (QQuickItemPrivate::get(item: child)->isAccessible) {
396 items->append(t: child);
397 } else {
398 unignoredChildren(item: child, items, paintOrder);
399 }
400 }
401}
402
403QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item, bool paintOrder)
404{
405 QList<QQuickItem *> items;
406 unignoredChildren(item, items: &items, paintOrder);
407 return items;
408}
409
410QList<QQuickItem *> QAccessibleQuickItem::childItems() const
411{
412 return accessibleUnignoredChildren(item: item());
413}
414
415static bool isTextRole(QAccessible::Role role)
416{
417 return role == QAccessible::EditableText || role == QAccessible::StaticText;
418}
419
420QAccessible::State QAccessibleQuickItem::state() const
421{
422 QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item());
423 if (!attached)
424 return QAccessible::State();
425
426 QAccessible::State state = attached->state();
427
428 QRect viewRect_ = viewRect();
429 QRect itemRect = rect();
430
431 if (viewRect_.isNull() || itemRect.isNull() || !window() || !window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(d: item()->opacity()))
432 state.invisible = true;
433 if (!viewRect_.intersects(r: itemRect))
434 state.offscreen = true;
435 if ((role() == QAccessible::CheckBox || role() == QAccessible::RadioButton) && object()->property(name: "checked").toBool())
436 state.checked = true;
437 if (item()->activeFocusOnTab() || isTextRole(role: role()))
438 state.focusable = true;
439 if (item()->hasActiveFocus())
440 state.focused = true;
441 if (role() == QAccessible::EditableText)
442 if (auto ti = qobject_cast<QQuickTextInput *>(object: item()))
443 state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
444 if (!item()->isEnabled()) {
445 state.focusable = false;
446 state.disabled = true;
447 }
448 return state;
449}
450
451QAccessible::Role QAccessibleQuickItem::role() const
452{
453 // Workaround for setAccessibleRole() not working for
454 // Text items. Text items are special since they are defined
455 // entirely from C++ (setting the role from QML works.)
456
457 QAccessible::Role role = QAccessible::NoRole;
458 if (item())
459 role = QQuickItemPrivate::get(item: item())->effectiveAccessibleRole();
460 if (role == QAccessible::NoRole) {
461 if (qobject_cast<QQuickText*>(object: const_cast<QQuickItem *>(item())))
462 role = QAccessible::StaticText;
463 else if (qobject_cast<QQuickTextInput*>(object: const_cast<QQuickItem *>(item())))
464 role = QAccessible::EditableText;
465 else
466 role = QAccessible::Client;
467 }
468
469 return role;
470}
471
472bool QAccessibleQuickItem::isAccessible() const
473{
474 return item()->d_func()->isAccessible;
475}
476
477QStringList QAccessibleQuickItem::actionNames() const
478{
479 QStringList actions;
480 switch (role()) {
481 case QAccessible::Link:
482 case QAccessible::PushButton:
483 actions << QAccessibleActionInterface::pressAction();
484 break;
485 case QAccessible::RadioButton:
486 case QAccessible::CheckBox:
487 actions << QAccessibleActionInterface::toggleAction()
488 << QAccessibleActionInterface::pressAction();
489 break;
490 case QAccessible::Slider:
491 case QAccessible::SpinBox:
492 case QAccessible::ScrollBar:
493 actions << QAccessibleActionInterface::increaseAction()
494 << QAccessibleActionInterface::decreaseAction();
495 break;
496 default:
497 break;
498 }
499 if (state().focusable)
500 actions.append(t: QAccessibleActionInterface::setFocusAction());
501
502 // ### The following can lead to duplicate action names.
503 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item()))
504 attached->availableActions(actions: &actions);
505 return actions;
506}
507
508void QAccessibleQuickItem::doAction(const QString &actionName)
509{
510 bool accepted = false;
511 if (actionName == QAccessibleActionInterface::setFocusAction()) {
512 item()->forceActiveFocus();
513 accepted = true;
514 }
515 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item()))
516 accepted = attached->doAction(actionName);
517
518 if (accepted)
519 return;
520 // Look for and call the accessible[actionName]Action() function on the item.
521 // This allows for overriding the default action handling.
522 const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action";
523 if (object()->metaObject()->indexOfMethod(method: QByteArray(functionName + "()")) != -1) {
524 QMetaObject::invokeMethod(obj: object(), member: functionName);
525 return;
526 }
527
528 // Role-specific default action handling follows. Items are expected to provide
529 // properties according to role conventions. These will then be read and/or updated
530 // by the accessibility system.
531 // Checkable roles : checked
532 // Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
533 switch (role()) {
534 case QAccessible::RadioButton:
535 case QAccessible::CheckBox: {
536 QVariant checked = object()->property(name: "checked");
537 if (checked.isValid()) {
538 if (actionName == QAccessibleActionInterface::toggleAction() ||
539 actionName == QAccessibleActionInterface::pressAction()) {
540
541 object()->setProperty(name: "checked", value: QVariant(!checked.toBool()));
542 }
543 }
544 break;
545 }
546 case QAccessible::Slider:
547 case QAccessible::SpinBox:
548 case QAccessible::Dial:
549 case QAccessible::ScrollBar: {
550 if (actionName != QAccessibleActionInterface::increaseAction() &&
551 actionName != QAccessibleActionInterface::decreaseAction())
552 break;
553
554 // Update the value using QAccessibleValueInterface, respecting
555 // the minimum and maximum value (if set). Also check for and
556 // use the "stepSize" property on the item
557 if (QAccessibleValueInterface *valueIface = valueInterface()) {
558 QVariant valueV = valueIface->currentValue();
559 qreal newValue = valueV.toReal();
560
561 QVariant stepSizeV = object()->property(name: "stepSize");
562 qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
563 if (actionName == QAccessibleActionInterface::increaseAction()) {
564 newValue += stepSize;
565 } else {
566 newValue -= stepSize;
567 }
568
569 QVariant minimumValueV = valueIface->minimumValue();
570 if (minimumValueV.isValid()) {
571 newValue = qMax(a: newValue, b: minimumValueV.toReal());
572 }
573 QVariant maximumValueV = valueIface->maximumValue();
574 if (maximumValueV.isValid()) {
575 newValue = qMin(a: newValue, b: maximumValueV.toReal());
576 }
577
578 valueIface->setCurrentValue(QVariant(newValue));
579 }
580 break;
581 }
582 default:
583 break;
584 }
585}
586
587QStringList QAccessibleQuickItem::keyBindingsForAction(const QString &actionName) const
588{
589 Q_UNUSED(actionName);
590 return QStringList();
591}
592
593QString QAccessibleQuickItem::text(QAccessible::Text textType) const
594{
595 // handles generic behavior not specific to an item
596 switch (textType) {
597 case QAccessible::Name: {
598 QVariant accessibleName = QQuickAccessibleAttached::property(object: object(), propertyName: "name");
599 if (!accessibleName.isNull())
600 return accessibleName.toString();
601 break;}
602 case QAccessible::Description: {
603 QVariant accessibleDecription = QQuickAccessibleAttached::property(object: object(), propertyName: "description");
604 if (!accessibleDecription.isNull())
605 return accessibleDecription.toString();
606 break;}
607 case QAccessible::Identifier: {
608 QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object: object(), propertyName: "id");
609 if (!accessibleIdentifier.isNull())
610 return accessibleIdentifier.toString();
611 auto quickItem = item();
612 if (quickItem->isComponentComplete()) {
613 QQmlContext *context = qmlContext(quickItem);
614 if (context) {
615 const auto objectId = context->nameForObject(quickItem);
616 if (!objectId.isEmpty())
617 return objectId;
618 }
619 }
620 break;}
621#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
622 case QAccessible::DebugDescription: {
623 QString debugString;
624 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
625 debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
626 return debugString;
627 break; }
628#endif
629 case QAccessible::Value:
630 case QAccessible::Help:
631 case QAccessible::Accelerator:
632 default:
633 break;
634 }
635
636 // the following block handles item-specific behavior
637 if (role() == QAccessible::EditableText) {
638 if (textType == QAccessible::Value) {
639 if (auto textInput = qobject_cast<QQuickTextInput *>(object: item()))
640 return textInput->displayText();
641 if (QTextDocument *doc = textDocument()) {
642 return doc->toPlainText();
643 }
644 QVariant text = object()->property(name: "text");
645 return text.toString();
646 }
647 }
648
649 return QString();
650}
651
652void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
653{
654 if (role() != QAccessible::EditableText)
655 return;
656 if (textType != QAccessible::Value)
657 return;
658
659 if (QTextDocument *doc = textDocument()) {
660 doc->setPlainText(text);
661 return;
662 }
663 auto textPropertyName = "text";
664 if (object()->metaObject()->indexOfProperty(name: textPropertyName) >= 0)
665 object()->setProperty(name: textPropertyName, value: text);
666}
667
668void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
669{
670 QAccessible::Role r = role();
671 if (t == QAccessible::ActionInterface)
672 return static_cast<QAccessibleActionInterface*>(this);
673 if (t == QAccessible::ValueInterface &&
674 (r == QAccessible::Slider ||
675 r == QAccessible::SpinBox ||
676 r == QAccessible::Dial ||
677 r == QAccessible::ScrollBar))
678 return static_cast<QAccessibleValueInterface*>(this);
679
680 if (t == QAccessible::TextInterface) {
681 if (r == QAccessible::EditableText ||
682 r == QAccessible::StaticText)
683 return static_cast<QAccessibleTextInterface*>(this);
684 }
685
686 return QAccessibleObject::interface_cast(t);
687}
688
689QVariant QAccessibleQuickItem::currentValue() const
690{
691 return item()->property(name: "value");
692}
693
694void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
695{
696 item()->setProperty(name: "value", value);
697}
698
699QVariant QAccessibleQuickItem::maximumValue() const
700{
701 return item()->property(name: "maximumValue");
702}
703
704QVariant QAccessibleQuickItem::minimumValue() const
705{
706 return item()->property(name: "minimumValue");
707}
708
709QVariant QAccessibleQuickItem::minimumStepSize() const
710{
711 return item()->property(name: "stepSize");
712}
713
714/*!
715 \internal
716 Shared between QAccessibleQuickItem and QAccessibleQuickView
717*/
718QRect itemScreenRect(QQuickItem *item)
719{
720 // ### no window in some cases.
721 // ### Should we really check for 0 opacity?
722 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(d: item->opacity())) {
723 return QRect();
724 }
725
726 QSize itemSize((int)item->width(), (int)item->height());
727 // ### If the bounding rect fails, we first try the implicit size, then we go for the
728 // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
729 if (itemSize.isEmpty()) {
730 itemSize = QSize((int)item->implicitWidth(), (int)item->implicitHeight());
731 if (itemSize.isEmpty() && item->parentItem())
732 // ### Seems that the above fallback is not enough, fallback to use the parent size...
733 itemSize = QSize((int)item->parentItem()->width(), (int)item->parentItem()->height());
734 }
735
736 QPointF scenePoint = item->mapToScene(point: QPointF(0, 0));
737 QPoint screenPos = item->window()->mapToGlobal(pos: scenePoint.toPoint());
738 return QRect(screenPos, itemSize);
739}
740
741QTextDocument *QAccessibleQuickItem::textDocument() const
742{
743 QVariant docVariant = item()->property(name: "textDocument");
744 if (docVariant.canConvert<QQuickTextDocument*>()) {
745 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
746 return qqdoc->textDocument();
747 }
748 return nullptr;
749}
750
751int QAccessibleQuickItem::characterCount() const
752{
753 if (m_doc) {
754 QTextCursor cursor = QTextCursor(m_doc);
755 cursor.movePosition(op: QTextCursor::End);
756 return cursor.position();
757 }
758 return text(textType: QAccessible::Value).size();
759}
760
761int QAccessibleQuickItem::cursorPosition() const
762{
763 QVariant pos = item()->property(name: "cursorPosition");
764 return pos.toInt();
765}
766
767void QAccessibleQuickItem::setCursorPosition(int position)
768{
769 item()->setProperty(name: "cursorPosition", value: position);
770}
771
772QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
773{
774 if (m_doc) {
775 QTextCursor cursor = QTextCursor(m_doc);
776 cursor.setPosition(pos: startOffset);
777 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
778 return cursor.selectedText();
779 }
780 return text(textType: QAccessible::Value).mid(position: startOffset, n: endOffset - startOffset);
781}
782
783QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
784 int *startOffset, int *endOffset) const
785{
786 Q_ASSERT(startOffset);
787 Q_ASSERT(endOffset);
788
789 if (m_doc) {
790 QTextCursor cursor = QTextCursor(m_doc);
791 cursor.setPosition(pos: offset);
792 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
793 cursor.setPosition(pos: boundaries.first - 1);
794 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
795
796 *startOffset = boundaries.first;
797 *endOffset = boundaries.second;
798
799 return text(startOffset: boundaries.first, endOffset: boundaries.second);
800 } else {
801 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
802 }
803}
804
805QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
806 int *startOffset, int *endOffset) const
807{
808 Q_ASSERT(startOffset);
809 Q_ASSERT(endOffset);
810
811 if (m_doc) {
812 QTextCursor cursor = QTextCursor(m_doc);
813 cursor.setPosition(pos: offset);
814 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
815 cursor.setPosition(pos: boundaries.second);
816 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
817
818 *startOffset = boundaries.first;
819 *endOffset = boundaries.second;
820
821 return text(startOffset: boundaries.first, endOffset: boundaries.second);
822 } else {
823 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
824 }
825}
826
827QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
828 int *startOffset, int *endOffset) const
829{
830 Q_ASSERT(startOffset);
831 Q_ASSERT(endOffset);
832
833 if (m_doc) {
834 QTextCursor cursor = QTextCursor(m_doc);
835 cursor.setPosition(pos: offset);
836 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
837
838 *startOffset = boundaries.first;
839 *endOffset = boundaries.second;
840 return text(startOffset: boundaries.first, endOffset: boundaries.second);
841 } else {
842 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
843 }
844}
845
846void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
847{
848 if (selectionIndex == 0) {
849 *startOffset = item()->property(name: "selectionStart").toInt();
850 *endOffset = item()->property(name: "selectionEnd").toInt();
851 } else {
852 *startOffset = 0;
853 *endOffset = 0;
854 }
855}
856
857int QAccessibleQuickItem::selectionCount() const
858{
859 if (item()->property(name: "selectionStart").toInt() != item()->property(name: "selectionEnd").toInt())
860 return 1;
861 return 0;
862}
863
864void QAccessibleQuickItem::addSelection(int /* startOffset */, int /* endOffset */)
865{
866
867}
868void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
869{
870
871}
872void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
873{
874
875}
876
877
878#endif // accessibility
879
880QT_END_NAMESPACE
881

source code of qtdeclarative/src/quick/accessible/qaccessiblequickitem.cpp