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