7#include <QtGui/private/qaccessiblehelper_p.h>
8#include <QtGui/qtextdocument.h>
10#include "QtQuick/private/qquickitem_p.h"
11#include "QtQuick/private/qquicktext_p.h"
12#include <private/qquicktext_p_p.h>
14#include "QtQuick/private/qquicktextinput_p.h"
15#include "QtQuick/private/qquickaccessibleattached_p.h"
16#include "QtQuick/qquicktextdocument.h"
17#include "QtQuick/qquickrendercontrol.h"
20#if QT_CONFIG(accessibility)
22class QAccessibleHyperlink :
public QAccessibleInterface,
public QAccessibleHyperlinkInterface {
24 QAccessibleHyperlink(QQuickItem *parentTextItem,
int linkIndex);
27 bool isValid()
const override;
28 QObject *object()
const override;
29 QWindow *window()
const override;
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;
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;
45 void *interface_cast(QAccessible::InterfaceType t) override;
48 QString anchor()
const override
50 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
51 if (linkIndex < links.size())
52 return links.at(linkIndex).m_anchor;
56 QString anchorTarget()
const override
58 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
59 if (linkIndex < links.size())
60 return links.at(linkIndex).m_anchorTarget;
64 int startIndex()
const override
66 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
67 if (linkIndex < links.size())
68 return links.at(linkIndex).m_startIndex;
72 int endIndex()
const override
74 const QVector<QQuickTextPrivate::LinkDesc> links = QQuickTextPrivate::get(textItem())->getLinks();
75 if (linkIndex < links.size())
76 return links.at(linkIndex).m_endIndex;
81 QQuickText *textItem()
const {
return qobject_cast<QQuickText*>(parentTextItem); }
82 QQuickItem *parentTextItem;
85 friend class QAccessibleQuickItem;
89QAccessibleHyperlink::QAccessibleHyperlink(QQuickItem *parentTextItem,
int linkIndex)
90 : parentTextItem(parentTextItem),
96bool QAccessibleHyperlink::isValid()
const
102QObject *QAccessibleHyperlink::object()
const
108QWindow *QAccessibleHyperlink::window()
const
110 return textItem()->window();
115QRect QAccessibleHyperlink::rect()
const
117 const QVector<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);
126QAccessibleInterface *QAccessibleHyperlink::childAt(
int,
int)
const
132QAccessibleInterface *QAccessibleHyperlink::parent()
const
134 return QAccessible::queryAccessibleInterface(textItem());
138QAccessibleInterface *QAccessibleHyperlink::child(
int)
const
144int QAccessibleHyperlink::childCount()
const
150int QAccessibleHyperlink::indexOfChild(
const QAccessibleInterface *)
const
156QAccessible::State QAccessibleHyperlink::state()
const
158 QAccessible::State s;
161 s.selectableText =
true;
167QAccessible::Role QAccessibleHyperlink::role()
const
169 return QAccessible::Link;
173QString QAccessibleHyperlink::text(QAccessible::Text t)
const
183 if (t == QAccessible::Name)
185 if (t == QAccessible::Value)
186 return anchorTarget();
191void QAccessibleHyperlink::setText(QAccessible::Text,
const QString &)
197void *QAccessibleHyperlink::interface_cast(QAccessible::InterfaceType t)
199 if (t == QAccessible::HyperlinkInterface)
200 return static_cast<QAccessibleHyperlinkInterface*>(
this);
206
207
208
209
210QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
211 : QAccessibleObject(item), m_doc(textDocument())
215bool QAccessibleQuickItem::isValid()
const
217 return item() && !QQuickItemPrivate::get(item())->inDestructor;
221QWindow *QAccessibleQuickItem::window()
const
223 QQuickWindow *window = item()->window();
228 if (window && !window->handle()) {
229 if (QQuickRenderControl *renderControl = QQuickWindowPrivate::get(window)->renderControl) {
230 if (QWindow *renderWindow = renderControl->renderWindow(
nullptr))
238int QAccessibleQuickItem::childCount()
const
242 if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
243 cc = QQuickTextPrivate::get(textItem)->getLinks().size();
245 cc += childItems().size();
249QRect QAccessibleQuickItem::rect()
const
251 const QRect r = itemScreenRect(item());
255QRect QAccessibleQuickItem::viewRect()
const
258 if (!item()->window()) {
262 QQuickWindow *window = item()->window();
263 QPoint screenPos = window->mapToGlobal(QPoint(0,0));
264 return QRect(screenPos, window->size());
268bool QAccessibleQuickItem::clipsChildren()
const
270 return static_cast<QQuickItem *>(item())->clip();
273QAccessibleInterface *QAccessibleQuickItem::childAt(
int x,
int y)
const
275 if (item()->clip()) {
276 if (!rect().contains(x, y))
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)) {
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))
297 if (childIface && !childIface->state().invisible) {
298 if (childIface->rect().contains(x, y))
306QAccessibleInterface *QAccessibleQuickItem::parent()
const
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();
316 if (itemWindow && !itemWindow->handle()) {
324 const auto parentWidgetProp = itemWindow->property(
"_q_parentWidget");
325 if (parentWidgetProp.isValid()) {
326 if (QObject *parentWidget = parentWidgetProp.value<QObject *>())
327 return QAccessible::queryAccessibleInterface(parentWidget);
331 return QAccessible::queryAccessibleInterface(window());
333 while (parent && !parent->d_func()->isAccessible)
334 parent = parent->parentItem();
335 return QAccessible::queryAccessibleInterface(parent);
341QAccessibleInterface *QAccessibleQuickItem::child(
int index)
const
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
368 if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
369 const int hyperLinkChildCount = QQuickTextPrivate::get(textItem)->getLinks().size();
370 if (index < hyperLinkChildCount) {
371 auto it = m_childToId.constFind(index);
372 if (it != m_childToId.constEnd())
373 return QAccessible::accessibleInterface(it.value());
375 QAccessibleHyperlink *iface =
new QAccessibleHyperlink(item(), index);
376 QAccessible::Id id = QAccessible::registerAccessibleInterface(iface);
377 m_childToId.insert(index, id);
380 index -= hyperLinkChildCount;
383 QList<QQuickItem *> children = childItems();
384 if (index < children.size()) {
385 QQuickItem *child = children.at(index);
386 return QAccessible::queryAccessibleInterface(child);
391int QAccessibleQuickItem::indexOfChild(
const QAccessibleInterface *iface)
const
393 int hyperLinkChildCount = 0;
394 if (QQuickText *textItem = qobject_cast<QQuickText*>(item())) {
395 hyperLinkChildCount = QQuickTextPrivate::get(textItem)->getLinks().size();
396 if (QAccessibleHyperlinkInterface *hyperLinkIface =
const_cast<QAccessibleInterface *>(iface)->hyperlinkInterface()) {
399 QAccessibleHyperlink *hyperLink =
static_cast<QAccessibleHyperlink*>(hyperLinkIface);
400 if (hyperLink->textItem() ==
static_cast<QQuickText*>(item())) {
401 return hyperLink->linkIndex;
405 QList<QQuickItem*> kids = childItems();
406 int idx = kids.indexOf(
static_cast<QQuickItem*>(iface->object()));
408 idx += hyperLinkChildCount;
412static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items,
bool paintOrder)
414 const QList<QQuickItem*> childItems = paintOrder ? QQuickItemPrivate::get(item)->paintOrderChildItems()
415 : item->childItems();
416 for (QQuickItem *child : childItems) {
417 if (QQuickItemPrivate::get(child)->isAccessible) {
418 items->append(child);
420 unignoredChildren(child, items, paintOrder);
425QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item,
bool paintOrder)
427 QList<QQuickItem *> items;
428 unignoredChildren(item, &items, paintOrder);
432QList<QQuickItem *> QAccessibleQuickItem::childItems()
const
434 return accessibleUnignoredChildren(item());
437static bool isTextRole(QAccessible::Role role)
439 return role == QAccessible::EditableText || role == QAccessible::StaticText;
442QAccessible::State QAccessibleQuickItem::state()
const
444 QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item());
446 return QAccessible::State();
448 QAccessible::State state = attached->state();
450 QRect viewRect_ = viewRect();
451 QRect itemRect = rect();
453 if (viewRect_.isNull() || itemRect.isNull() || !window() || !window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity()))
454 state.invisible =
true;
455 if (!viewRect_.intersects(itemRect))
456 state.offscreen =
true;
457 if ((role() == QAccessible::CheckBox
458 || role() == QAccessible::RadioButton
459 || role() == QAccessible::Switch)
460 && object()->property(
"checked").toBool())
461 state.checked =
true;
462 if (item()->activeFocusOnTab() || isTextRole(role()))
463 state.focusable =
true;
464 if (item()->hasActiveFocus())
465 state.focused =
true;
466 if (role() == QAccessible::EditableText)
467 if (
auto ti = qobject_cast<QQuickTextInput *>(item()))
468 state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
469 if (!item()->isEnabled()) {
470 state.focusable =
false;
471 state.disabled =
true;
476QList<std::pair<QAccessibleInterface *, QAccessible::Relation>>
477QAccessibleQuickItem::relations(QAccessible::Relation match)
const
479 QList<std::pair<QAccessibleInterface *, QAccessible::Relation>> rels =
480 QAccessibleObject::relations(match);
481 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item())) {
482 if (match & QAccessible::Labelled) {
483 if (
auto *labelFor = attached->labelFor()) {
484 rels.append({QAccessible::queryAccessibleInterface(labelFor),
485 QAccessible::Labelled});
489 if (match & QAccessible::Label) {
490 if (
auto *labelledBy = attached->labelledBy()) {
491 rels.append({QAccessible::queryAccessibleInterface(labelledBy),
492 QAccessible::Label});
499QAccessible::Role QAccessibleQuickItem::role()
const
505 QAccessible::Role role = QAccessible::NoRole;
507 role = QQuickItemPrivate::get(item())->effectiveAccessibleRole();
508 if (role == QAccessible::NoRole) {
509 if (qobject_cast<QQuickText*>(
const_cast<QQuickItem *>(item())))
510 role = QAccessible::StaticText;
511 else if (qobject_cast<QQuickTextInput*>(
const_cast<QQuickItem *>(item())))
512 role = QAccessible::EditableText;
514 role = QAccessible::Client;
520bool QAccessibleQuickItem::isAccessible()
const
522 return item()->d_func()->isAccessible;
525QStringList QAccessibleQuickItem::actionNames()
const
529 case QAccessible::Link:
530 case QAccessible::PushButton:
531 case QAccessible::MenuItem:
532 actions << QAccessibleActionInterface::pressAction();
534 case QAccessible::RadioButton:
535 case QAccessible::CheckBox:
536 case QAccessible::Switch:
537 actions << QAccessibleActionInterface::toggleAction()
538 << QAccessibleActionInterface::pressAction();
540 case QAccessible::Slider:
541 case QAccessible::SpinBox:
542 case QAccessible::ScrollBar:
543 actions << QAccessibleActionInterface::increaseAction()
544 << QAccessibleActionInterface::decreaseAction();
549 if (state().focusable)
550 actions.append(QAccessibleActionInterface::setFocusAction());
553 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
554 attached->availableActions(&actions);
558void QAccessibleQuickItem::doAction(
const QString &actionName)
560 bool accepted =
false;
561 if (actionName == QAccessibleActionInterface::setFocusAction()) {
562 item()->forceActiveFocus();
565 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
566 accepted = attached->doAction(actionName);
572 const QByteArray functionName =
"accessible" + actionName.toLatin1() +
"Action";
573 if (object()->metaObject()->indexOfMethod(QByteArray(functionName +
"()")) != -1) {
574 QMetaObject::invokeMethod(object(), functionName);
584 case QAccessible::RadioButton:
585 case QAccessible::CheckBox:
586 case QAccessible::Switch: {
587 QVariant checked = object()->property(
"checked");
588 if (checked.isValid()) {
589 if (actionName == QAccessibleActionInterface::toggleAction() ||
590 actionName == QAccessibleActionInterface::pressAction()) {
592 object()->setProperty(
"checked", QVariant(!checked.toBool()));
597 case QAccessible::Slider:
598 case QAccessible::SpinBox:
599 case QAccessible::Dial:
600 case QAccessible::ScrollBar: {
601 if (actionName != QAccessibleActionInterface::increaseAction() &&
602 actionName != QAccessibleActionInterface::decreaseAction())
608 if (QAccessibleValueInterface *valueIface = valueInterface()) {
609 QVariant valueV = valueIface->currentValue();
610 qreal newValue = valueV.toReal();
612 QVariant stepSizeV = object()->property(
"stepSize");
613 qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
614 if (actionName == QAccessibleActionInterface::increaseAction()) {
615 newValue += stepSize;
617 newValue -= stepSize;
620 QVariant minimumValueV = valueIface->minimumValue();
621 if (minimumValueV.isValid()) {
622 newValue = qMax(newValue, minimumValueV.toReal());
624 QVariant maximumValueV = valueIface->maximumValue();
625 if (maximumValueV.isValid()) {
626 newValue = qMin(newValue, maximumValueV.toReal());
629 valueIface->setCurrentValue(QVariant(newValue));
638QStringList QAccessibleQuickItem::keyBindingsForAction(
const QString &actionName)
const
640 Q_UNUSED(actionName);
641 return QStringList();
644QString QAccessibleQuickItem::text(QAccessible::Text textType)
const
648 case QAccessible::Name: {
649 QVariant accessibleName = QQuickAccessibleAttached::property(object(),
"name");
650 if (!accessibleName.isNull())
651 return accessibleName.toString();
653 case QAccessible::Description: {
654 QVariant accessibleDecription = QQuickAccessibleAttached::property(object(),
"description");
655 if (!accessibleDecription.isNull())
656 return accessibleDecription.toString();
658 case QAccessible::Identifier: {
659 QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object(),
"id");
660 if (!accessibleIdentifier.isNull())
661 return accessibleIdentifier.toString();
662 auto quickItem = item();
663 if (quickItem->isComponentComplete()) {
664 QQmlContext *context = qmlContext(quickItem);
666 const auto objectId = context->nameForObject(quickItem);
667 if (!objectId.isEmpty())
672#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
673 case QAccessible::DebugDescription: {
675 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(
' ');
676 debugString += isAccessible() ? QLatin1String(
"enabled") : QLatin1String(
"disabled");
680 case QAccessible::Value:
681 case QAccessible::Help:
682 case QAccessible::Accelerator:
688 if (role() == QAccessible::EditableText) {
689 if (textType == QAccessible::Value) {
690 if (
auto textInput = qobject_cast<QQuickTextInput *>(item()))
691 return textInput->displayText();
692 if (QTextDocument *doc = textDocument()) {
693 return doc->toPlainText();
695 QVariant text = object()->property(
"text");
696 return text.toString();
703void QAccessibleQuickItem::setText(QAccessible::Text textType,
const QString &text)
705 if (role() != QAccessible::EditableText)
707 if (textType != QAccessible::Value)
710 if (QTextDocument *doc = textDocument()) {
711 doc->setPlainText(text);
714 auto textPropertyName =
"text";
715 if (object()->metaObject()->indexOfProperty(textPropertyName) >= 0)
716 object()->setProperty(textPropertyName, text);
719void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
721 const QAccessible::Role r = role();
723 case QAccessible::ActionInterface:
724 return static_cast<QAccessibleActionInterface*>(
this);
725 case QAccessible::ValueInterface:
726 if (r == QAccessible::Slider
727 || r == QAccessible::SpinBox
728 || r == QAccessible::Dial
729 || r == QAccessible::ScrollBar
730 || r == QAccessible::ProgressBar) {
731 return static_cast<QAccessibleValueInterface*>(
this);
734 case QAccessible::TextInterface:
735 if (r == QAccessible::EditableText
736 || r == QAccessible::StaticText
737 || r == QAccessible::Heading) {
738 return static_cast<QAccessibleTextInterface*>(
this);
745 return QAccessibleObject::interface_cast(t);
748QVariant QAccessibleQuickItem::currentValue()
const
750 return item()->property(
"value");
753void QAccessibleQuickItem::setCurrentValue(
const QVariant &value)
755 item()->setProperty(
"value", value);
758QVariant QAccessibleQuickItem::maximumValue()
const
760 const auto minimumValue = item()->property(
"minimumValue");
761 const auto maximumValue = item()->property(
"maximumValue");
762 const auto from = item()->property(
"from");
763 const auto to = item()->property(
"to");
765 if (minimumValue.isValid() && maximumValue.isValid())
768 if (from.isValid() && to.isValid())
774QVariant QAccessibleQuickItem::minimumValue()
const
776 const auto minimumValue = item()->property(
"minimumValue");
777 const auto maximumValue = item()->property(
"maximumValue");
778 const auto from = item()->property(
"from");
779 const auto to = item()->property(
"to");
781 if (minimumValue.isValid() && maximumValue.isValid())
784 if (from.isValid() && to.isValid())
790QVariant QAccessibleQuickItem::minimumStepSize()
const
792 return item()->property(
"stepSize");
796
797
798
799QRect itemScreenRect(QQuickItem *item)
803 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(item->opacity())) {
807 QSizeF itemSize(item->width(), item->height());
810 if (itemSize.isEmpty()) {
811 itemSize = QSize(item->implicitWidth(), item->implicitHeight());
812 if (itemSize.isEmpty() && item->parentItem())
814 itemSize = QSize(item->parentItem()->width(), item->parentItem()->height());
817 QRectF sceneRect = item->mapRectToScene(QRectF(QPointF(0, 0), itemSize));
818 QPoint screenPos = item->window()->mapToGlobal(sceneRect.topLeft().toPoint());
819 QSize screenSize = sceneRect.size().toSize();
820 return QRect(screenPos, screenSize);
823QTextDocument *QAccessibleQuickItem::textDocument()
const
825 QVariant docVariant = item()->property(
"textDocument");
826 if (docVariant.canConvert<QQuickTextDocument*>()) {
827 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
828 return qqdoc->textDocument();
833int QAccessibleQuickItem::characterCount()
const
836 QTextCursor cursor = QTextCursor(m_doc);
837 cursor.movePosition(QTextCursor::End);
838 return cursor.position();
841 if (role() == QAccessible::EditableText)
842 return text(QAccessible::Value).size();
844 return text(QAccessible::Name).size();
847int QAccessibleQuickItem::cursorPosition()
const
849 QVariant pos = item()->property(
"cursorPosition");
853void QAccessibleQuickItem::setCursorPosition(
int position)
855 item()->setProperty(
"cursorPosition", position);
858QString QAccessibleQuickItem::text(
int startOffset,
int endOffset)
const
861 QTextCursor cursor = QTextCursor(m_doc);
862 cursor.setPosition(startOffset);
863 cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
864 return cursor.selectedText();
867 if (role() == QAccessible::EditableText)
868 return text(QAccessible::Value).mid(startOffset, endOffset - startOffset);
870 return text(QAccessible::Name).mid(startOffset, endOffset - startOffset);
873QString QAccessibleQuickItem::textBeforeOffset(
int offset, QAccessible::TextBoundaryType boundaryType,
874 int *startOffset,
int *endOffset)
const
876 Q_ASSERT(startOffset);
880 return qt_accTextBeforeOffsetHelper(*
this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
882 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
886QString QAccessibleQuickItem::textAfterOffset(
int offset, QAccessible::TextBoundaryType boundaryType,
887 int *startOffset,
int *endOffset)
const
889 Q_ASSERT(startOffset);
893 return qt_accTextAfterOffsetHelper(*
this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
895 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
899QString QAccessibleQuickItem::textAtOffset(
int offset, QAccessible::TextBoundaryType boundaryType,
900 int *startOffset,
int *endOffset)
const
902 Q_ASSERT(startOffset);
906 return qt_accTextAtOffsetHelper(*
this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
908 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
912void QAccessibleQuickItem::selection(
int selectionIndex,
int *startOffset,
int *endOffset)
const
914 if (selectionIndex == 0) {
915 *startOffset = item()->property(
"selectionStart").toInt();
916 *endOffset = item()->property(
"selectionEnd").toInt();
923int QAccessibleQuickItem::selectionCount()
const
925 if (item()->property(
"selectionStart").toInt() != item()->property(
"selectionEnd").toInt())
930void QAccessibleQuickItem::addSelection(
int startOffset,
int endOffset)
932 setSelection(0, startOffset, endOffset);
934void QAccessibleQuickItem::removeSelection(
int )
938void QAccessibleQuickItem::setSelection(
int ,
int ,
int )