aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/accessible/qaccessiblequickitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/accessible/qaccessiblequickitem.cpp')
-rw-r--r--src/quick/accessible/qaccessiblequickitem.cpp369
1 files changed, 310 insertions, 59 deletions
diff --git a/src/quick/accessible/qaccessiblequickitem.cpp b/src/quick/accessible/qaccessiblequickitem.cpp
index bca03b496d..514b1a9214 100644
--- a/src/quick/accessible/qaccessiblequickitem.cpp
+++ b/src/quick/accessible/qaccessiblequickitem.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQuick module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// 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
#include "qaccessiblequickitem_p.h"
@@ -43,13 +7,204 @@
#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 QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
+ if (linkIndex < links.size())
+ return links.at(linkIndex).m_anchor;
+ return QString();
+ }
+
+ QString anchorTarget() const override
+ {
+ const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
+ if (linkIndex < links.size())
+ return links.at(linkIndex).m_anchorTarget;
+ return QString();
+ }
+
+ int startIndex() const override
+ {
+ const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
+ if (linkIndex < links.size())
+ return links.at(linkIndex).m_startIndex;
+ return -1;
+ }
+
+ int endIndex() const override
+ {
+ const QVector<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 QVector<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())
{
@@ -57,12 +212,30 @@ QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
QWindow *QAccessibleQuickItem::window() const
{
- return item()->window();
+ 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
{
- return childItems().count();
+ // 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
@@ -96,8 +269,20 @@ QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const
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.count() - 1; i >= 0; --i) {
+ 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;
@@ -113,19 +298,15 @@ QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const
QAccessibleInterface *QAccessibleQuickItem::parent() const
{
QQuickItem *parent = item()->parentItem();
- QQuickWindow *window = item()->window();
- QQuickItem *ci = window ? window->contentItem() : nullptr;
+ 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) {
- // Jump out to the scene widget if the parent is the root item.
- // There are two root items, QQuickWindow::rootItem and
- // QQuickView::declarativeRoot. The former is the true root item,
- // but is not a part of the accessibility tree. Check if we hit
- // it here and return an interface for the scene instead.
- return QAccessible::queryAccessibleInterface(window);
+ // 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();
@@ -137,19 +318,73 @@ QAccessibleInterface *QAccessibleQuickItem::parent() const
QAccessibleInterface *QAccessibleQuickItem::child(int index) const
{
- QList<QQuickItem *> children = childItems();
+ /* 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).
- if (index < 0 || index >= children.count())
+ 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;
- QQuickItem *child = children.at(index);
- return QAccessible::queryAccessibleInterface(child);
+
+ 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();
- return kids.indexOf(static_cast<QQuickItem*>(iface->object()));
+ 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)
@@ -177,6 +412,11 @@ 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
{
QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item());
@@ -188,19 +428,23 @@ QAccessible::State QAccessibleQuickItem::state() const
QRect viewRect_ = viewRect();
QRect itemRect = rect();
- if (viewRect_.isNull() || itemRect.isNull() || !item()->window() || !item()->window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity()))
+ 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) && object()->property("checked").toBool())
state.checked = true;
- if (item()->activeFocusOnTab() || role() == QAccessible::EditableText)
+ 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;
+ }
return state;
}
@@ -212,10 +456,12 @@ QAccessible::Role QAccessibleQuickItem::role() const
QAccessible::Role role = QAccessible::NoRole;
if (item())
- role = QQuickItemPrivate::get(item())->accessibleRole();
+ 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;
}
@@ -232,6 +478,7 @@ QStringList QAccessibleQuickItem::actionNames() const
{
QStringList actions;
switch (role()) {
+ case QAccessible::Link:
case QAccessible::PushButton:
actions << QAccessibleActionInterface::pressAction();
break;
@@ -375,6 +622,8 @@ QString QAccessibleQuickItem::text(QAccessible::Text textType) const
// 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();
}
@@ -414,9 +663,11 @@ void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
r == QAccessible::ScrollBar))
return static_cast<QAccessibleValueInterface*>(this);
- if (t == QAccessible::TextInterface &&
- (r == QAccessible::EditableText))
+ if (t == QAccessible::TextInterface) {
+ if (r == QAccessible::EditableText ||
+ r == QAccessible::StaticText)
return static_cast<QAccessibleTextInterface*>(this);
+ }
return QAccessibleObject::interface_cast(t);
}