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 if (itemWindow && !itemWindow->handle()) {
309 // If the item's window is a QQuickWidgetOffscreenWindow, then use
310 // the QQuickWidget as the accessible parent of the item. Since the
311 // QQuickWidget reports the quick items as its accessible children,
312 // why not report QQuickWidget as their accessible parent?
313 // The QQuickWidget instance has been set as the "_q_parentWidget"
314 // property of the QQuickWidgetOffscreenWindow when creating the
315 // instance of its offscreenWindow
316 const auto parentWidgetProp = itemWindow->property(name: "_q_parentWidget");
317 if (parentWidgetProp.isValid()) {
318 if (QObject *parentWidget = parentWidgetProp.value<QObject *>())
319 return QAccessible::queryAccessibleInterface(parentWidget);
320 }
321 }
322 // Jump out to the window if the parent is the root item
323 return QAccessible::queryAccessibleInterface(window());
324 } else {
325 while (parent && !parent->d_func()->isAccessible)
326 parent = parent->parentItem();
327 return QAccessible::queryAccessibleInterface(parent);
328 }
329 }
330 return nullptr;
331}
332
333QAccessibleInterface *QAccessibleQuickItem::child(int index) const
334{
335 /* Text with hyperlinks will have dedicated children interfaces representing each hyperlink.
336
337 For the pathological case when a Text node has hyperlinks in its text *and* accessible
338 quick items as children, we put the hyperlink a11y interfaces as the first children, then
339 the other interfaces follows the hyperlink children (as siblings).
340
341 For example, suppose you have two links in the text and an image as a child of the text,
342 it will have the following a11y hierarchy:
343
344 [a11y:TextInterface]
345 |
346 +- [a11y:HyperlinkInterface]
347 +- [a11y:HyperlinkInterface]
348 +- [a11y:ImageInterface]
349
350 Having this order (as opposed to having hyperlink interfaces last) will at least
351 ensure that the child id of hyperlink children is not altered when child is added/removed
352 to the text item and marked accessible.
353 In addition, hyperlink interfaces as children should be the common case, so it is preferred
354 to explore those first when iterating.
355 */
356 if (index < 0)
357 return nullptr;
358
359
360 if (QQuickText *textItem = qobject_cast<QQuickText*>(object: item())) {
361 const int hyperLinkChildCount = QQuickTextPrivate::get(t: textItem)->getLinks().size();
362 if (index < hyperLinkChildCount) {
363 auto it = m_childToId.constFind(key: index);
364 if (it != m_childToId.constEnd())
365 return QAccessible::accessibleInterface(uniqueId: it.value());
366
367 QAccessibleHyperlink *iface = new QAccessibleHyperlink(item(), index);
368 QAccessible::Id id = QAccessible::registerAccessibleInterface(iface);
369 m_childToId.insert(key: index, value: id);
370 return iface;
371 }
372 index -= hyperLinkChildCount;
373 }
374
375 QList<QQuickItem *> children = childItems();
376 if (index < children.size()) {
377 QQuickItem *child = children.at(i: index);
378 return QAccessible::queryAccessibleInterface(child);
379 }
380 return nullptr;
381}
382
383int QAccessibleQuickItem::indexOfChild(const QAccessibleInterface *iface) const
384{
385 int hyperLinkChildCount = 0;
386 if (QQuickText *textItem = qobject_cast<QQuickText*>(object: item())) {
387 hyperLinkChildCount = QQuickTextPrivate::get(t: textItem)->getLinks().size();
388 if (QAccessibleHyperlinkInterface *hyperLinkIface = const_cast<QAccessibleInterface *>(iface)->hyperlinkInterface()) {
389 // ### assumes that there is only one subclass implementing QAccessibleHyperlinkInterface
390 // Alternatively, we could simply iterate with child() and do a linear search for it
391 QAccessibleHyperlink *hyperLink = static_cast<QAccessibleHyperlink*>(hyperLinkIface);
392 if (hyperLink->textItem() == static_cast<QQuickText*>(item())) {
393 return hyperLink->linkIndex;
394 }
395 }
396 }
397 QList<QQuickItem*> kids = childItems();
398 int idx = kids.indexOf(t: static_cast<QQuickItem*>(iface->object()));
399 if (idx >= 0)
400 idx += hyperLinkChildCount;
401 return idx;
402}
403
404static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items, bool paintOrder)
405{
406 const QList<QQuickItem*> childItems = paintOrder ? QQuickItemPrivate::get(item)->paintOrderChildItems()
407 : item->childItems();
408 for (QQuickItem *child : childItems) {
409 if (QQuickItemPrivate::get(item: child)->isAccessible) {
410 items->append(t: child);
411 } else {
412 unignoredChildren(item: child, items, paintOrder);
413 }
414 }
415}
416
417QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item, bool paintOrder)
418{
419 QList<QQuickItem *> items;
420 unignoredChildren(item, items: &items, paintOrder);
421 return items;
422}
423
424QList<QQuickItem *> QAccessibleQuickItem::childItems() const
425{
426 return accessibleUnignoredChildren(item: item());
427}
428
429static bool isTextRole(QAccessible::Role role)
430{
431 return role == QAccessible::EditableText || role == QAccessible::StaticText;
432}
433
434QAccessible::State QAccessibleQuickItem::state() const
435{
436 QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item());
437 if (!attached)
438 return QAccessible::State();
439
440 QAccessible::State state = attached->state();
441
442 QRect viewRect_ = viewRect();
443 QRect itemRect = rect();
444
445 if (viewRect_.isNull() || itemRect.isNull() || !window() || !window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(d: item()->opacity()))
446 state.invisible = true;
447 if (!viewRect_.intersects(r: itemRect))
448 state.offscreen = true;
449 if ((role() == QAccessible::CheckBox || role() == QAccessible::RadioButton) && object()->property(name: "checked").toBool())
450 state.checked = true;
451 if (item()->activeFocusOnTab() || isTextRole(role: role()))
452 state.focusable = true;
453 if (item()->hasActiveFocus())
454 state.focused = true;
455 if (role() == QAccessible::EditableText)
456 if (auto ti = qobject_cast<QQuickTextInput *>(object: item()))
457 state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
458 if (!item()->isEnabled()) {
459 state.focusable = false;
460 state.disabled = true;
461 }
462 return state;
463}
464
465QList<std::pair<QAccessibleInterface *, QAccessible::Relation>>
466QAccessibleQuickItem::relations(QAccessible::Relation match) const
467{
468 QList<std::pair<QAccessibleInterface *, QAccessible::Relation>> rels =
469 QAccessibleObject::relations(match);
470 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item())) {
471 if (match & QAccessible::Labelled) {
472 if (auto *labelFor = attached->labelFor()) {
473 rels.append(t: {QAccessible::queryAccessibleInterface(labelFor),
474 QAccessible::Labelled});
475 }
476 }
477
478 if (match & QAccessible::Label) {
479 if (auto *labelledBy = attached->labelledBy()) {
480 rels.append(t: {QAccessible::queryAccessibleInterface(labelledBy),
481 QAccessible::Label});
482 }
483 }
484 }
485 return rels;
486}
487
488QAccessible::Role QAccessibleQuickItem::role() const
489{
490 // Workaround for setAccessibleRole() not working for
491 // Text items. Text items are special since they are defined
492 // entirely from C++ (setting the role from QML works.)
493
494 QAccessible::Role role = QAccessible::NoRole;
495 if (item())
496 role = QQuickItemPrivate::get(item: item())->effectiveAccessibleRole();
497 if (role == QAccessible::NoRole) {
498 if (qobject_cast<QQuickText*>(object: const_cast<QQuickItem *>(item())))
499 role = QAccessible::StaticText;
500 else if (qobject_cast<QQuickTextInput*>(object: const_cast<QQuickItem *>(item())))
501 role = QAccessible::EditableText;
502 else
503 role = QAccessible::Client;
504 }
505
506 return role;
507}
508
509bool QAccessibleQuickItem::isAccessible() const
510{
511 return item()->d_func()->isAccessible;
512}
513
514QStringList QAccessibleQuickItem::actionNames() const
515{
516 QStringList actions;
517 switch (role()) {
518 case QAccessible::Link:
519 case QAccessible::PushButton:
520 case QAccessible::MenuItem:
521 actions << QAccessibleActionInterface::pressAction();
522 break;
523 case QAccessible::RadioButton:
524 case QAccessible::CheckBox:
525 actions << QAccessibleActionInterface::toggleAction()
526 << QAccessibleActionInterface::pressAction();
527 break;
528 case QAccessible::Slider:
529 case QAccessible::SpinBox:
530 case QAccessible::ScrollBar:
531 actions << QAccessibleActionInterface::increaseAction()
532 << QAccessibleActionInterface::decreaseAction();
533 break;
534 default:
535 break;
536 }
537 if (state().focusable)
538 actions.append(t: QAccessibleActionInterface::setFocusAction());
539
540 // ### The following can lead to duplicate action names.
541 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item()))
542 attached->availableActions(actions: &actions);
543 return actions;
544}
545
546void QAccessibleQuickItem::doAction(const QString &actionName)
547{
548 bool accepted = false;
549 if (actionName == QAccessibleActionInterface::setFocusAction()) {
550 item()->forceActiveFocus();
551 accepted = true;
552 }
553 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item()))
554 accepted = attached->doAction(actionName);
555
556 if (accepted)
557 return;
558 // Look for and call the accessible[actionName]Action() function on the item.
559 // This allows for overriding the default action handling.
560 const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action";
561 if (object()->metaObject()->indexOfMethod(method: QByteArray(functionName + "()")) != -1) {
562 QMetaObject::invokeMethod(obj: object(), member: functionName);
563 return;
564 }
565
566 // Role-specific default action handling follows. Items are expected to provide
567 // properties according to role conventions. These will then be read and/or updated
568 // by the accessibility system.
569 // Checkable roles : checked
570 // Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
571 switch (role()) {
572 case QAccessible::RadioButton:
573 case QAccessible::CheckBox: {
574 QVariant checked = object()->property(name: "checked");
575 if (checked.isValid()) {
576 if (actionName == QAccessibleActionInterface::toggleAction() ||
577 actionName == QAccessibleActionInterface::pressAction()) {
578
579 object()->setProperty(name: "checked", value: QVariant(!checked.toBool()));
580 }
581 }
582 break;
583 }
584 case QAccessible::Slider:
585 case QAccessible::SpinBox:
586 case QAccessible::Dial:
587 case QAccessible::ScrollBar: {
588 if (actionName != QAccessibleActionInterface::increaseAction() &&
589 actionName != QAccessibleActionInterface::decreaseAction())
590 break;
591
592 // Update the value using QAccessibleValueInterface, respecting
593 // the minimum and maximum value (if set). Also check for and
594 // use the "stepSize" property on the item
595 if (QAccessibleValueInterface *valueIface = valueInterface()) {
596 QVariant valueV = valueIface->currentValue();
597 qreal newValue = valueV.toReal();
598
599 QVariant stepSizeV = object()->property(name: "stepSize");
600 qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
601 if (actionName == QAccessibleActionInterface::increaseAction()) {
602 newValue += stepSize;
603 } else {
604 newValue -= stepSize;
605 }
606
607 QVariant minimumValueV = valueIface->minimumValue();
608 if (minimumValueV.isValid()) {
609 newValue = qMax(a: newValue, b: minimumValueV.toReal());
610 }
611 QVariant maximumValueV = valueIface->maximumValue();
612 if (maximumValueV.isValid()) {
613 newValue = qMin(a: newValue, b: maximumValueV.toReal());
614 }
615
616 valueIface->setCurrentValue(QVariant(newValue));
617 }
618 break;
619 }
620 default:
621 break;
622 }
623}
624
625QStringList QAccessibleQuickItem::keyBindingsForAction(const QString &actionName) const
626{
627 Q_UNUSED(actionName);
628 return QStringList();
629}
630
631QString QAccessibleQuickItem::text(QAccessible::Text textType) const
632{
633 // handles generic behavior not specific to an item
634 switch (textType) {
635 case QAccessible::Name: {
636 QVariant accessibleName = QQuickAccessibleAttached::property(object: object(), propertyName: "name");
637 if (!accessibleName.isNull())
638 return accessibleName.toString();
639 break;}
640 case QAccessible::Description: {
641 QVariant accessibleDecription = QQuickAccessibleAttached::property(object: object(), propertyName: "description");
642 if (!accessibleDecription.isNull())
643 return accessibleDecription.toString();
644 break;}
645 case QAccessible::Identifier: {
646 QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object: object(), propertyName: "id");
647 if (!accessibleIdentifier.isNull())
648 return accessibleIdentifier.toString();
649 auto quickItem = item();
650 if (quickItem->isComponentComplete()) {
651 QQmlContext *context = qmlContext(quickItem);
652 if (context) {
653 const auto objectId = context->nameForObject(quickItem);
654 if (!objectId.isEmpty())
655 return objectId;
656 }
657 }
658 break;}
659#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
660 case QAccessible::DebugDescription: {
661 QString debugString;
662 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
663 debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
664 return debugString;
665 break; }
666#endif
667 case QAccessible::Value:
668 case QAccessible::Help:
669 case QAccessible::Accelerator:
670 default:
671 break;
672 }
673
674 // the following block handles item-specific behavior
675 if (role() == QAccessible::EditableText) {
676 if (textType == QAccessible::Value) {
677 if (auto textInput = qobject_cast<QQuickTextInput *>(object: item()))
678 return textInput->displayText();
679 if (QTextDocument *doc = textDocument()) {
680 return doc->toPlainText();
681 }
682 QVariant text = object()->property(name: "text");
683 return text.toString();
684 }
685 }
686
687 return QString();
688}
689
690void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
691{
692 if (role() != QAccessible::EditableText)
693 return;
694 if (textType != QAccessible::Value)
695 return;
696
697 if (QTextDocument *doc = textDocument()) {
698 doc->setPlainText(text);
699 return;
700 }
701 auto textPropertyName = "text";
702 if (object()->metaObject()->indexOfProperty(name: textPropertyName) >= 0)
703 object()->setProperty(name: textPropertyName, value: text);
704}
705
706void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
707{
708 const QAccessible::Role r = role();
709 switch (t) {
710 case QAccessible::ActionInterface:
711 return static_cast<QAccessibleActionInterface*>(this);
712 case QAccessible::ValueInterface:
713 if (r == QAccessible::Slider
714 || r == QAccessible::SpinBox
715 || r == QAccessible::Dial
716 || r == QAccessible::ScrollBar
717 || r == QAccessible::ProgressBar) {
718 return static_cast<QAccessibleValueInterface*>(this);
719 }
720 break;
721 case QAccessible::TextInterface:
722 if (r == QAccessible::EditableText
723 || r == QAccessible::StaticText
724 || r == QAccessible::Heading) {
725 return static_cast<QAccessibleTextInterface*>(this);
726 }
727 break;
728 default:
729 break;
730 }
731
732 return QAccessibleObject::interface_cast(t);
733}
734
735QVariant QAccessibleQuickItem::currentValue() const
736{
737 return item()->property(name: "value");
738}
739
740void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
741{
742 item()->setProperty(name: "value", value);
743}
744
745QVariant QAccessibleQuickItem::maximumValue() const
746{
747 const auto minimumValue = item()->property(name: "minimumValue");
748 const auto maximumValue = item()->property(name: "maximumValue");
749 const auto from = item()->property(name: "from");
750 const auto to = item()->property(name: "to");
751
752 if (minimumValue.isValid() && maximumValue.isValid())
753 return maximumValue;
754
755 if (from.isValid() && to.isValid())
756 return to;
757
758 return QVariant();
759}
760
761QVariant QAccessibleQuickItem::minimumValue() const
762{
763 const auto minimumValue = item()->property(name: "minimumValue");
764 const auto maximumValue = item()->property(name: "maximumValue");
765 const auto from = item()->property(name: "from");
766 const auto to = item()->property(name: "to");
767
768 if (minimumValue.isValid() && maximumValue.isValid())
769 return minimumValue;
770
771 if (from.isValid() && to.isValid())
772 return from;
773
774 return QVariant();
775}
776
777QVariant QAccessibleQuickItem::minimumStepSize() const
778{
779 return item()->property(name: "stepSize");
780}
781
782/*!
783 \internal
784 Shared between QAccessibleQuickItem and QAccessibleQuickView
785*/
786QRect itemScreenRect(QQuickItem *item)
787{
788 // ### no window in some cases.
789 // ### Should we really check for 0 opacity?
790 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(d: item->opacity())) {
791 return QRect();
792 }
793
794 QSizeF itemSize(item->width(), item->height());
795 // ### If the bounding rect fails, we first try the implicit size, then we go for the
796 // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
797 if (itemSize.isEmpty()) {
798 itemSize = QSize(item->implicitWidth(), item->implicitHeight());
799 if (itemSize.isEmpty() && item->parentItem())
800 // ### Seems that the above fallback is not enough, fallback to use the parent size...
801 itemSize = QSize(item->parentItem()->width(), item->parentItem()->height());
802 }
803
804 QRectF sceneRect = item->mapRectToScene(rect: QRectF(QPointF(0, 0), itemSize));
805 QPoint screenPos = item->window()->mapToGlobal(pos: sceneRect.topLeft().toPoint());
806 QSize screenSize = sceneRect.size().toSize();
807 return QRect(screenPos, screenSize);
808}
809
810QTextDocument *QAccessibleQuickItem::textDocument() const
811{
812 QVariant docVariant = item()->property(name: "textDocument");
813 if (docVariant.canConvert<QQuickTextDocument*>()) {
814 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
815 return qqdoc->textDocument();
816 }
817 return nullptr;
818}
819
820int QAccessibleQuickItem::characterCount() const
821{
822 if (m_doc) {
823 QTextCursor cursor = QTextCursor(m_doc);
824 cursor.movePosition(op: QTextCursor::End);
825 return cursor.position();
826 }
827
828 if (role() == QAccessible::EditableText)
829 return text(textType: QAccessible::Value).size();
830
831 return text(textType: QAccessible::Name).size();
832}
833
834int QAccessibleQuickItem::cursorPosition() const
835{
836 QVariant pos = item()->property(name: "cursorPosition");
837 return pos.toInt();
838}
839
840void QAccessibleQuickItem::setCursorPosition(int position)
841{
842 item()->setProperty(name: "cursorPosition", value: position);
843}
844
845QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
846{
847 if (m_doc) {
848 QTextCursor cursor = QTextCursor(m_doc);
849 cursor.setPosition(pos: startOffset);
850 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
851 return cursor.selectedText();
852 }
853
854 if (role() == QAccessible::EditableText)
855 return text(textType: QAccessible::Value).mid(position: startOffset, n: endOffset - startOffset);
856
857 return text(textType: QAccessible::Name).mid(position: startOffset, n: endOffset - startOffset);
858}
859
860QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
861 int *startOffset, int *endOffset) const
862{
863 Q_ASSERT(startOffset);
864 Q_ASSERT(endOffset);
865
866 if (m_doc) {
867 QTextCursor cursor = QTextCursor(m_doc);
868 cursor.setPosition(pos: offset);
869 std::pair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
870 cursor.setPosition(pos: boundaries.first - 1);
871 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
872
873 *startOffset = boundaries.first;
874 *endOffset = boundaries.second;
875
876 return text(startOffset: boundaries.first, endOffset: boundaries.second);
877 } else {
878 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
879 }
880}
881
882QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
883 int *startOffset, int *endOffset) const
884{
885 Q_ASSERT(startOffset);
886 Q_ASSERT(endOffset);
887
888 if (m_doc) {
889 QTextCursor cursor = QTextCursor(m_doc);
890 cursor.setPosition(pos: offset);
891 std::pair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
892 cursor.setPosition(pos: boundaries.second);
893 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
894
895 *startOffset = boundaries.first;
896 *endOffset = boundaries.second;
897
898 return text(startOffset: boundaries.first, endOffset: boundaries.second);
899 } else {
900 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
901 }
902}
903
904QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
905 int *startOffset, int *endOffset) const
906{
907 Q_ASSERT(startOffset);
908 Q_ASSERT(endOffset);
909
910 if (m_doc) {
911 QTextCursor cursor = QTextCursor(m_doc);
912 cursor.setPosition(pos: offset);
913 std::pair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
914
915 *startOffset = boundaries.first;
916 *endOffset = boundaries.second;
917 return text(startOffset: boundaries.first, endOffset: boundaries.second);
918 } else {
919 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
920 }
921}
922
923void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
924{
925 if (selectionIndex == 0) {
926 *startOffset = item()->property(name: "selectionStart").toInt();
927 *endOffset = item()->property(name: "selectionEnd").toInt();
928 } else {
929 *startOffset = 0;
930 *endOffset = 0;
931 }
932}
933
934int QAccessibleQuickItem::selectionCount() const
935{
936 if (item()->property(name: "selectionStart").toInt() != item()->property(name: "selectionEnd").toInt())
937 return 1;
938 return 0;
939}
940
941void QAccessibleQuickItem::addSelection(int /* startOffset */, int /* endOffset */)
942{
943
944}
945void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
946{
947
948}
949void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
950{
951
952}
953
954
955#endif // accessibility
956
957QT_END_NAMESPACE
958

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