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