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