Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qaccessiblequickitem_p.h"
++
++ #include <QtGui/private/qaccessiblehelper_p.h>
++ #include <QtGui/qtextdocument.h>
++
++ #include "QtQuick/private/qquickitem_p.h"
++ #include "QtQuick/private/qquicktext_p.h"
++ #include <private/qquicktext_p_p.h>
++
++ #include "QtQuick/private/qquicktextinput_p.h"
++ #include "QtQuick/private/qquickaccessibleattached_p.h"
++ #include "QtQuick/qquicktextdocument.h"
++ #include "QtQuick/qquickrendercontrol.h"
++ QT_BEGIN_NAMESPACE
++
++ #if QT_CONFIG(accessibility)
++
++ class QAccessibleHyperlink : public QAccessibleInterface, public QAccessibleHyperlinkInterface {
++ public:
++ QAccessibleHyperlink(QQuickItem *parentTextItem, int linkIndex);
++
++ // check for valid pointers
++ bool isValid() const override;
++ QObject *object() const override;
++ QWindow *window() const override;
++
++ // navigation, hierarchy
++ QAccessibleInterface *parent() const override;
++ QAccessibleInterface *child(int index) const override;
++ int childCount() const override;
++ int indexOfChild(const QAccessibleInterface *iface) const override;
++ QAccessibleInterface *childAt(int x, int y) const override;
++
++ // properties and state
++ QString text(QAccessible::Text) const override;
++ void setText(QAccessible::Text, const QString &text) override;
++ QRect rect() const override;
++ QAccessible::Role role() const override;
++ QAccessible::State state() const override;
++
++ void *interface_cast(QAccessible::InterfaceType t) override;
++
++ // QAccessibleHyperlinkInterface
++ QString anchor() const override
++ {
++ const QList<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
++ if (linkIndex < links.size())
++ return links.at(linkIndex).m_anchor;
++ return QString();
++ }
++
++ QString anchorTarget() const override
++ {
++ const QList<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
++ if (linkIndex < links.size())
++ return links.at(linkIndex).m_anchorTarget;
++ return QString();
++ }
++
++ int startIndex() const override
++ {
++ const QList<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
++ if (linkIndex < links.size())
++ return links.at(linkIndex).m_startIndex;
++ return -1;
++ }
++
++ int endIndex() const override
++ {
++ const QList<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
++ if (linkIndex < links.size())
++ return links.at(linkIndex).m_endIndex;
++ return -1;
++ }
++
++ private:
++ QQuickText *textItem() const { return qobject_cast<QQuickText*>(parentTextItem); }
++ QQuickItem *parentTextItem;
++ const int linkIndex;
++
++ friend class QAccessibleQuickItem;
++ };
++
++
++ QAccessibleHyperlink::QAccessibleHyperlink(QQuickItem *parentTextItem, int linkIndex)
++ : parentTextItem(parentTextItem),
++ linkIndex(linkIndex)
++ {
++ }
++
++
++ bool QAccessibleHyperlink::isValid() const
++ {
++ return textItem();
++ }
++
++
++ QObject *QAccessibleHyperlink::object() const
++ {
++ return nullptr;
++ }
++
++
++ QWindow *QAccessibleHyperlink::window() const
++ {
++ return textItem()->window();
++ }
++
++
++ /* \reimp */
++ QRect QAccessibleHyperlink::rect() const
++ {
++ const QList<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
++ if (linkIndex < links.size()) {
++ const QPoint tl = itemScreenRect(textItem()).topLeft();
++ return links.at(linkIndex).rect.translated(tl);
++ }
++ return QRect();
++ }
++
++ /* \reimp */
++ QAccessibleInterface *QAccessibleHyperlink::childAt(int, int) const
++ {
++ return nullptr;
++ }
++
++ /* \reimp */
++ QAccessibleInterface *QAccessibleHyperlink::parent() const
++ {
++ return QAccessible::queryAccessibleInterface(textItem());
++ }
++
++ /* \reimp */
++ QAccessibleInterface *QAccessibleHyperlink::child(int) const
++ {
++ return nullptr;
++ }
++
++ /* \reimp */
++ int QAccessibleHyperlink::childCount() const
++ {
++ return 0;
++ }
++
++ /* \reimp */
++ int QAccessibleHyperlink::indexOfChild(const QAccessibleInterface *) const
++ {
++ return -1;
++ }
++
++ /* \reimp */
++ QAccessible::State QAccessibleHyperlink::state() const
++ {
++ QAccessible::State s;
++ s.selectable = true;
++ s.focusable = true;
++ s.selectableText = true;
++ s.selected = false;
++ return s;
++ }
++
++ /* \reimp */
++ QAccessible::Role QAccessibleHyperlink::role() const
++ {
++ return QAccessible::Link;
++ }
++
++ /* \reimp */
++ QString QAccessibleHyperlink::text(QAccessible::Text t) const
++ {
++ // AT servers have different behaviors:
++ // Wordpad on windows have this behavior:
++ // * Name returns the anchor target (URL)
++ // * Value returns the anchor target (URL)
++
++ // Other AT servers (e.g. MS Edge on Windows) does what seems to be more sensible:
++ // * Name returns the anchor name
++ // * Value returns the anchor target (URL)
++ if (t == QAccessible::Name)
++ return anchor();
++ if (t == QAccessible::Value)
++ return anchorTarget();
++ return QString();
++ }
++
++ /* \reimp */
++ void QAccessibleHyperlink::setText(QAccessible::Text, const QString &)
++ {
++
++ }
++
++ /* \reimp */
++ void *QAccessibleHyperlink::interface_cast(QAccessible::InterfaceType t)
++ {
++ if (t == QAccessible::HyperlinkInterface)
++ return static_cast<QAccessibleHyperlinkInterface*>(this);
++ return nullptr;
++ }
++
++
++ /*!
++ * \internal
++ * \brief QAccessibleQuickItem::QAccessibleQuickItem
++ * \param item
++ */
++ QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
++ : QAccessibleObject(item), m_doc(textDocument())
++ {
++ }
++
++ bool QAccessibleQuickItem::isValid() const
++ {
++ return item() && !QQuickItemPrivate::get(item())->inDestructor;
++ }
++
++
++ QWindow *QAccessibleQuickItem::window() const
++ {
++ QQuickWindow *window = item()->window();
++
++ // For QQuickWidget the above window will be the offscreen QQuickWindow,
++ // which is not a part of the accessibility tree. Detect this case and
++ // return the window for the QQuickWidget instead.
++ if (window && !window->handle()) {
++ if (QQuickRenderControl *renderControl = QQuickWindowPrivate::get(window)->renderControl) {
++ if (QWindow *renderWindow = renderControl->renderWindow(nullptr))
++ return renderWindow;
++ }
++ }
++
++ return window;
++ }
++
++ int QAccessibleQuickItem::childCount() const
++ {
++ // see comment in QAccessibleQuickItem::child() as to why we do this
++ int cc = 0;
++ if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
++ cc = QQuickTextPrivate::get(textItem)->getLinks().size();
++ }
++ cc += childItems().size();
++ return cc;
++ }
++
++ QRect QAccessibleQuickItem::rect() const
++ {
++ const QRect r = itemScreenRect(item());
++ return r;
++ }
++
++ QRect QAccessibleQuickItem::viewRect() const
++ {
++ // ### no window in some cases.
++ if (!item()->window()) {
++ return QRect();
++ }
++
++ QQuickWindow *window = item()->window();
++ QPoint screenPos = window->mapToGlobal(QPoint(0,0));
++ return QRect(screenPos, window->size());
++ }
++
++
++ bool QAccessibleQuickItem::clipsChildren() const
++ {
++ return static_cast<QQuickItem *>(item())->clip();
++ }
++
++ QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const
++ {
++ if (item()->clip()) {
++ if (!rect().contains(x, y))
++ return nullptr;
++ }
++
++ // special case for text interfaces
++ if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
++ const auto hyperLinkChildCount = QQuickTextPrivate::get(textItem)->getLinks().size();
++ for (auto i = 0; i < hyperLinkChildCount; i++) {
++ QAccessibleInterface *iface = child(i);
++ if (iface->rect().contains(x,y)) {
++ return iface;
++ }
++ }
++ }
++
++ // general item hit test
++ const QList<QQuickItem*> kids = accessibleUnignoredChildren(item(), true);
++ for (int i = kids.size() - 1; i >= 0; --i) {
++ QAccessibleInterface *childIface = QAccessible::queryAccessibleInterface(kids.at(i));
++ if (QAccessibleInterface *childChild = childIface->childAt(x, y))
++ return childChild;
++ if (childIface && !childIface->state().invisible) {
++ if (childIface->rect().contains(x, y))
++ return childIface;
++ }
++ }
++
++ return nullptr;
++ }
++
++ QAccessibleInterface *QAccessibleQuickItem::parent() const
++ {
++ QQuickItem *parent = item()->parentItem();
++ QQuickWindow *itemWindow = item()->window();
++ QQuickItem *ci = itemWindow ? itemWindow->contentItem() : nullptr;
++ while (parent && !QQuickItemPrivate::get(parent)->isAccessible && parent != ci)
++ parent = parent->parentItem();
++
++ if (parent) {
++ if (parent == ci) {
++ if (auto *iface = itemWindow->accessibleRoot())
++ return iface;
++ // Jump out to the window if the parent is the root item
++ return QAccessible::queryAccessibleInterface(window());
++ } else {
++ while (parent && !parent->d_func()->isAccessible)
++ parent = parent->parentItem();
++ return QAccessible::queryAccessibleInterface(parent);
++ }
++ }
++ return nullptr;
++ }
++
++ QAccessibleInterface *QAccessibleQuickItem::child(int index) const
++ {
++ /* Text with hyperlinks will have dedicated children interfaces representing each hyperlink.
++
++ For the pathological case when a Text node has hyperlinks in its text *and* accessible
++ quick items as children, we put the hyperlink a11y interfaces as the first children, then
++ the other interfaces follows the hyperlink children (as siblings).
++
++ For example, suppose you have two links in the text and an image as a child of the text,
++ it will have the following a11y hierarchy:
++
++ [a11y:TextInterface]
++ |
++ +- [a11y:HyperlinkInterface]
++ +- [a11y:HyperlinkInterface]
++ +- [a11y:ImageInterface]
++
++ Having this order (as opposed to having hyperlink interfaces last) will at least
++ ensure that the child id of hyperlink children is not altered when child is added/removed
++ to the text item and marked accessible.
++ In addition, hyperlink interfaces as children should be the common case, so it is preferred
++ to explore those first when iterating.
++ */
++ if (index < 0)
++ return nullptr;
++
++
++ if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
++ const int hyperLinkChildCount = QQuickTextPrivate::get(textItem)->getLinks().size();
++ if (index < hyperLinkChildCount) {
++ auto it = m_childToId.constFind(index);
++ if (it != m_childToId.constEnd())
++ return QAccessible::accessibleInterface(it.value());
++
++ QAccessibleHyperlink *iface = new QAccessibleHyperlink(item(), index);
++ QAccessible::Id id = QAccessible::registerAccessibleInterface(iface);
++ m_childToId.insert(index, id);
++ return iface;
++ }
++ index -= hyperLinkChildCount;
++ }
++
++ QList<QQuickItem *> children = childItems();
++ if (index < children.size()) {
++ QQuickItem *child = children.at(index);
++ return QAccessible::queryAccessibleInterface(child);
++ }
++ return nullptr;
++ }
++
++ int QAccessibleQuickItem::indexOfChild(const QAccessibleInterface *iface) const
++ {
++ int hyperLinkChildCount = 0;
++ if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
++ hyperLinkChildCount = QQuickTextPrivate::get(textItem)->getLinks().size();
++ if (QAccessibleHyperlinkInterface *hyperLinkIface = const_cast<QAccessibleInterface *>(iface)->hyperlinkInterface()) {
++ // ### assumes that there is only one subclass implementing QAccessibleHyperlinkInterface
++ // Alternatively, we could simply iterate with child() and do a linear search for it
++ QAccessibleHyperlink *hyperLink = static_cast<QAccessibleHyperlink*>(hyperLinkIface);
++ if (hyperLink->textItem() == static_cast<QQuickText*>(item())) {
++ return hyperLink->linkIndex;
++ }
++ }
++ }
++ QList<QQuickItem*> kids = childItems();
++ int idx = kids.indexOf(static_cast<QQuickItem*>(iface->object()));
++ if (idx >= 0)
++ idx += hyperLinkChildCount;
++ return idx;
++ }
++
++ static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items, bool paintOrder)
++ {
++ const QList<QQuickItem*> childItems = paintOrder ? QQuickItemPrivate::get(item)->paintOrderChildItems()
++ : item->childItems();
++ for (QQuickItem *child : childItems) {
++ if (QQuickItemPrivate::get(child)->isAccessible) {
++ items->append(child);
++ } else {
++ unignoredChildren(child, items, paintOrder);
++ }
++ }
++ }
++
++ QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item, bool paintOrder)
++ {
++ QList<QQuickItem *> items;
++ unignoredChildren(item, &items, paintOrder);
++ return items;
++ }
++
++ QList<QQuickItem *> QAccessibleQuickItem::childItems() const
++ {
++ return accessibleUnignoredChildren(item());
++ }
++
++ static bool isTextRole(QAccessible::Role role)
++ {
++ return role == QAccessible::EditableText || role == QAccessible::StaticText;
++ }
++
++ QAccessible::State QAccessibleQuickItem::state() const
++ {
++ QAccessible::State state;
++ if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
++ state = attached->state();
++
++ QRect viewRect_ = viewRect();
++ QRect itemRect = rect();
++
++ if (viewRect_.isNull() || itemRect.isNull() || !window() || !window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity()))
++ state.invisible = true;
++ if (!viewRect_.intersects(itemRect))
++ state.offscreen = true;
++ if ((role() == QAccessible::CheckBox
++ || role() == QAccessible::RadioButton
++ || role() == QAccessible::Switch)
++ && object()->property("checked").toBool())
++ state.checked = true;
++ if (role() == QAccessible::Button && object()->property("expandable").toBool()) {
++ state.expandable = true;
++ state.expanded = object()->property("expanded").toBool();
++ }
++ if (item()->activeFocusOnTab() || isTextRole(role()))
++ state.focusable = true;
++ if (item()->hasActiveFocus())
++ state.focused = true;
++ if (role() == QAccessible::EditableText)
++ if (auto ti = qobject_cast<QQuickTextInput *>(item()))
++ state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
++ if (!item()->isEnabled()) {
++ state.focusable = false;
++ state.disabled = true;
++ }
++ if (item()->property("readOnly").toBool() || role() == QAccessible::ProgressBar)
++ state.readOnly = true;
++ return state;
++ }
++
++ QList<std::pair<QAccessibleInterface *, QAccessible::Relation>>
++ QAccessibleQuickItem::relations(QAccessible::Relation match) const
++ {
++ QList<std::pair<QAccessibleInterface *, QAccessible::Relation>> rels =
++ QAccessibleObject::relations(match);
++ if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item())) {
++ if (match & QAccessible::Labelled) {
++ if (auto *labelFor = attached->labelFor()) {
++ rels.append({QAccessible::queryAccessibleInterface(labelFor),
++ QAccessible::Labelled});
++ }
++ }
++
++ if (match & QAccessible::Label) {
++ if (auto *labelledBy = attached->labelledBy()) {
++ rels.append({QAccessible::queryAccessibleInterface(labelledBy),
++ QAccessible::Label});
++ }
++ }
++ }
++ return rels;
++ }
++
++ QAccessible::Role QAccessibleQuickItem::role() const
++ {
++ // Workaround for setAccessibleRole() not working for
++ // Text items. Text items are special since they are defined
++ // entirely from C++ (setting the role from QML works.)
++
++ QAccessible::Role role = QAccessible::NoRole;
++ if (item())
++ role = QQuickItemPrivate::get(item())->effectiveAccessibleRole();
++ if (role == QAccessible::NoRole) {
++ if (qobject_cast<QQuickText*>(const_cast<QQuickItem *>(item())))
++ role = QAccessible::StaticText;
++ else if (qobject_cast<QQuickTextInput*>(const_cast<QQuickItem *>(item())))
++ role = QAccessible::EditableText;
++ else
++ role = QAccessible::Client;
++ }
++
++ return role;
++ }
++
++ bool QAccessibleQuickItem::isAccessible() const
++ {
++ return item()->d_func()->isAccessible;
++ }
++
++ QStringList QAccessibleQuickItem::actionNames() const
++ {
++ QStringList actions;
++ switch (role()) {
++ case QAccessible::Button:
++ if (state().expandable)
++ actions << QAccessibleActionInterface::toggleAction();
++ Q_FALLTHROUGH();
++ case QAccessible::Link:
++ case QAccessible::MenuItem:
++ actions << QAccessibleActionInterface::pressAction();
++ break;
++ case QAccessible::RadioButton:
++ case QAccessible::CheckBox:
++ case QAccessible::Switch:
++ actions << QAccessibleActionInterface::toggleAction()
++ << QAccessibleActionInterface::pressAction();
++ break;
++ case QAccessible::Slider:
++ case QAccessible::SpinBox:
++ case QAccessible::ScrollBar:
++ actions << QAccessibleActionInterface::increaseAction()
++ << QAccessibleActionInterface::decreaseAction();
++ break;
++ default:
++ break;
++ }
++ if (state().focusable)
++ actions.append(QAccessibleActionInterface::setFocusAction());
++
++ // ### The following can lead to duplicate action names.
++ if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
++ attached->availableActions(&actions);
++ return actions;
++ }
++
++ void QAccessibleQuickItem::doAction(const QString &actionName)
++ {
++ bool accepted = false;
++ if (actionName == QAccessibleActionInterface::setFocusAction()) {
++ item()->forceActiveFocus();
++ accepted = true;
++ }
++ if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
++ accepted = attached->doAction(actionName);
++
++ if (accepted)
++ return;
++ // Look for and call the accessible[actionName]Action() function on the item.
++ // This allows for overriding the default action handling.
++ const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action";
++ if (object()->metaObject()->indexOfMethod(QByteArray(functionName + "()")) != -1) {
++ QMetaObject::invokeMethod(object(), functionName);
++ return;
++ }
++
++ // Role-specific default action handling follows. Items are expected to provide
++ // properties according to role conventions. These will then be read and/or updated
++ // by the accessibility system.
++ // Checkable roles : checked
++ // Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
++ switch (role()) {
++ case QAccessible::RadioButton:
++ case QAccessible::CheckBox:
++ case QAccessible::Switch: {
++ QVariant checked = object()->property("checked");
++ if (checked.isValid()) {
++ if (actionName == QAccessibleActionInterface::toggleAction()
++ || actionName == QAccessibleActionInterface::pressAction()) {
++ object()->setProperty("checked", QVariant(!checked.toBool()));
++ }
++ }
++ break;
++ }
++ case QAccessible::Slider:
++ case QAccessible::SpinBox:
++ case QAccessible::Dial:
++ case QAccessible::ScrollBar: {
++ if (actionName != QAccessibleActionInterface::increaseAction() &&
++ actionName != QAccessibleActionInterface::decreaseAction())
++ break;
++
++ // Update the value using QAccessibleValueInterface, respecting
++ // the minimum and maximum value (if set). Also check for and
++ // use the "stepSize" property on the item
++ if (QAccessibleValueInterface *valueIface = valueInterface()) {
++ QVariant valueV = valueIface->currentValue();
++ qreal newValue = valueV.toReal();
++
++ QVariant stepSizeV = object()->property("stepSize");
++ qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
++ if (actionName == QAccessibleActionInterface::increaseAction()) {
++ newValue += stepSize;
++ } else {
++ newValue -= stepSize;
++ }
++
++ QVariant minimumValueV = valueIface->minimumValue();
++ if (minimumValueV.isValid()) {
++ newValue = qMax(newValue, minimumValueV.toReal());
++ }
++ QVariant maximumValueV = valueIface->maximumValue();
++ if (maximumValueV.isValid()) {
++ newValue = qMin(newValue, maximumValueV.toReal());
++ }
++
++ valueIface->setCurrentValue(QVariant(newValue));
++ }
++ break;
++ }
++ case QAccessible::Button: {
++ QVariant expanded = object()->property("expanded");
++ if (expanded.isValid()) {
++ if (actionName == QAccessibleActionInterface::toggleAction()
++ || actionName == QAccessibleActionInterface::pressAction()) {
++ object()->setProperty("expanded", QVariant(!expanded.toBool()));
++ }
++ }
++ break;
++ }
++ default:
++ break;
++ }
++ }
++
++ QStringList QAccessibleQuickItem::keyBindingsForAction(const QString &actionName) const
++ {
++ Q_UNUSED(actionName);
++ return QStringList();
++ }
++
++ QString QAccessibleQuickItem::text(QAccessible::Text textType) const
++ {
++ // handles generic behavior not specific to an item
++ switch (textType) {
++ case QAccessible::Name: {
++ QVariant accessibleName = QQuickAccessibleAttached::property(object(), "name");
++ if (!accessibleName.isNull())
++ return accessibleName.toString();
++ break;}
++ case QAccessible::Description: {
++ QVariant accessibleDecription = QQuickAccessibleAttached::property(object(), "description");
++ if (!accessibleDecription.isNull())
++ return accessibleDecription.toString();
++ break;}
++ case QAccessible::Identifier: {
++ QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object(), "id");
++ if (!accessibleIdentifier.isNull())
++ return accessibleIdentifier.toString();
++ auto quickItem = item();
++ if (quickItem->isComponentComplete()) {
++ QQmlContext *context = qmlContext(quickItem);
++ if (context) {
++ const auto objectId = context->nameForObject(quickItem);
++ if (!objectId.isEmpty())
++ return objectId;
++ }
++ }
++ break;}
++ #ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
++ case QAccessible::DebugDescription: {
++ QString debugString;
++ debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
++ debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
++ return debugString;
++ break; }
++ #endif
++ case QAccessible::Value:
++ case QAccessible::Help:
++ case QAccessible::Accelerator:
++ default:
++ break;
++ }
++
++ // the following block handles item-specific behavior
++ if (role() == QAccessible::EditableText) {
++ if (textType == QAccessible::Value) {
++ if (auto textInput = qobject_cast<QQuickTextInput *>(item()))
++ return textInput->displayText();
++ if (QTextDocument *doc = textDocument()) {
++ return doc->toPlainText();
++ }
++ QVariant text = object()->property("text");
++ return text.toString();
++ }
++ }
++
++ return QString();
++ }
++
++ void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
++ {
++ if (role() != QAccessible::EditableText)
++ return;
++ if (textType != QAccessible::Value)
++ return;
++
++ if (QTextDocument *doc = textDocument()) {
++ doc->setPlainText(text);
++ return;
++ }
++ auto textPropertyName = "text";
++ if (object()->metaObject()->indexOfProperty(textPropertyName) >= 0)
++ object()->setProperty(textPropertyName, text);
++ }
++
++ void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
++ {
++ const QAccessible::Role r = role();
++ switch (t) {
++ case QAccessible::ActionInterface:
++ return static_cast<QAccessibleActionInterface*>(this);
++ case QAccessible::AttributesInterface:
++ return static_cast<QAccessibleAttributesInterface *>(this);
++ case QAccessible::ValueInterface:
++ if (r == QAccessible::Slider
++ || r == QAccessible::SpinBox
++ || r == QAccessible::Dial
++ || r == QAccessible::ScrollBar
++ || r == QAccessible::ProgressBar) {
++ return static_cast<QAccessibleValueInterface*>(this);
++ }
++ break;
++ case QAccessible::TextInterface:
++ if (r == QAccessible::EditableText
++ || r == QAccessible::StaticText
++ || r == QAccessible::Heading) {
++ return static_cast<QAccessibleTextInterface*>(this);
++ }
++ break;
++ default:
++ break;
++ }
++
++ return QAccessibleObject::interface_cast(t);
++ }
++
++ QVariant QAccessibleQuickItem::currentValue() const
++ {
++ return item()->property("value");
++ }
++
++ void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
++ {
++ item()->setProperty("value", value);
++ }
++
++ QVariant QAccessibleQuickItem::maximumValue() const
++ {
++ const auto minimumValue = item()->property("minimumValue");
++ const auto maximumValue = item()->property("maximumValue");
++ const auto from = item()->property("from");
++ const auto to = item()->property("to");
++
++ if (minimumValue.isValid() && maximumValue.isValid())
++ return maximumValue;
++
++ if (from.isValid() && to.isValid())
++ return to;
++
++ return QVariant();
++ }
++
++ QVariant QAccessibleQuickItem::minimumValue() const
++ {
++ const auto minimumValue = item()->property("minimumValue");
++ const auto maximumValue = item()->property("maximumValue");
++ const auto from = item()->property("from");
++ const auto to = item()->property("to");
++
++ if (minimumValue.isValid() && maximumValue.isValid())
++ return minimumValue;
++
++ if (from.isValid() && to.isValid())
++ return from;
++
++ return QVariant();
++ }
++
++ QVariant QAccessibleQuickItem::minimumStepSize() const
++ {
++ return item()->property("stepSize");
++ }
++
++ /*!
++ \internal
++ Shared between QAccessibleQuickItem and QAccessibleQuickView
++ */
++ QRect itemScreenRect(QQuickItem *item)
++ {
++ // ### no window in some cases.
++ // ### Should we really check for 0 opacity?
++ if (!item->window() ||!item->isVisible() || qFuzzyIsNull(item->opacity())) {
++ return QRect();
++ }
++
++ QSizeF itemSize(item->width(), item->height());
++ // ### If the bounding rect fails, we first try the implicit size, then we go for the
++ // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
++ if (itemSize.isEmpty()) {
++ itemSize = QSize(item->implicitWidth(), item->implicitHeight());
++ if (itemSize.isEmpty() && item->parentItem())
++ // ### Seems that the above fallback is not enough, fallback to use the parent size...
++ itemSize = QSize(item->parentItem()->width(), item->parentItem()->height());
++ }
++
++ QRectF sceneRect = item->mapRectToScene(QRectF(QPointF(0, 0), itemSize));
++ QPoint screenPos = item->window()->mapToGlobal(sceneRect.topLeft().toPoint());
++ QSize screenSize = sceneRect.size().toSize();
++ return QRect(screenPos, screenSize);
++ }
++
++ QTextDocument *QAccessibleQuickItem::textDocument() const
++ {
++ QVariant docVariant = item()->property("textDocument");
++ if (docVariant.canConvert<QQuickTextDocument*>()) {
++ QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
++ return qqdoc->textDocument();
++ }
++ return nullptr;
++ }
++
++ int QAccessibleQuickItem::characterCount() const
++ {
++ if (m_doc) {
++ QTextCursor cursor = QTextCursor(m_doc);
++ cursor.movePosition(QTextCursor::End);
++ return cursor.position();
++ }
++
++ if (role() == QAccessible::EditableText)
++ return text(QAccessible::Value).size();
++
++ return text(QAccessible::Name).size();
++ }
++
++ int QAccessibleQuickItem::cursorPosition() const
++ {
++ QVariant pos = item()->property("cursorPosition");
++ return pos.toInt();
++ }
++
++ void QAccessibleQuickItem::setCursorPosition(int position)
++ {
++ item()->setProperty("cursorPosition", position);
++ }
++
++ QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
++ {
++ if (m_doc) {
++ QTextCursor cursor = QTextCursor(m_doc);
++ cursor.setPosition(startOffset);
++ cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
++ return cursor.selectedText();
++ }
++
++ if (role() == QAccessible::EditableText)
++ return text(QAccessible::Value).mid(startOffset, endOffset - startOffset);
++
++ return text(QAccessible::Name).mid(startOffset, endOffset - startOffset);
++ }
++
++ QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
++ int *startOffset, int *endOffset) const
++ {
++ Q_ASSERT(startOffset);
++ Q_ASSERT(endOffset);
++
++ if (m_doc) {
++ return qt_accTextBeforeOffsetHelper(*this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
++ } else {
++ return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
++ }
++ }
++
++ QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
++ int *startOffset, int *endOffset) const
++ {
++ Q_ASSERT(startOffset);
++ Q_ASSERT(endOffset);
++
++ if (m_doc) {
++ return qt_accTextAfterOffsetHelper(*this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
++ } else {
++ return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
++ }
++ }
++
++ QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
++ int *startOffset, int *endOffset) const
++ {
++ Q_ASSERT(startOffset);
++ Q_ASSERT(endOffset);
++
++ if (m_doc) {
++ return qt_accTextAtOffsetHelper(*this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
++ } else {
++ return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
++ }
++ }
++
++ void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
++ {
++ if (selectionIndex == 0) {
++ *startOffset = item()->property("selectionStart").toInt();
++ *endOffset = item()->property("selectionEnd").toInt();
++ } else {
++ *startOffset = 0;
++ *endOffset = 0;
++ }
++ }
++
++ int QAccessibleQuickItem::selectionCount() const
++ {
++ if (item()->property("selectionStart").toInt() != item()->property("selectionEnd").toInt())
++ return 1;
++ return 0;
++ }
++
++ void QAccessibleQuickItem::addSelection(int startOffset, int endOffset)
++ {
++ setSelection(0, startOffset, endOffset);
++ }
++ void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
++ {
++
++ }
++ void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
++ {
++
++ }
++
++ QList<QAccessible::Attribute> QAccessibleQuickItem::attributeKeys() const
++ {
++ QList<QAccessible::Attribute> attributes;
++
++ if (attributeValue(QAccessible::Attribute::Locale).isValid())
++ attributes << QAccessible::Attribute::Locale;
++
++ if (attributeValue(QAccessible::Attribute::Level).isValid())
++ attributes << QAccessible::Attribute::Level;
++
++ return attributes;
++ }
++
++ QVariant QAccessibleQuickItem::attributeValue(QAccessible::Attribute key) const
++ {
++ if (key == QAccessible::Attribute::Locale) {
++ QAccessibleInterface *windowIface = QAccessible::queryAccessibleInterface(window());
++ if (!windowIface || !windowIface->attributesInterface())
++ return QVariant();
++
++ return windowIface->attributesInterface()->attributeValue(QAccessible::Attribute::Locale);
++ }
++
++ if (key == QAccessible::Attribute::Level) {
++ const auto levelAttribute = QQuickAccessibleAttached::property(object(), "levelAttribute");
++ if (!levelAttribute.isNull())
++ return levelAttribute;
++ }
++
++ return QVariant();
++ }
++
++ #endif // accessibility
++
++ QT_END_NAMESPACE