blob: 5e1ae25c380e57147a80e9eaa75aa415709fce6c [file] [log] [blame]
/****************************************************************************
**
** 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$
**
****************************************************************************/
#include "qaccessiblequickitem_p.h"
#include <QtGui/qtextdocument.h>
#include "QtQuick/private/qquickitem_p.h"
#include "QtQuick/private/qquicktext_p.h"
#include "QtQuick/private/qquicktextinput_p.h"
#include "QtQuick/private/qquickaccessibleattached_p.h"
#include "QtQuick/qquicktextdocument.h"
QT_BEGIN_NAMESPACE
#if QT_CONFIG(accessibility)
QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
: QAccessibleObject(item), m_doc(textDocument())
{
}
QWindow *QAccessibleQuickItem::window() const
{
return item()->window();
}
int QAccessibleQuickItem::childCount() const
{
return childItems().count();
}
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;
}
const QList<QQuickItem*> kids = accessibleUnignoredChildren(item(), true);
for (int i = kids.count() - 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 *window = item()->window();
QQuickItem *ci = window ? window->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);
} else {
while (parent && !parent->d_func()->isAccessible)
parent = parent->parentItem();
return QAccessible::queryAccessibleInterface(parent);
}
}
return nullptr;
}
QAccessibleInterface *QAccessibleQuickItem::child(int index) const
{
QList<QQuickItem *> children = childItems();
if (index < 0 || index >= children.count())
return nullptr;
QQuickItem *child = children.at(index);
return QAccessible::queryAccessibleInterface(child);
}
int QAccessibleQuickItem::indexOfChild(const QAccessibleInterface *iface) const
{
QList<QQuickItem*> kids = childItems();
return kids.indexOf(static_cast<QQuickItem*>(iface->object()));
}
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());
}
QAccessible::State QAccessibleQuickItem::state() const
{
QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item());
if (!attached)
return QAccessible::State();
QAccessible::State state = attached->state();
QRect viewRect_ = viewRect();
QRect itemRect = rect();
if (viewRect_.isNull() || itemRect.isNull() || !item()->window() || !item()->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)
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;
return state;
}
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())->accessibleRole();
if (role == QAccessible::NoRole) {
if (qobject_cast<QQuickText*>(const_cast<QQuickItem *>(item())))
role = QAccessible::StaticText;
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::PushButton:
actions << QAccessibleActionInterface::pressAction();
break;
case QAccessible::RadioButton:
case QAccessible::CheckBox:
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: {
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;
}
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;}
#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 (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)
{
QAccessible::Role r = role();
if (t == QAccessible::ActionInterface)
return static_cast<QAccessibleActionInterface*>(this);
if (t == QAccessible::ValueInterface &&
(r == QAccessible::Slider ||
r == QAccessible::SpinBox ||
r == QAccessible::Dial ||
r == QAccessible::ScrollBar))
return static_cast<QAccessibleValueInterface*>(this);
if (t == QAccessible::TextInterface &&
(r == QAccessible::EditableText))
return static_cast<QAccessibleTextInterface*>(this);
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
{
return item()->property("maximumValue");
}
QVariant QAccessibleQuickItem::minimumValue() const
{
return item()->property("minimumValue");
}
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();
}
QSize itemSize((int)item->width(), (int)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((int)item->implicitWidth(), (int)item->implicitHeight());
if (itemSize.isEmpty() && item->parentItem())
// ### Seems that the above fallback is not enough, fallback to use the parent size...
itemSize = QSize((int)item->parentItem()->width(), (int)item->parentItem()->height());
}
QPointF scenePoint = item->mapToScene(QPointF(0, 0));
QPoint screenPos = item->window()->mapToGlobal(scenePoint.toPoint());
return QRect(screenPos, itemSize);
}
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();
}
return text(QAccessible::Value).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();
}
return text(QAccessible::Value).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) {
QTextCursor cursor = QTextCursor(m_doc);
cursor.setPosition(offset);
QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
cursor.setPosition(boundaries.first - 1);
boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
*startOffset = boundaries.first;
*endOffset = boundaries.second;
return text(boundaries.first, boundaries.second);
} 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) {
QTextCursor cursor = QTextCursor(m_doc);
cursor.setPosition(offset);
QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
cursor.setPosition(boundaries.second);
boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
*startOffset = boundaries.first;
*endOffset = boundaries.second;
return text(boundaries.first, boundaries.second);
} 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) {
QTextCursor cursor = QTextCursor(m_doc);
cursor.setPosition(offset);
QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
*startOffset = boundaries.first;
*endOffset = boundaries.second;
return text(boundaries.first, boundaries.second);
} 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 */)
{
}
void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
{
}
void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
{
}
#endif // accessibility
QT_END_NAMESPACE