1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickaccessibleattached_p.h"
5
6#if QT_CONFIG(accessibility)
7
8#include <QtQml/qqmlinfo.h>
9
10#include "private/qquickitem_p.h"
11
12QT_BEGIN_NAMESPACE
13
14/*!
15 \qmltype Accessible
16 \instantiates QQuickAccessibleAttached
17 \brief Enables accessibility of QML items.
18
19 \inqmlmodule QtQuick
20 \ingroup qtquick-visual-utility
21 \ingroup accessibility
22
23 This class is part of the \l {Accessibility for Qt Quick Applications}.
24
25 Items the user interacts with or that give information to the user
26 need to expose their information to the accessibility framework.
27 Then assistive tools can make use of that information to enable
28 users to interact with the application in various ways.
29 This enables Qt Quick applications to be used with screen-readers for example.
30
31 The most important properties are \l name, \l description and \l role.
32
33 Example implementation of a simple button:
34 \qml
35 Rectangle {
36 id: myButton
37 Text {
38 id: label
39 text: "next"
40 }
41 Accessible.role: Accessible.Button
42 Accessible.name: label.text
43 Accessible.description: "shows the next page"
44 Accessible.onPressAction: {
45 // do a button click
46 }
47 }
48 \endqml
49 The \l role is set to \c Button to indicate the type of control.
50 \l {Accessible::}{name} is the most important information and bound to the text on the button.
51 The name is a short and consise description of the control and should reflect the visual label.
52 In this case it is not clear what the button does with the name only, so \l description contains
53 an explanation.
54 There is also a signal handler \l {Accessible::pressAction}{Accessible.pressAction} which can be invoked by assistive tools to trigger
55 the button. This signal handler needs to have the same effect as tapping or clicking the button would have.
56
57 \sa Accessibility
58*/
59
60/*!
61 \qmlproperty string QtQuick::Accessible::name
62
63 This property sets an accessible name.
64 For a button for example, this should have a binding to its text.
65 In general this property should be set to a simple and concise
66 but human readable name. Do not include the type of control
67 you want to represent but just the name.
68*/
69
70/*!
71 \qmlproperty string QtQuick::Accessible::description
72
73 This property sets an accessible description.
74 Similar to the name it describes the item. The description
75 can be a little more verbose and tell what the item does,
76 for example the functionality of the button it describes.
77*/
78
79/*!
80 \qmlproperty enumeration QtQuick::Accessible::role
81
82 This flags sets the semantic type of the widget.
83 A button for example would have "Button" as type.
84 The value must be one of \l QAccessible::Role.
85
86 Some roles have special semantics.
87 In order to implement check boxes for example a "checked" property is expected.
88
89 \table
90 \header
91 \li \b {Role}
92 \li \b {Properties and signals}
93 \li \b {Explanation}
94 \row
95 \li All interactive elements
96 \li \l focusable and \l focused
97 \li All elements that the user can interact with should have focusable set to \c true and
98 set \c focus to \c true when they have the focus. This is important even for applications
99 that run on touch-only devices since screen readers often implement a virtual focus that
100 can be moved from item to item.
101 \row
102 \li Button, CheckBox, RadioButton
103 \li \l {Accessible::pressAction}{Accessible.pressAction}
104 \li A button should have a signal handler with the name \c onPressAction.
105 This signal may be emitted by an assistive tool such as a screen-reader.
106 The implementation needs to behave the same as a mouse click or tap on the button.
107 \row
108 \li CheckBox, RadioButton
109 \li \l checkable, \l checked, \l {Accessible::toggleAction}{Accessible.toggleAction}
110
111 \li The check state of the check box. Updated on Press, Check and Uncheck actions.
112 \row
113 \li Slider, SpinBox, Dial, ScrollBar
114 \li \c value, \c minimumValue, \c maximumValue, \c stepSize
115 \li These properties reflect the state and possible values for the elements.
116 \row
117 \li Slider, SpinBox, Dial, ScrollBar
118 \li \l {Accessible::increaseAction}{Accessible.increaseAction}, \l {Accessible::decreaseAction}{Accessible.decreaseAction}
119 \li Actions to increase and decrease the value of the element.
120 \endtable
121*/
122
123/*! \qmlproperty bool QtQuick::Accessible::focusable
124 \brief This property holds whether this item is focusable.
125
126 By default, this property is \c false except for items where the role is one of
127 \c CheckBox, \c RadioButton, \c Button, \c MenuItem, \c PageTab, \c EditableText, \c SpinBox, \c ComboBox,
128 \c Terminal or \c ScrollBar.
129 \sa focused
130*/
131/*! \qmlproperty bool QtQuick::Accessible::focused
132 \brief This property holds whether this item currently has the active focus.
133
134 By default, this property is \c false, but it will return \c true for items that
135 have \l QQuickItem::hasActiveFocus() returning \c true.
136 \sa focusable
137*/
138/*! \qmlproperty bool QtQuick::Accessible::checkable
139 \brief This property holds whether this item is checkable (like a check box or some buttons).
140
141 By default this property is \c false.
142 \sa checked
143*/
144/*! \qmlproperty bool QtQuick::Accessible::checked
145 \brief This property holds whether this item is currently checked.
146
147 By default this property is \c false.
148 \sa checkable
149*/
150/*! \qmlproperty bool QtQuick::Accessible::editable
151 \brief This property holds whether this item has editable text.
152
153 By default this property is \c false.
154*/
155/*! \qmlproperty bool QtQuick::Accessible::searchEdit
156 \brief This property holds whether this item is input for a search query.
157 This property will only affect editable text.
158
159 By default this property is \c false.
160*/
161/*! \qmlproperty bool QtQuick::Accessible::ignored
162 \brief This property holds whether this item should be ignored by the accessibility framework.
163
164 Sometimes an item is part of a group of items that should be treated as one. For example two labels might be
165 visually placed next to each other, but separate items. For accessibility purposes they should be treated as one
166 and thus they are represented by a third invisible item with the right geometry.
167
168 For example a speed display adds "m/s" as a smaller label:
169 \qml
170 Row {
171 Label {
172 id: speedLabel
173 text: "Speed: 5"
174 Accessible.ignored: true
175 }
176 Label {
177 text: qsTr("m/s")
178 Accessible.ignored: true
179 }
180 Accessible.role: Accessible.StaticText
181 Accessible.name: speedLabel.text + " meters per second"
182 }
183 \endqml
184
185 \since 5.4
186 By default this property is \c false.
187*/
188/*! \qmlproperty bool QtQuick::Accessible::multiLine
189 \brief This property holds whether this item has multiple text lines.
190
191 By default this property is \c false.
192*/
193/*! \qmlproperty bool QtQuick::Accessible::readOnly
194 \brief This property indicates that a text field is read only.
195
196 It is relevant when the role is \l QAccessible::EditableText and set to be read-only.
197 By default this property is \c false.
198*/
199/*! \qmlproperty bool QtQuick::Accessible::selected
200 \brief This property holds whether this item is selected.
201
202 By default this property is \c false.
203 \sa selectable
204*/
205/*! \qmlproperty bool QtQuick::Accessible::selectable
206 \brief This property holds whether this item can be selected.
207
208 By default this property is \c false.
209 \sa selected
210*/
211/*! \qmlproperty bool QtQuick::Accessible::pressed
212 \brief This property holds whether this item is pressed (for example a button during a mouse click).
213
214 By default this property is \c false.
215*/
216/*! \qmlproperty bool QtQuick::Accessible::checkStateMixed
217 \brief This property holds whether this item is in the partially checked state.
218
219 By default this property is \c false.
220 \sa checked, checkable
221*/
222/*! \qmlproperty bool QtQuick::Accessible::defaultButton
223 \brief This property holds whether this item is the default button of a dialog.
224
225 By default this property is \c false.
226*/
227/*! \qmlproperty bool QtQuick::Accessible::passwordEdit
228 \brief This property holds whether this item is a password text edit.
229
230 By default this property is \c false.
231*/
232/*! \qmlproperty bool QtQuick::Accessible::selectableText
233 \brief This property holds whether this item contains selectable text.
234
235 By default this property is \c false.
236*/
237
238/*!
239 \qmlsignal QtQuick::Accessible::pressAction()
240
241 This signal is emitted when a press action is received from an assistive tool such as a screen-reader.
242*/
243/*!
244 \qmlsignal QtQuick::Accessible::toggleAction()
245
246 This signal is emitted when a toggle action is received from an assistive tool such as a screen-reader.
247*/
248/*!
249 \qmlsignal QtQuick::Accessible::increaseAction()
250
251 This signal is emitted when a increase action is received from an assistive tool such as a screen-reader.
252*/
253/*!
254 \qmlsignal QtQuick::Accessible::decreaseAction()
255
256 This signal is emitted when a decrease action is received from an assistive tool such as a screen-reader.
257*/
258/*!
259 \qmlsignal QtQuick::Accessible::scrollUpAction()
260
261 This signal is emitted when a scroll up action is received from an assistive tool such as a screen-reader.
262*/
263/*!
264 \qmlsignal QtQuick::Accessible::scrollDownAction()
265
266 This signal is emitted when a scroll down action is received from an assistive tool such as a screen-reader.
267*/
268/*!
269 \qmlsignal QtQuick::Accessible::scrollLeftAction()
270
271 This signal is emitted when a scroll left action is received from an assistive tool such as a screen-reader.
272*/
273/*!
274 \qmlsignal QtQuick::Accessible::scrollRightAction()
275
276 This signal is emitted when a scroll right action is received from an assistive tool such as a screen-reader.
277*/
278/*!
279 \qmlsignal QtQuick::Accessible::previousPageAction()
280
281 This signal is emitted when a previous page action is received from an assistive tool such as a screen-reader.
282*/
283/*!
284 \qmlsignal QtQuick::Accessible::nextPageAction()
285
286 This signal is emitted when a next page action is received from an assistive tool such as a screen-reader.
287*/
288
289QMetaMethod QQuickAccessibleAttached::sigPress;
290QMetaMethod QQuickAccessibleAttached::sigToggle;
291QMetaMethod QQuickAccessibleAttached::sigIncrease;
292QMetaMethod QQuickAccessibleAttached::sigDecrease;
293QMetaMethod QQuickAccessibleAttached::sigScrollUp;
294QMetaMethod QQuickAccessibleAttached::sigScrollDown;
295QMetaMethod QQuickAccessibleAttached::sigScrollLeft;
296QMetaMethod QQuickAccessibleAttached::sigScrollRight;
297QMetaMethod QQuickAccessibleAttached::sigPreviousPage;
298QMetaMethod QQuickAccessibleAttached::sigNextPage;
299
300QQuickAccessibleAttached::QQuickAccessibleAttached(QObject *parent)
301 : QObject(parent), m_role(QAccessible::NoRole)
302{
303 Q_ASSERT(parent);
304 if (!item()) {
305 qmlWarning(me: parent) << "Accessible must be attached to an Item";
306 return;
307 }
308
309 // Enable accessibility for items with accessible content. This also
310 // enables accessibility for the ancestors of souch items.
311 item()->d_func()->setAccessible();
312 QAccessibleEvent ev(item(), QAccessible::ObjectCreated);
313 QAccessible::updateAccessibility(event: &ev);
314
315 if (const QMetaObject *pmo = parent->metaObject()) {
316 auto connectPropertyChangeSignal = [parent, pmo, this](
317 const char *propertyName, const char *signalName, int slotIndex)
318 {
319 // basically does this:
320 // if the parent has the property \a propertyName with the associated \a signalName:
321 // connect(parent, signalName, this, slotIndex)
322
323 // Note that we explicitly want to only connect to standard property/signal naming
324 // convention: "value" & "valueChanged"
325 // (e.g. avoid a compound property with e.g. a signal notifier named "updated()")
326 int idxProperty = pmo->indexOfProperty(name: propertyName);
327 if (idxProperty != -1) {
328 const QMetaProperty property = pmo->property(index: idxProperty);
329 const QMetaMethod signal = property.notifySignal();
330 if (signal.name() == signalName)
331 QMetaObject::connect(sender: parent, signal_index: signal.methodIndex(), receiver: this, method_index: slotIndex);
332 }
333 return;
334 };
335 const QMetaObject &smo = staticMetaObject;
336 static const int valueChangedIndex = smo.indexOfSlot(slot: "valueChanged()");
337 connectPropertyChangeSignal("value", "valueChanged", valueChangedIndex);
338
339 static const int cursorPositionChangedIndex = smo.indexOfSlot(slot: "cursorPositionChanged()");
340 connectPropertyChangeSignal("cursorPosition", "cursorPositionChanged",
341 cursorPositionChangedIndex);
342 }
343
344 if (!sigPress.isValid()) {
345 sigPress = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::pressAction);
346 sigToggle = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::toggleAction);
347 sigIncrease = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::increaseAction);
348 sigDecrease = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::decreaseAction);
349 sigScrollUp = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::scrollUpAction);
350 sigScrollDown = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::scrollDownAction);
351 sigScrollLeft = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::scrollLeftAction);
352 sigScrollRight = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::scrollRightAction);
353 sigPreviousPage = QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::previousPageAction);
354 sigNextPage= QMetaMethod::fromSignal(signal: &QQuickAccessibleAttached::nextPageAction);
355 }
356}
357
358QQuickAccessibleAttached::~QQuickAccessibleAttached()
359{
360}
361
362void QQuickAccessibleAttached::setRole(QAccessible::Role role)
363{
364 if (role != m_role) {
365 m_role = role;
366 Q_EMIT roleChanged();
367 // There is no way to signify role changes at the moment.
368 // QAccessible::updateAccessibility(parent(), 0, QAccessible::);
369
370 switch (role) {
371 case QAccessible::CheckBox:
372 case QAccessible::RadioButton:
373 if (!m_stateExplicitlySet.focusable)
374 m_state.focusable = true;
375 if (!m_stateExplicitlySet.checkable)
376 m_state.checkable = true;
377 break;
378 case QAccessible::Button:
379 case QAccessible::MenuItem:
380 case QAccessible::PageTab:
381 case QAccessible::SpinBox:
382 case QAccessible::ComboBox:
383 case QAccessible::Terminal:
384 case QAccessible::ScrollBar:
385 if (!m_stateExplicitlySet.focusable)
386 m_state.focusable = true;
387 break;
388 case QAccessible::EditableText:
389 if (!m_stateExplicitlySet.editable)
390 m_state.editable = true;
391 if (!m_stateExplicitlySet.focusable)
392 m_state.focusable = true;
393 break;
394 case QAccessible::StaticText:
395 if (!m_stateExplicitlySet.readOnly)
396 m_state.readOnly = true;
397 if (!m_stateExplicitlySet.focusable)
398 m_state.focusable = true;
399 break;
400 default:
401 break;
402 }
403 }
404}
405
406bool QQuickAccessibleAttached::wasNameExplicitlySet() const
407{
408 return m_nameExplicitlySet;
409}
410
411// Allows types to attach an accessible name to an item as a convenience,
412// so long as the user hasn't done so themselves.
413void QQuickAccessibleAttached::setNameImplicitly(const QString &name)
414{
415 setName(name);
416 m_nameExplicitlySet = false;
417}
418
419QQuickAccessibleAttached *QQuickAccessibleAttached::qmlAttachedProperties(QObject *obj)
420{
421 return new QQuickAccessibleAttached(obj);
422}
423
424bool QQuickAccessibleAttached::ignored() const
425{
426 return item() ? !item()->d_func()->isAccessible : false;
427}
428
429void QQuickAccessibleAttached::setIgnored(bool ignored)
430{
431 if (this->ignored() != ignored && item()) {
432 item()->d_func()->isAccessible = !ignored;
433 emit ignoredChanged();
434 }
435}
436
437bool QQuickAccessibleAttached::doAction(const QString &actionName)
438{
439 QMetaMethod *sig = nullptr;
440 if (actionName == QAccessibleActionInterface::pressAction())
441 sig = &sigPress;
442 else if (actionName == QAccessibleActionInterface::toggleAction())
443 sig = &sigToggle;
444 else if (actionName == QAccessibleActionInterface::increaseAction())
445 sig = &sigIncrease;
446 else if (actionName == QAccessibleActionInterface::decreaseAction())
447 sig = &sigDecrease;
448 else if (actionName == QAccessibleActionInterface::scrollUpAction())
449 sig = &sigScrollUp;
450 else if (actionName == QAccessibleActionInterface::scrollDownAction())
451 sig = &sigScrollDown;
452 else if (actionName == QAccessibleActionInterface::scrollLeftAction())
453 sig = &sigScrollLeft;
454 else if (actionName == QAccessibleActionInterface::scrollRightAction())
455 sig = &sigScrollRight;
456 else if (actionName == QAccessibleActionInterface::previousPageAction())
457 sig = &sigPreviousPage;
458 else if (actionName == QAccessibleActionInterface::nextPageAction())
459 sig = &sigNextPage;
460 if (sig && isSignalConnected(signal: *sig))
461 return sig->invoke(obj: this);
462 return false;
463}
464
465void QQuickAccessibleAttached::availableActions(QStringList *actions) const
466{
467 if (isSignalConnected(signal: sigPress))
468 actions->append(t: QAccessibleActionInterface::pressAction());
469 if (isSignalConnected(signal: sigToggle))
470 actions->append(t: QAccessibleActionInterface::toggleAction());
471 if (isSignalConnected(signal: sigIncrease))
472 actions->append(t: QAccessibleActionInterface::increaseAction());
473 if (isSignalConnected(signal: sigDecrease))
474 actions->append(t: QAccessibleActionInterface::decreaseAction());
475 if (isSignalConnected(signal: sigScrollUp))
476 actions->append(t: QAccessibleActionInterface::scrollUpAction());
477 if (isSignalConnected(signal: sigScrollDown))
478 actions->append(t: QAccessibleActionInterface::scrollDownAction());
479 if (isSignalConnected(signal: sigScrollLeft))
480 actions->append(t: QAccessibleActionInterface::scrollLeftAction());
481 if (isSignalConnected(signal: sigScrollRight))
482 actions->append(t: QAccessibleActionInterface::scrollRightAction());
483 if (isSignalConnected(signal: sigPreviousPage))
484 actions->append(t: QAccessibleActionInterface::previousPageAction());
485 if (isSignalConnected(signal: sigNextPage))
486 actions->append(t: QAccessibleActionInterface::nextPageAction());
487}
488
489QString QQuickAccessibleAttached::stripHtml(const QString &html)
490{
491#ifndef QT_NO_TEXTHTMLPARSER
492 QTextDocument doc;
493 doc.setHtml(html);
494 return doc.toPlainText();
495#else
496 return html;
497#endif
498}
499
500QT_END_NAMESPACE
501
502#include "moc_qquickaccessibleattached_p.cpp"
503
504#endif
505

source code of qtdeclarative/src/quick/items/qquickaccessibleattached.cpp