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