1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qaccessiblequickitem_p.h"
41
42#include <QtGui/qtextdocument.h>
43
44#include "QtQuick/private/qquickitem_p.h"
45#include "QtQuick/private/qquicktext_p.h"
46#include "QtQuick/private/qquicktextinput_p.h"
47#include "QtQuick/private/qquickaccessibleattached_p.h"
48#include "QtQuick/qquicktextdocument.h"
49QT_BEGIN_NAMESPACE
50
51#if QT_CONFIG(accessibility)
52
53QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
54 : QAccessibleObject(item), m_doc(textDocument())
55{
56}
57
58QWindow *QAccessibleQuickItem::window() const
59{
60 return item()->window();
61}
62
63int QAccessibleQuickItem::childCount() const
64{
65 return childItems().count();
66}
67
68QRect QAccessibleQuickItem::rect() const
69{
70 const QRect r = itemScreenRect(item: item());
71 return r;
72}
73
74QRect QAccessibleQuickItem::viewRect() const
75{
76 // ### no window in some cases.
77 if (!item()->window()) {
78 return QRect();
79 }
80
81 QQuickWindow *window = item()->window();
82 QPoint screenPos = window->mapToGlobal(pos: QPoint(0,0));
83 return QRect(screenPos, window->size());
84}
85
86
87bool QAccessibleQuickItem::clipsChildren() const
88{
89 return static_cast<QQuickItem *>(item())->clip();
90}
91
92QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const
93{
94 if (item()->clip()) {
95 if (!rect().contains(ax: x, ay: y))
96 return nullptr;
97 }
98
99 const QList<QQuickItem*> kids = accessibleUnignoredChildren(item: item(), paintOrder: true);
100 for (int i = kids.count() - 1; i >= 0; --i) {
101 QAccessibleInterface *childIface = QAccessible::queryAccessibleInterface(kids.at(i));
102 if (QAccessibleInterface *childChild = childIface->childAt(x, y))
103 return childChild;
104 if (childIface && !childIface->state().invisible) {
105 if (childIface->rect().contains(ax: x, ay: y))
106 return childIface;
107 }
108 }
109
110 return nullptr;
111}
112
113QAccessibleInterface *QAccessibleQuickItem::parent() const
114{
115 QQuickItem *parent = item()->parentItem();
116 QQuickWindow *window = item()->window();
117 QQuickItem *ci = window ? window->contentItem() : nullptr;
118 while (parent && !QQuickItemPrivate::get(item: parent)->isAccessible && parent != ci)
119 parent = parent->parentItem();
120
121 if (parent) {
122 if (parent == ci) {
123 // Jump out to the scene widget if the parent is the root item.
124 // There are two root items, QQuickWindow::rootItem and
125 // QQuickView::declarativeRoot. The former is the true root item,
126 // but is not a part of the accessibility tree. Check if we hit
127 // it here and return an interface for the scene instead.
128 return QAccessible::queryAccessibleInterface(window);
129 } else {
130 while (parent && !parent->d_func()->isAccessible)
131 parent = parent->parentItem();
132 return QAccessible::queryAccessibleInterface(parent);
133 }
134 }
135 return nullptr;
136}
137
138QAccessibleInterface *QAccessibleQuickItem::child(int index) const
139{
140 QList<QQuickItem *> children = childItems();
141
142 if (index < 0 || index >= children.count())
143 return nullptr;
144
145 QQuickItem *child = children.at(i: index);
146 return QAccessible::queryAccessibleInterface(child);
147}
148
149int QAccessibleQuickItem::indexOfChild(const QAccessibleInterface *iface) const
150{
151 QList<QQuickItem*> kids = childItems();
152 return kids.indexOf(t: static_cast<QQuickItem*>(iface->object()));
153}
154
155static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items, bool paintOrder)
156{
157 const QList<QQuickItem*> childItems = paintOrder ? QQuickItemPrivate::get(item)->paintOrderChildItems()
158 : item->childItems();
159 for (QQuickItem *child : childItems) {
160 if (QQuickItemPrivate::get(item: child)->isAccessible) {
161 items->append(t: child);
162 } else {
163 unignoredChildren(item: child, items, paintOrder);
164 }
165 }
166}
167
168QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item, bool paintOrder)
169{
170 QList<QQuickItem *> items;
171 unignoredChildren(item, items: &items, paintOrder);
172 return items;
173}
174
175QList<QQuickItem *> QAccessibleQuickItem::childItems() const
176{
177 return accessibleUnignoredChildren(item: item());
178}
179
180static bool isTextRole(QAccessible::Role role)
181{
182 return role == QAccessible::EditableText || role == QAccessible::StaticText;
183}
184
185QAccessible::State QAccessibleQuickItem::state() const
186{
187 QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item());
188 if (!attached)
189 return QAccessible::State();
190
191 QAccessible::State state = attached->state();
192
193 QRect viewRect_ = viewRect();
194 QRect itemRect = rect();
195
196 if (viewRect_.isNull() || itemRect.isNull() || !item()->window() || !item()->window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(d: item()->opacity()))
197 state.invisible = true;
198 if (!viewRect_.intersects(r: itemRect))
199 state.offscreen = true;
200 if ((role() == QAccessible::CheckBox || role() == QAccessible::RadioButton) && object()->property(name: "checked").toBool())
201 state.checked = true;
202 if (item()->activeFocusOnTab() || isTextRole(role: role()))
203 state.focusable = true;
204 if (item()->hasActiveFocus())
205 state.focused = true;
206 if (role() == QAccessible::EditableText)
207 if (auto ti = qobject_cast<QQuickTextInput *>(object: item()))
208 state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
209 return state;
210}
211
212QAccessible::Role QAccessibleQuickItem::role() const
213{
214 // Workaround for setAccessibleRole() not working for
215 // Text items. Text items are special since they are defined
216 // entirely from C++ (setting the role from QML works.)
217
218 QAccessible::Role role = QAccessible::NoRole;
219 if (item())
220 role = QQuickItemPrivate::get(item: item())->accessibleRole();
221 if (role == QAccessible::NoRole) {
222 if (qobject_cast<QQuickText*>(object: const_cast<QQuickItem *>(item())))
223 role = QAccessible::StaticText;
224 else if (qobject_cast<QQuickTextInput*>(object: const_cast<QQuickItem *>(item())))
225 role = QAccessible::EditableText;
226 else
227 role = QAccessible::Client;
228 }
229
230 return role;
231}
232
233bool QAccessibleQuickItem::isAccessible() const
234{
235 return item()->d_func()->isAccessible;
236}
237
238QStringList QAccessibleQuickItem::actionNames() const
239{
240 QStringList actions;
241 switch (role()) {
242 case QAccessible::Link:
243 case QAccessible::PushButton:
244 actions << QAccessibleActionInterface::pressAction();
245 break;
246 case QAccessible::RadioButton:
247 case QAccessible::CheckBox:
248 actions << QAccessibleActionInterface::toggleAction()
249 << QAccessibleActionInterface::pressAction();
250 break;
251 case QAccessible::Slider:
252 case QAccessible::SpinBox:
253 case QAccessible::ScrollBar:
254 actions << QAccessibleActionInterface::increaseAction()
255 << QAccessibleActionInterface::decreaseAction();
256 break;
257 default:
258 break;
259 }
260 if (state().focusable)
261 actions.append(t: QAccessibleActionInterface::setFocusAction());
262
263 // ### The following can lead to duplicate action names.
264 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item()))
265 attached->availableActions(actions: &actions);
266 return actions;
267}
268
269void QAccessibleQuickItem::doAction(const QString &actionName)
270{
271 bool accepted = false;
272 if (actionName == QAccessibleActionInterface::setFocusAction()) {
273 item()->forceActiveFocus();
274 accepted = true;
275 }
276 if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(obj: item()))
277 accepted = attached->doAction(actionName);
278
279 if (accepted)
280 return;
281 // Look for and call the accessible[actionName]Action() function on the item.
282 // This allows for overriding the default action handling.
283 const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action";
284 if (object()->metaObject()->indexOfMethod(method: QByteArray(functionName + "()")) != -1) {
285 QMetaObject::invokeMethod(obj: object(), member: functionName);
286 return;
287 }
288
289 // Role-specific default action handling follows. Items are expected to provide
290 // properties according to role conventions. These will then be read and/or updated
291 // by the accessibility system.
292 // Checkable roles : checked
293 // Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
294 switch (role()) {
295 case QAccessible::RadioButton:
296 case QAccessible::CheckBox: {
297 QVariant checked = object()->property(name: "checked");
298 if (checked.isValid()) {
299 if (actionName == QAccessibleActionInterface::toggleAction() ||
300 actionName == QAccessibleActionInterface::pressAction()) {
301
302 object()->setProperty(name: "checked", value: QVariant(!checked.toBool()));
303 }
304 }
305 break;
306 }
307 case QAccessible::Slider:
308 case QAccessible::SpinBox:
309 case QAccessible::Dial:
310 case QAccessible::ScrollBar: {
311 if (actionName != QAccessibleActionInterface::increaseAction() &&
312 actionName != QAccessibleActionInterface::decreaseAction())
313 break;
314
315 // Update the value using QAccessibleValueInterface, respecting
316 // the minimum and maximum value (if set). Also check for and
317 // use the "stepSize" property on the item
318 if (QAccessibleValueInterface *valueIface = valueInterface()) {
319 QVariant valueV = valueIface->currentValue();
320 qreal newValue = valueV.toReal();
321
322 QVariant stepSizeV = object()->property(name: "stepSize");
323 qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
324 if (actionName == QAccessibleActionInterface::increaseAction()) {
325 newValue += stepSize;
326 } else {
327 newValue -= stepSize;
328 }
329
330 QVariant minimumValueV = valueIface->minimumValue();
331 if (minimumValueV.isValid()) {
332 newValue = qMax(a: newValue, b: minimumValueV.toReal());
333 }
334 QVariant maximumValueV = valueIface->maximumValue();
335 if (maximumValueV.isValid()) {
336 newValue = qMin(a: newValue, b: maximumValueV.toReal());
337 }
338
339 valueIface->setCurrentValue(QVariant(newValue));
340 }
341 break;
342 }
343 default:
344 break;
345 }
346}
347
348QStringList QAccessibleQuickItem::keyBindingsForAction(const QString &actionName) const
349{
350 Q_UNUSED(actionName)
351 return QStringList();
352}
353
354QString QAccessibleQuickItem::text(QAccessible::Text textType) const
355{
356 // handles generic behavior not specific to an item
357 switch (textType) {
358 case QAccessible::Name: {
359 QVariant accessibleName = QQuickAccessibleAttached::property(object: object(), propertyName: "name");
360 if (!accessibleName.isNull())
361 return accessibleName.toString();
362 break;}
363 case QAccessible::Description: {
364 QVariant accessibleDecription = QQuickAccessibleAttached::property(object: object(), propertyName: "description");
365 if (!accessibleDecription.isNull())
366 return accessibleDecription.toString();
367 break;}
368#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
369 case QAccessible::DebugDescription: {
370 QString debugString;
371 debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
372 debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
373 return debugString;
374 break; }
375#endif
376 case QAccessible::Value:
377 case QAccessible::Help:
378 case QAccessible::Accelerator:
379 default:
380 break;
381 }
382
383 // the following block handles item-specific behavior
384 if (role() == QAccessible::EditableText) {
385 if (textType == QAccessible::Value) {
386 if (QTextDocument *doc = textDocument()) {
387 return doc->toPlainText();
388 }
389 QVariant text = object()->property(name: "text");
390 return text.toString();
391 }
392 }
393
394 return QString();
395}
396
397void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
398{
399 if (role() != QAccessible::EditableText)
400 return;
401 if (textType != QAccessible::Value)
402 return;
403
404 if (QTextDocument *doc = textDocument()) {
405 doc->setPlainText(text);
406 return;
407 }
408 auto textPropertyName = "text";
409 if (object()->metaObject()->indexOfProperty(name: textPropertyName) >= 0)
410 object()->setProperty(name: textPropertyName, value: text);
411}
412
413void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
414{
415 QAccessible::Role r = role();
416 if (t == QAccessible::ActionInterface)
417 return static_cast<QAccessibleActionInterface*>(this);
418 if (t == QAccessible::ValueInterface &&
419 (r == QAccessible::Slider ||
420 r == QAccessible::SpinBox ||
421 r == QAccessible::Dial ||
422 r == QAccessible::ScrollBar))
423 return static_cast<QAccessibleValueInterface*>(this);
424
425 if (t == QAccessible::TextInterface &&
426 (r == QAccessible::EditableText))
427 return static_cast<QAccessibleTextInterface*>(this);
428
429 return QAccessibleObject::interface_cast(t);
430}
431
432QVariant QAccessibleQuickItem::currentValue() const
433{
434 return item()->property(name: "value");
435}
436
437void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
438{
439 item()->setProperty(name: "value", value);
440}
441
442QVariant QAccessibleQuickItem::maximumValue() const
443{
444 return item()->property(name: "maximumValue");
445}
446
447QVariant QAccessibleQuickItem::minimumValue() const
448{
449 return item()->property(name: "minimumValue");
450}
451
452QVariant QAccessibleQuickItem::minimumStepSize() const
453{
454 return item()->property(name: "stepSize");
455}
456
457/*!
458 \internal
459 Shared between QAccessibleQuickItem and QAccessibleQuickView
460*/
461QRect itemScreenRect(QQuickItem *item)
462{
463 // ### no window in some cases.
464 // ### Should we really check for 0 opacity?
465 if (!item->window() ||!item->isVisible() || qFuzzyIsNull(d: item->opacity())) {
466 return QRect();
467 }
468
469 QSize itemSize((int)item->width(), (int)item->height());
470 // ### If the bounding rect fails, we first try the implicit size, then we go for the
471 // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
472 if (itemSize.isEmpty()) {
473 itemSize = QSize((int)item->implicitWidth(), (int)item->implicitHeight());
474 if (itemSize.isEmpty() && item->parentItem())
475 // ### Seems that the above fallback is not enough, fallback to use the parent size...
476 itemSize = QSize((int)item->parentItem()->width(), (int)item->parentItem()->height());
477 }
478
479 QPointF scenePoint = item->mapToScene(point: QPointF(0, 0));
480 QPoint screenPos = item->window()->mapToGlobal(pos: scenePoint.toPoint());
481 return QRect(screenPos, itemSize);
482}
483
484QTextDocument *QAccessibleQuickItem::textDocument() const
485{
486 QVariant docVariant = item()->property(name: "textDocument");
487 if (docVariant.canConvert<QQuickTextDocument*>()) {
488 QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
489 return qqdoc->textDocument();
490 }
491 return nullptr;
492}
493
494int QAccessibleQuickItem::characterCount() const
495{
496 if (m_doc) {
497 QTextCursor cursor = QTextCursor(m_doc);
498 cursor.movePosition(op: QTextCursor::End);
499 return cursor.position();
500 }
501 return text(textType: QAccessible::Value).size();
502}
503
504int QAccessibleQuickItem::cursorPosition() const
505{
506 QVariant pos = item()->property(name: "cursorPosition");
507 return pos.toInt();
508}
509
510void QAccessibleQuickItem::setCursorPosition(int position)
511{
512 item()->setProperty(name: "cursorPosition", value: position);
513}
514
515QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
516{
517 if (m_doc) {
518 QTextCursor cursor = QTextCursor(m_doc);
519 cursor.setPosition(pos: startOffset);
520 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
521 return cursor.selectedText();
522 }
523 return text(textType: QAccessible::Value).mid(position: startOffset, n: endOffset - startOffset);
524}
525
526QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
527 int *startOffset, int *endOffset) const
528{
529 Q_ASSERT(startOffset);
530 Q_ASSERT(endOffset);
531
532 if (m_doc) {
533 QTextCursor cursor = QTextCursor(m_doc);
534 cursor.setPosition(pos: offset);
535 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
536 cursor.setPosition(pos: boundaries.first - 1);
537 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
538
539 *startOffset = boundaries.first;
540 *endOffset = boundaries.second;
541
542 return text(startOffset: boundaries.first, endOffset: boundaries.second);
543 } else {
544 return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
545 }
546}
547
548QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
549 int *startOffset, int *endOffset) const
550{
551 Q_ASSERT(startOffset);
552 Q_ASSERT(endOffset);
553
554 if (m_doc) {
555 QTextCursor cursor = QTextCursor(m_doc);
556 cursor.setPosition(pos: offset);
557 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
558 cursor.setPosition(pos: boundaries.second);
559 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
560
561 *startOffset = boundaries.first;
562 *endOffset = boundaries.second;
563
564 return text(startOffset: boundaries.first, endOffset: boundaries.second);
565 } else {
566 return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
567 }
568}
569
570QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
571 int *startOffset, int *endOffset) const
572{
573 Q_ASSERT(startOffset);
574 Q_ASSERT(endOffset);
575
576 if (m_doc) {
577 QTextCursor cursor = QTextCursor(m_doc);
578 cursor.setPosition(pos: offset);
579 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
580
581 *startOffset = boundaries.first;
582 *endOffset = boundaries.second;
583 return text(startOffset: boundaries.first, endOffset: boundaries.second);
584 } else {
585 return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
586 }
587}
588
589void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
590{
591 if (selectionIndex == 0) {
592 *startOffset = item()->property(name: "selectionStart").toInt();
593 *endOffset = item()->property(name: "selectionEnd").toInt();
594 } else {
595 *startOffset = 0;
596 *endOffset = 0;
597 }
598}
599
600int QAccessibleQuickItem::selectionCount() const
601{
602 if (item()->property(name: "selectionStart").toInt() != item()->property(name: "selectionEnd").toInt())
603 return 1;
604 return 0;
605}
606
607void QAccessibleQuickItem::addSelection(int /* startOffset */, int /* endOffset */)
608{
609
610}
611void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
612{
613
614}
615void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
616{
617
618}
619
620
621#endif // accessibility
622
623QT_END_NAMESPACE
624

source code of qtdeclarative/src/quick/accessible/qaccessiblequickitem.cpp