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