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 QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item());
433 if (!attached)
434 return QAccessible::State();
435
436 QAccessible::State state = attached->state();
437
438 QRect viewRect_ = viewRect();
439 QRect itemRect = rect();
440
441 if (viewRect_.isNull() || itemRect.isNull() || !window() || !window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity()))
442 state.invisible = true;
443 if (!viewRect_.intersects(itemRect))
444 state.offscreen = true;
445 if ((role() == QAccessible::CheckBox
446 || role() == QAccessible::RadioButton
447 || role() == QAccessible::Switch)
448 && object()->property("checked").toBool())
449 state.checked = true;
450 if (item()->activeFocusOnTab() || isTextRole(role()))
451 state.focusable = true;
452 if (item()->hasActiveFocus())
453 state.focused = true;
454 if (role() == QAccessible::EditableText)
455 if (auto ti = qobject_cast<QQuickTextInput *>(item()))
456 state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
457 if (!item()->isEnabled()) {
458 state.focusable = false;
459 state.disabled = true;
460 }
461 if (item()->property("readOnly").toBool() || role() == QAccessible::ProgressBar)
462 state.readOnly = true;
463 return state;
464}
465
466QList<std::pair<QAccessibleInterface *, QAccessible::Relation>>
467QAccessibleQuickItem::relations(QAccessible::Relation match) const
468{
469 QList<std::pair<QAccessibleInterface *, QAccessible::Relation>> rels =
470 QAccessibleObject::relations(match);
471 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item())) {
472 if (match & QAccessible::Labelled) {
473 if (auto *labelFor = attached->labelFor()) {
474 rels.append({QAccessible::queryAccessibleInterface(labelFor),
475 QAccessible::Labelled});
476 }
477 }
478
479 if (match & QAccessible::Label) {
480 if (auto *labelledBy = attached->labelledBy()) {
481 rels.append({QAccessible::queryAccessibleInterface(labelledBy),
482 QAccessible::Label});
483 }
484 }
485 }
486 return rels;
487}
488
489QAccessible::Role QAccessibleQuickItem::role() const
490{
491 // Workaround for setAccessibleRole() not working for
492 // Text items. Text items are special since they are defined
493 // entirely from C++ (setting the role from QML works.)
494
495 QAccessible::Role role = QAccessible::NoRole;
496 if (item())
497 role = QQuickItemPrivate::get(item())->effectiveAccessibleRole();
498 if (role == QAccessible::NoRole) {
499 if (qobject_cast<QQuickText*>(const_cast<QQuickItem *>(item())))
500 role = QAccessible::StaticText;
501 else if (qobject_cast<QQuickTextInput*>(const_cast<QQuickItem *>(item())))
502 role = QAccessible::EditableText;
503 else
504 role = QAccessible::Client;
505 }
506
507 return role;
508}
509
510bool QAccessibleQuickItem::isAccessible() const
511{
512 return item()->d_func()->isAccessible;
513}
514
515QStringList QAccessibleQuickItem::actionNames() const
516{
517 QStringList actions;
518 switch (role()) {
519 case QAccessible::Link:
520 case QAccessible::PushButton:
521 case QAccessible::MenuItem:
522 actions << QAccessibleActionInterface::pressAction();
523 break;
524 case QAccessible::RadioButton:
525 case QAccessible::CheckBox:
526 case QAccessible::Switch:
527 actions << QAccessibleActionInterface::toggleAction()
528 << QAccessibleActionInterface::pressAction();
529 break;
530 case QAccessible::Slider:
531 case QAccessible::SpinBox:
532 case QAccessible::ScrollBar:
533 actions << QAccessibleActionInterface::increaseAction()
534 << QAccessibleActionInterface::decreaseAction();
535 break;
536 default:
537 break;
538 }
539 if (state().focusable)
540 actions.append(QAccessibleActionInterface::setFocusAction());
541
542 // ### The following can lead to duplicate action names.
543 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
544 attached->availableActions(&actions);
545 return actions;
546}
547
548void QAccessibleQuickItem::doAction(const QString &actionName)
549{
550 bool accepted = false;
551 if (actionName == QAccessibleActionInterface::setFocusAction()) {
552 item()->forceActiveFocus();
553 accepted = true;
554 }
555 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
556 accepted = attached->doAction(actionName);
557
558 if (accepted)
559 return;
560 // Look for and call the accessible[actionName]Action() function on the item.
561 // This allows for overriding the default action handling.
562 const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action";
563 if (object()->metaObject()->indexOfMethod(QByteArray(functionName + "()")) != -1) {
564 QMetaObject::invokeMethod(object(), functionName);
565 return;
566 }
567
568 // Role-specific default action handling follows. Items are expected to provide
569 // properties according to role conventions. These will then be read and/or updated
570 // by the accessibility system.
571 // Checkable roles : checked
572 // Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
573 switch (role()) {
574 case QAccessible::RadioButton:
575 case QAccessible::CheckBox:
576 case QAccessible::Switch: {
577 QVariant checked = object()->property("checked");
578 if (checked.isValid()) {
579 if (actionName == QAccessibleActionInterface::toggleAction() ||
580 actionName == QAccessibleActionInterface::pressAction()) {
581
582 object()->setProperty("checked", QVariant(!checked.toBool()));
583 }
584 }
585 break;
586 }
587 case QAccessible::Slider:
588 case QAccessible::SpinBox:
589 case QAccessible::Dial:
590 case QAccessible::ScrollBar: {
591 if (actionName != QAccessibleActionInterface::increaseAction() &&
592 actionName != QAccessibleActionInterface::decreaseAction())
593 break;
594
595 // Update the value using QAccessibleValueInterface, respecting
596 // the minimum and maximum value (if set). Also check for and
597 // use the "stepSize" property on the item
598 if (QAccessibleValueInterface *valueIface = valueInterface()) {
599 QVariant valueV = valueIface->currentValue();
600 qreal newValue = valueV.toReal();
601
602 QVariant stepSizeV = object()->property("stepSize");
603 qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
604 if (actionName == QAccessibleActionInterface::increaseAction()) {
605 newValue += stepSize;
606 } else {
607 newValue -= stepSize;
608 }
609
610 QVariant minimumValueV = valueIface->minimumValue();
611 if (minimumValueV.isValid()) {
612 newValue = qMax(newValue, minimumValueV.toReal());
613 }
614 QVariant maximumValueV = valueIface->maximumValue();
615 if (maximumValueV.isValid()) {
616 newValue = qMin(newValue, maximumValueV.toReal());
617 }
618
619 valueIface->setCurrentValue(QVariant(newValue));
620 }
621 break;
622 }
623 default:
624 break;
625 }
626}
627
628QStringList QAccessibleQuickItem::keyBindingsForAction(const QString &actionName) const
629{
630 Q_UNUSED(actionName);
631 return QStringList();
632}
633
634QString QAccessibleQuickItem::text(QAccessible::Text textType) const
635{
636 // handles generic behavior not specific to an item
637 switch (textType) {
638 case QAccessible::Name: {
639 QVariant accessibleName = QQuickAccessibleAttached::property(object(), "name");
640 if (!accessibleName.isNull())
641 return accessibleName.toString();
642 break;}
643 case QAccessible::Description: {
644 QVariant accessibleDecription = QQuickAccessibleAttached::property(object(), "description");
645 if (!accessibleDecription.isNull())
646 return accessibleDecription.toString();
647 break;}
648 case QAccessible::Identifier: {
649 QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object(), "id");
650 if (!accessibleIdentifier.isNull())
651 return accessibleIdentifier.toString();
652 auto quickItem = item();
653 if (quickItem->isComponentComplete()) {
654 QQmlContext *context = qmlContext(quickItem);
655 if (context) {
656 const auto objectId = context->nameForObject(quickItem);
657 if (!objectId.isEmpty())
658 return objectId;
659 }
660 }
661 break;}
662#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
663 case QAccessible::DebugDescription: {
664 QString debugString;
665 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
666 debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
667 return debugString;
668 break; }
669#endif
670 case QAccessible::Value:
671 case QAccessible::Help:
672 case QAccessible::Accelerator:
673 default:
674 break;
675 }
676
677 // the following block handles item-specific behavior
678 if (role() == QAccessible::EditableText) {
679 if (textType == QAccessible::Value) {
680 if (auto textInput = qobject_cast<QQuickTextInput *>(item()))
681 return textInput->displayText();
682 if (QTextDocument *doc = textDocument()) {
683 return doc->toPlainText();
684 }
685 QVariant text = object()->property("text");
686 return text.toString();
687 }
688 }
689
690 return QString();
691}
692
693void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
694{
695 if (role() != QAccessible::EditableText)
696 return;
697 if (textType != QAccessible::Value)
698 return;
699
700 if (QTextDocument *doc = textDocument()) {
701 doc->setPlainText(text);
702 return;
703 }
704 auto textPropertyName = "text";
705 if (object()->metaObject()->indexOfProperty(textPropertyName) >= 0)
706 object()->setProperty(textPropertyName, text);
707}
708
709void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
710{
711 const QAccessible::Role r = role();
712 switch (t) {
713 case QAccessible::ActionInterface:
714 return static_cast<QAccessibleActionInterface*>(this);
715 case QAccessible::AttributesInterface:
716 return static_cast<QAccessibleAttributesInterface *>(this);
717 case QAccessible::ValueInterface:
718 if (r == QAccessible::Slider
719 || r == QAccessible::SpinBox
720 || r == QAccessible::Dial
721 || r == QAccessible::ScrollBar
722 || r == QAccessible::ProgressBar) {
723 return static_cast<QAccessibleValueInterface*>(this);
724 }
725 break;
726 case QAccessible::TextInterface:
727 if (r == QAccessible::EditableText
728 || r == QAccessible::StaticText
729 || r == QAccessible::Heading) {
730 return static_cast<QAccessibleTextInterface*>(this);
731 }
732 break;
733 default:
734 break;
735 }
736
737 return QAccessibleObject::interface_cast(t);
738}
739
740QVariant QAccessibleQuickItem::currentValue() const
741{
742 return item()->property("value");
743}
744
745void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
746{
747 item()->setProperty("value", value);
748}
749
750QVariant QAccessibleQuickItem::maximumValue() const
751{
752 const auto minimumValue = item()->property("minimumValue");
753 const auto maximumValue = item()->property("maximumValue");
754 const auto from = item()->property("from");
755 const auto to = item()->property("to");
756
757 if (minimumValue.isValid() && maximumValue.isValid())
758 return maximumValue;
759
760 if (from.isValid() && to.isValid())
761 return to;
762
763 return QVariant();
764}
765
766QVariant QAccessibleQuickItem::minimumValue() const
767{
768 const auto minimumValue = item()->property("minimumValue");
769 const auto maximumValue = item()->property("maximumValue");
770 const auto from = item()->property("from");
771 const auto to = item()->property("to");
772
773 if (minimumValue.isValid() && maximumValue.isValid())
774 return minimumValue;
775
776 if (from.isValid() && to.isValid())
777 return from;
778
779 return QVariant();
780}
781
782QVariant QAccessibleQuickItem::minimumStepSize() const
783{
784 return item()->property("stepSize");
785}
786
787/*!
788 \internal
789 Shared between QAccessibleQuickItem and QAccessibleQuickView
790*/
791QRect itemScreenRect(QQuickItem *item)
792{
793 // ### no window in some cases.
794 // ### Should we really check for 0 opacity?
795 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(item->opacity())) {
796 return QRect();
797 }
798
799 QSizeF itemSize(item->width(), item->height());
800 // ### If the bounding rect fails, we first try the implicit size, then we go for the
801 // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
802 if (itemSize.isEmpty()) {
803 itemSize = QSize(item->implicitWidth(), item->implicitHeight());
804 if (itemSize.isEmpty() && item->parentItem())
805 // ### Seems that the above fallback is not enough, fallback to use the parent size...
806 itemSize = QSize(item->parentItem()->width(), item->parentItem()->height());
807 }
808
809 QRectF sceneRect = item->mapRectToScene(QRectF(QPointF(0, 0), itemSize));
810 QPoint screenPos = item->window()->mapToGlobal(sceneRect.topLeft().toPoint());
811 QSize screenSize = sceneRect.size().toSize();
812 return QRect(screenPos, screenSize);
813}
814
815QTextDocument *QAccessibleQuickItem::textDocument() const
816{
817 QVariant docVariant = item()->property("textDocument");
818 if (docVariant.canConvert<QQuickTextDocument*>()) {
819 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
820 return qqdoc->textDocument();
821 }
822 return nullptr;
823}
824
825int QAccessibleQuickItem::characterCount() const
826{
827 if (m_doc) {
828 QTextCursor cursor = QTextCursor(m_doc);
829 cursor.movePosition(QTextCursor::End);
830 return cursor.position();
831 }
832
833 if (role() == QAccessible::EditableText)
834 return text(QAccessible::Value).size();
835
836 return text(QAccessible::Name).size();
837}
838
839int QAccessibleQuickItem::cursorPosition() const
840{
841 QVariant pos = item()->property("cursorPosition");
842 return pos.toInt();
843}
844
845void QAccessibleQuickItem::setCursorPosition(int position)
846{
847 item()->setProperty("cursorPosition", position);
848}
849
850QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
851{
852 if (m_doc) {
853 QTextCursor cursor = QTextCursor(m_doc);
854 cursor.setPosition(startOffset);
855 cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
856 return cursor.selectedText();
857 }
858
859 if (role() == QAccessible::EditableText)
860 return text(QAccessible::Value).mid(startOffset, endOffset - startOffset);
861
862 return text(QAccessible::Name).mid(startOffset, endOffset - startOffset);
863}
864
865QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
866 int *startOffset, int *endOffset) const
867{
868 Q_ASSERT(startOffset);
869 Q_ASSERT(endOffset);
870
871 if (m_doc) {
872 return qt_accTextBeforeOffsetHelper(*this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
873 } else {
874 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
875 }
876}
877
878QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
879 int *startOffset, int *endOffset) const
880{
881 Q_ASSERT(startOffset);
882 Q_ASSERT(endOffset);
883
884 if (m_doc) {
885 return qt_accTextAfterOffsetHelper(*this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
886 } else {
887 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
888 }
889}
890
891QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
892 int *startOffset, int *endOffset) const
893{
894 Q_ASSERT(startOffset);
895 Q_ASSERT(endOffset);
896
897 if (m_doc) {
898 return qt_accTextAtOffsetHelper(*this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
899 } else {
900 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
901 }
902}
903
904void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
905{
906 if (selectionIndex == 0) {
907 *startOffset = item()->property("selectionStart").toInt();
908 *endOffset = item()->property("selectionEnd").toInt();
909 } else {
910 *startOffset = 0;
911 *endOffset = 0;
912 }
913}
914
915int QAccessibleQuickItem::selectionCount() const
916{
917 if (item()->property("selectionStart").toInt() != item()->property("selectionEnd").toInt())
918 return 1;
919 return 0;
920}
921
922void QAccessibleQuickItem::addSelection(int startOffset, int endOffset)
923{
924 setSelection(0, startOffset, endOffset);
925}
926void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
927{
928
929}
930void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
931{
932
933}
934
935QList<QAccessible::Attribute> QAccessibleQuickItem::attributeKeys() const
936{
937 if (attributeValue(QAccessible::Attribute::Locale).isValid())
938 return { QAccessible::Attribute::Locale };
939
940 return {};
941}
942
943QVariant QAccessibleQuickItem::attributeValue(QAccessible::Attribute key) const
944{
945 if (key == QAccessible::Attribute::Locale) {
946 QAccessibleInterface *windowIface = QAccessible::queryAccessibleInterface(window());
947 if (!windowIface || !windowIface->attributesInterface())
948 return QVariant();
949
950 return windowIface->attributesInterface()->attributeValue(QAccessible::Attribute::Locale);
951 }
952
953 return QVariant();
954}
955
956#endif // accessibility
957
958QT_END_NAMESPACE