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#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
608 case QAccessible::DebugDescription: {
609 QString debugString;
610 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
611 debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
612 return debugString;
613 break; }
614#endif
615 case QAccessible::Value:
616 case QAccessible::Help:
617 case QAccessible::Accelerator:
618 default:
619 break;
620 }
621
622 // the following block handles item-specific behavior
623 if (role() == QAccessible::EditableText) {
624 if (textType == QAccessible::Value) {
625 if (QTextDocument *doc = textDocument()) {
626 return doc->toPlainText();
627 }
628 QVariant text = object()->property(name: "text");
629 return text.toString();
630 }
631 }
632
633 return QString();
634}
635
636void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
637{
638 if (role() != QAccessible::EditableText)
639 return;
640 if (textType != QAccessible::Value)
641 return;
642
643 if (QTextDocument *doc = textDocument()) {
644 doc->setPlainText(text);
645 return;
646 }
647 auto textPropertyName = "text";
648 if (object()->metaObject()->indexOfProperty(name: textPropertyName) >= 0)
649 object()->setProperty(name: textPropertyName, value: text);
650}
651
652void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
653{
654 QAccessible::Role r = role();
655 if (t == QAccessible::ActionInterface)
656 return static_cast<QAccessibleActionInterface*>(this);
657 if (t == QAccessible::ValueInterface &&
658 (r == QAccessible::Slider ||
659 r == QAccessible::SpinBox ||
660 r == QAccessible::Dial ||
661 r == QAccessible::ScrollBar))
662 return static_cast<QAccessibleValueInterface*>(this);
663
664 if (t == QAccessible::TextInterface) {
665 if (r == QAccessible::EditableText ||
666 r == QAccessible::StaticText)
667 return static_cast<QAccessibleTextInterface*>(this);
668 }
669
670 return QAccessibleObject::interface_cast(t);
671}
672
673QVariant QAccessibleQuickItem::currentValue() const
674{
675 return item()->property(name: "value");
676}
677
678void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
679{
680 item()->setProperty(name: "value", value);
681}
682
683QVariant QAccessibleQuickItem::maximumValue() const
684{
685 return item()->property(name: "maximumValue");
686}
687
688QVariant QAccessibleQuickItem::minimumValue() const
689{
690 return item()->property(name: "minimumValue");
691}
692
693QVariant QAccessibleQuickItem::minimumStepSize() const
694{
695 return item()->property(name: "stepSize");
696}
697
698/*!
699 \internal
700 Shared between QAccessibleQuickItem and QAccessibleQuickView
701*/
702QRect itemScreenRect(QQuickItem *item)
703{
704 // ### no window in some cases.
705 // ### Should we really check for 0 opacity?
706 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(d: item->opacity())) {
707 return QRect();
708 }
709
710 QSize itemSize((int)item->width(), (int)item->height());
711 // ### If the bounding rect fails, we first try the implicit size, then we go for the
712 // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
713 if (itemSize.isEmpty()) {
714 itemSize = QSize((int)item->implicitWidth(), (int)item->implicitHeight());
715 if (itemSize.isEmpty() && item->parentItem())
716 // ### Seems that the above fallback is not enough, fallback to use the parent size...
717 itemSize = QSize((int)item->parentItem()->width(), (int)item->parentItem()->height());
718 }
719
720 QPointF scenePoint = item->mapToScene(point: QPointF(0, 0));
721 QPoint screenPos = item->window()->mapToGlobal(pos: scenePoint.toPoint());
722 return QRect(screenPos, itemSize);
723}
724
725QTextDocument *QAccessibleQuickItem::textDocument() const
726{
727 QVariant docVariant = item()->property(name: "textDocument");
728 if (docVariant.canConvert<QQuickTextDocument*>()) {
729 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
730 return qqdoc->textDocument();
731 }
732 return nullptr;
733}
734
735int QAccessibleQuickItem::characterCount() const
736{
737 if (m_doc) {
738 QTextCursor cursor = QTextCursor(m_doc);
739 cursor.movePosition(op: QTextCursor::End);
740 return cursor.position();
741 }
742 return text(textType: QAccessible::Value).size();
743}
744
745int QAccessibleQuickItem::cursorPosition() const
746{
747 QVariant pos = item()->property(name: "cursorPosition");
748 return pos.toInt();
749}
750
751void QAccessibleQuickItem::setCursorPosition(int position)
752{
753 item()->setProperty(name: "cursorPosition", value: position);
754}
755
756QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
757{
758 if (m_doc) {
759 QTextCursor cursor = QTextCursor(m_doc);
760 cursor.setPosition(pos: startOffset);
761 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
762 return cursor.selectedText();
763 }
764 return text(textType: QAccessible::Value).mid(position: startOffset, n: endOffset - startOffset);
765}
766
767QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
768 int *startOffset, int *endOffset) const
769{
770 Q_ASSERT(startOffset);
771 Q_ASSERT(endOffset);
772
773 if (m_doc) {
774 QTextCursor cursor = QTextCursor(m_doc);
775 cursor.setPosition(pos: offset);
776 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
777 cursor.setPosition(pos: boundaries.first - 1);
778 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
779
780 *startOffset = boundaries.first;
781 *endOffset = boundaries.second;
782
783 return text(startOffset: boundaries.first, endOffset: boundaries.second);
784 } else {
785 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
786 }
787}
788
789QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
790 int *startOffset, int *endOffset) const
791{
792 Q_ASSERT(startOffset);
793 Q_ASSERT(endOffset);
794
795 if (m_doc) {
796 QTextCursor cursor = QTextCursor(m_doc);
797 cursor.setPosition(pos: offset);
798 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
799 cursor.setPosition(pos: boundaries.second);
800 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
801
802 *startOffset = boundaries.first;
803 *endOffset = boundaries.second;
804
805 return text(startOffset: boundaries.first, endOffset: boundaries.second);
806 } else {
807 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
808 }
809}
810
811QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
812 int *startOffset, int *endOffset) const
813{
814 Q_ASSERT(startOffset);
815 Q_ASSERT(endOffset);
816
817 if (m_doc) {
818 QTextCursor cursor = QTextCursor(m_doc);
819 cursor.setPosition(pos: offset);
820 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
821
822 *startOffset = boundaries.first;
823 *endOffset = boundaries.second;
824 return text(startOffset: boundaries.first, endOffset: boundaries.second);
825 } else {
826 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
827 }
828}
829
830void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
831{
832 if (selectionIndex == 0) {
833 *startOffset = item()->property(name: "selectionStart").toInt();
834 *endOffset = item()->property(name: "selectionEnd").toInt();
835 } else {
836 *startOffset = 0;
837 *endOffset = 0;
838 }
839}
840
841int QAccessibleQuickItem::selectionCount() const
842{
843 if (item()->property(name: "selectionStart").toInt() != item()->property(name: "selectionEnd").toInt())
844 return 1;
845 return 0;
846}
847
848void QAccessibleQuickItem::addSelection(int /* startOffset */, int /* endOffset */)
849{
850
851}
852void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
853{
854
855}
856void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
857{
858
859}
860
861
862#endif // accessibility
863
864QT_END_NAMESPACE
865

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