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 QList<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 QList<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 QList<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 QList<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 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);
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;
473 if (item()->property(
"readOnly").toBool() || role() == QAccessible::ProgressBar)
474 state.readOnly =
true;
478QList<std::pair<QAccessibleInterface *, QAccessible::Relation>>
479QAccessibleQuickItem::relations(QAccessible::Relation match)
const
481 QList<std::pair<QAccessibleInterface *, QAccessible::Relation>> rels =
482 QAccessibleObject::relations(match);
483 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item())) {
484 if (match & QAccessible::Labelled) {
485 if (
auto *labelFor = attached->labelFor()) {
486 rels.append({QAccessible::queryAccessibleInterface(labelFor),
487 QAccessible::Labelled});
491 if (match & QAccessible::Label) {
492 if (
auto *labelledBy = attached->labelledBy()) {
493 rels.append({QAccessible::queryAccessibleInterface(labelledBy),
494 QAccessible::Label});
501QAccessible::Role QAccessibleQuickItem::role()
const
507 QAccessible::Role role = QAccessible::NoRole;
509 role = QQuickItemPrivate::get(item())->effectiveAccessibleRole();
510 if (role == QAccessible::NoRole) {
511 if (qobject_cast<QQuickText*>(
const_cast<QQuickItem *>(item())))
512 role = QAccessible::StaticText;
513 else if (qobject_cast<QQuickTextInput*>(
const_cast<QQuickItem *>(item())))
514 role = QAccessible::EditableText;
516 role = QAccessible::Client;
522bool QAccessibleQuickItem::isAccessible()
const
524 return item()->d_func()->isAccessible;
527QStringList QAccessibleQuickItem::actionNames()
const
531 case QAccessible::Link:
532 case QAccessible::PushButton:
533 case QAccessible::MenuItem:
534 actions << QAccessibleActionInterface::pressAction();
536 case QAccessible::RadioButton:
537 case QAccessible::CheckBox:
538 case QAccessible::Switch:
539 actions << QAccessibleActionInterface::toggleAction()
540 << QAccessibleActionInterface::pressAction();
542 case QAccessible::Slider:
543 case QAccessible::SpinBox:
544 case QAccessible::ScrollBar:
545 actions << QAccessibleActionInterface::increaseAction()
546 << QAccessibleActionInterface::decreaseAction();
551 if (state().focusable)
552 actions.append(QAccessibleActionInterface::setFocusAction());
555 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
556 attached->availableActions(&actions);
560void QAccessibleQuickItem::doAction(
const QString &actionName)
562 bool accepted =
false;
563 if (actionName == QAccessibleActionInterface::setFocusAction()) {
564 item()->forceActiveFocus();
567 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
568 accepted = attached->doAction(actionName);
574 const QByteArray functionName =
"accessible" + actionName.toLatin1() +
"Action";
575 if (object()->metaObject()->indexOfMethod(QByteArray(functionName +
"()")) != -1) {
576 QMetaObject::invokeMethod(object(), functionName);
586 case QAccessible::RadioButton:
587 case QAccessible::CheckBox:
588 case QAccessible::Switch: {
589 QVariant checked = object()->property(
"checked");
590 if (checked.isValid()) {
591 if (actionName == QAccessibleActionInterface::toggleAction() ||
592 actionName == QAccessibleActionInterface::pressAction()) {
594 object()->setProperty(
"checked", QVariant(!checked.toBool()));
599 case QAccessible::Slider:
600 case QAccessible::SpinBox:
601 case QAccessible::Dial:
602 case QAccessible::ScrollBar: {
603 if (actionName != QAccessibleActionInterface::increaseAction() &&
604 actionName != QAccessibleActionInterface::decreaseAction())
610 if (QAccessibleValueInterface *valueIface = valueInterface()) {
611 QVariant valueV = valueIface->currentValue();
612 qreal newValue = valueV.toReal();
614 QVariant stepSizeV = object()->property(
"stepSize");
615 qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
616 if (actionName == QAccessibleActionInterface::increaseAction()) {
617 newValue += stepSize;
619 newValue -= stepSize;
622 QVariant minimumValueV = valueIface->minimumValue();
623 if (minimumValueV.isValid()) {
624 newValue = qMax(newValue, minimumValueV.toReal());
626 QVariant maximumValueV = valueIface->maximumValue();
627 if (maximumValueV.isValid()) {
628 newValue = qMin(newValue, maximumValueV.toReal());
631 valueIface->setCurrentValue(QVariant(newValue));
640QStringList QAccessibleQuickItem::keyBindingsForAction(
const QString &actionName)
const
642 Q_UNUSED(actionName);
643 return QStringList();
646QString QAccessibleQuickItem::text(QAccessible::Text textType)
const
650 case QAccessible::Name: {
651 QVariant accessibleName = QQuickAccessibleAttached::property(object(),
"name");
652 if (!accessibleName.isNull())
653 return accessibleName.toString();
655 case QAccessible::Description: {
656 QVariant accessibleDecription = QQuickAccessibleAttached::property(object(),
"description");
657 if (!accessibleDecription.isNull())
658 return accessibleDecription.toString();
660 case QAccessible::Identifier: {
661 QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object(),
"id");
662 if (!accessibleIdentifier.isNull())
663 return accessibleIdentifier.toString();
664 auto quickItem = item();
665 if (quickItem->isComponentComplete()) {
666 QQmlContext *context = qmlContext(quickItem);
668 const auto objectId = context->nameForObject(quickItem);
669 if (!objectId.isEmpty())
674#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
675 case QAccessible::DebugDescription: {
677 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(
' ');
678 debugString += isAccessible() ? QLatin1String(
"enabled") : QLatin1String(
"disabled");
682 case QAccessible::Value:
683 case QAccessible::Help:
684 case QAccessible::Accelerator:
690 if (role() == QAccessible::EditableText) {
691 if (textType == QAccessible::Value) {
692 if (
auto textInput = qobject_cast<QQuickTextInput *>(item()))
693 return textInput->displayText();
694 if (QTextDocument *doc = textDocument()) {
695 return doc->toPlainText();
697 QVariant text = object()->property(
"text");
698 return text.toString();
705void QAccessibleQuickItem::setText(QAccessible::Text textType,
const QString &text)
707 if (role() != QAccessible::EditableText)
709 if (textType != QAccessible::Value)
712 if (QTextDocument *doc = textDocument()) {
713 doc->setPlainText(text);
716 auto textPropertyName =
"text";
717 if (object()->metaObject()->indexOfProperty(textPropertyName) >= 0)
718 object()->setProperty(textPropertyName, text);
721void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
723 const QAccessible::Role r = role();
725 case QAccessible::ActionInterface:
726 return static_cast<QAccessibleActionInterface*>(
this);
727 case QAccessible::ValueInterface:
728 if (r == QAccessible::Slider
729 || r == QAccessible::SpinBox
730 || r == QAccessible::Dial
731 || r == QAccessible::ScrollBar
732 || r == QAccessible::ProgressBar) {
733 return static_cast<QAccessibleValueInterface*>(
this);
736 case QAccessible::TextInterface:
737 if (r == QAccessible::EditableText
738 || r == QAccessible::StaticText
739 || r == QAccessible::Heading) {
740 return static_cast<QAccessibleTextInterface*>(
this);
747 return QAccessibleObject::interface_cast(t);
750QVariant QAccessibleQuickItem::currentValue()
const
752 return item()->property(
"value");
755void QAccessibleQuickItem::setCurrentValue(
const QVariant &value)
757 item()->setProperty(
"value", value);
760QVariant QAccessibleQuickItem::maximumValue()
const
762 const auto minimumValue = item()->property(
"minimumValue");
763 const auto maximumValue = item()->property(
"maximumValue");
764 const auto from = item()->property(
"from");
765 const auto to = item()->property(
"to");
767 if (minimumValue.isValid() && maximumValue.isValid())
770 if (from.isValid() && to.isValid())
776QVariant QAccessibleQuickItem::minimumValue()
const
778 const auto minimumValue = item()->property(
"minimumValue");
779 const auto maximumValue = item()->property(
"maximumValue");
780 const auto from = item()->property(
"from");
781 const auto to = item()->property(
"to");
783 if (minimumValue.isValid() && maximumValue.isValid())
786 if (from.isValid() && to.isValid())
792QVariant QAccessibleQuickItem::minimumStepSize()
const
794 return item()->property(
"stepSize");
798
799
800
801QRect itemScreenRect(QQuickItem *item)
805 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(item->opacity())) {
809 QSizeF itemSize(item->width(), item->height());
812 if (itemSize.isEmpty()) {
813 itemSize = QSize(item->implicitWidth(), item->implicitHeight());
814 if (itemSize.isEmpty() && item->parentItem())
816 itemSize = QSize(item->parentItem()->width(), item->parentItem()->height());
819 QRectF sceneRect = item->mapRectToScene(QRectF(QPointF(0, 0), itemSize));
820 QPoint screenPos = item->window()->mapToGlobal(sceneRect.topLeft().toPoint());
821 QSize screenSize = sceneRect.size().toSize();
822 return QRect(screenPos, screenSize);
825QTextDocument *QAccessibleQuickItem::textDocument()
const
827 QVariant docVariant = item()->property(
"textDocument");
828 if (docVariant.canConvert<QQuickTextDocument*>()) {
829 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
830 return qqdoc->textDocument();
835int QAccessibleQuickItem::characterCount()
const
838 QTextCursor cursor = QTextCursor(m_doc);
839 cursor.movePosition(QTextCursor::End);
840 return cursor.position();
843 if (role() == QAccessible::EditableText)
844 return text(QAccessible::Value).size();
846 return text(QAccessible::Name).size();
849int QAccessibleQuickItem::cursorPosition()
const
851 QVariant pos = item()->property(
"cursorPosition");
855void QAccessibleQuickItem::setCursorPosition(
int position)
857 item()->setProperty(
"cursorPosition", position);
860QString QAccessibleQuickItem::text(
int startOffset,
int endOffset)
const
863 QTextCursor cursor = QTextCursor(m_doc);
864 cursor.setPosition(startOffset);
865 cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
866 return cursor.selectedText();
869 if (role() == QAccessible::EditableText)
870 return text(QAccessible::Value).mid(startOffset, endOffset - startOffset);
872 return text(QAccessible::Name).mid(startOffset, endOffset - startOffset);
875QString QAccessibleQuickItem::textBeforeOffset(
int offset, QAccessible::TextBoundaryType boundaryType,
876 int *startOffset,
int *endOffset)
const
878 Q_ASSERT(startOffset);
882 return qt_accTextBeforeOffsetHelper(*
this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
884 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
888QString QAccessibleQuickItem::textAfterOffset(
int offset, QAccessible::TextBoundaryType boundaryType,
889 int *startOffset,
int *endOffset)
const
891 Q_ASSERT(startOffset);
895 return qt_accTextAfterOffsetHelper(*
this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
897 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
901QString QAccessibleQuickItem::textAtOffset(
int offset, QAccessible::TextBoundaryType boundaryType,
902 int *startOffset,
int *endOffset)
const
904 Q_ASSERT(startOffset);
908 return qt_accTextAtOffsetHelper(*
this, QTextCursor(m_doc), offset, boundaryType, startOffset, endOffset);
910 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
914void QAccessibleQuickItem::selection(
int selectionIndex,
int *startOffset,
int *endOffset)
const
916 if (selectionIndex == 0) {
917 *startOffset = item()->property(
"selectionStart").toInt();
918 *endOffset = item()->property(
"selectionEnd").toInt();
925int QAccessibleQuickItem::selectionCount()
const
927 if (item()->property(
"selectionStart").toInt() != item()->property(
"selectionEnd").toInt())
932void QAccessibleQuickItem::addSelection(
int startOffset,
int endOffset)
934 setSelection(0, startOffset, endOffset);
936void QAccessibleQuickItem::removeSelection(
int )
940void QAccessibleQuickItem::setSelection(
int ,
int ,
int )