Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qcocoaaccessibility.mm
Go to the documentation of this file.
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 <AppKit/AppKit.h>
5
8#include <QtGui/qaccessible.h>
9#include <QtCore/qmap.h>
10#include <private/qcore_mac_p.h>
11
13
14using namespace Qt::StringLiterals;
15
16#if QT_CONFIG(accessibility)
17
18QCocoaAccessibility::QCocoaAccessibility()
19{
20
21}
22
23QCocoaAccessibility::~QCocoaAccessibility()
24{
25
26}
27
28void QCocoaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
29{
30 if (!isActive() || !event->accessibleInterface() || !event->accessibleInterface()->isValid())
31 return;
32 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: event->uniqueId()];
33 if (!element) {
34 qWarning("QCocoaAccessibility::notifyAccessibilityUpdate: invalid element");
35 return;
36 }
37
38 switch (event->type()) {
39 case QAccessible::Focus: {
40 NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification);
41 break;
42 }
43 case QAccessible::StateChanged:
44 case QAccessible::ValueChanged:
45 case QAccessible::TextInserted:
46 case QAccessible::TextRemoved:
47 case QAccessible::TextUpdated:
48 NSAccessibilityPostNotification(element, NSAccessibilityValueChangedNotification);
49 break;
50 case QAccessible::TextCaretMoved:
51 case QAccessible::TextSelectionChanged:
52 NSAccessibilityPostNotification(element, NSAccessibilitySelectedTextChangedNotification);
53 break;
54 case QAccessible::NameChanged:
55 NSAccessibilityPostNotification(element, NSAccessibilityTitleChangedNotification);
56 break;
57 case QAccessible::TableModelChanged:
58 // ### Could NSAccessibilityRowCountChangedNotification be relevant here?
59 [element updateTableModel];
60 break;
61 default:
62 break;
63 }
64}
65
66void QCocoaAccessibility::setRootObject(QObject *o)
67{
68 Q_UNUSED(o);
69}
70
71void QCocoaAccessibility::initialize()
72{
73
74}
75
76void QCocoaAccessibility::cleanup()
77{
78
79}
80
81namespace QCocoaAccessible {
82
83typedef QMap<QAccessible::Role, NSString *> QMacAccessibiltyRoleMap;
84Q_GLOBAL_STATIC(QMacAccessibiltyRoleMap, qMacAccessibiltyRoleMap);
85
86static void populateRoleMap()
87{
88 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
89 roleMap[QAccessible::MenuItem] = NSAccessibilityMenuItemRole;
90 roleMap[QAccessible::MenuBar] = NSAccessibilityMenuBarRole;
91 roleMap[QAccessible::ScrollBar] = NSAccessibilityScrollBarRole;
92 roleMap[QAccessible::Grip] = NSAccessibilityGrowAreaRole;
93 roleMap[QAccessible::Window] = NSAccessibilityWindowRole;
94 roleMap[QAccessible::Dialog] = NSAccessibilityWindowRole;
95 roleMap[QAccessible::AlertMessage] = NSAccessibilityWindowRole;
96 roleMap[QAccessible::ToolTip] = NSAccessibilityWindowRole;
97 roleMap[QAccessible::HelpBalloon] = NSAccessibilityWindowRole;
98 roleMap[QAccessible::PopupMenu] = NSAccessibilityMenuRole;
99 roleMap[QAccessible::Application] = NSAccessibilityApplicationRole;
100 roleMap[QAccessible::Pane] = NSAccessibilityGroupRole;
101 roleMap[QAccessible::Grouping] = NSAccessibilityGroupRole;
102 roleMap[QAccessible::Separator] = NSAccessibilitySplitterRole;
103 roleMap[QAccessible::ToolBar] = NSAccessibilityToolbarRole;
104 roleMap[QAccessible::PageTab] = NSAccessibilityRadioButtonRole;
105 roleMap[QAccessible::ButtonMenu] = NSAccessibilityMenuButtonRole;
106 roleMap[QAccessible::ButtonDropDown] = NSAccessibilityPopUpButtonRole;
107 roleMap[QAccessible::SpinBox] = NSAccessibilityIncrementorRole;
108 roleMap[QAccessible::Slider] = NSAccessibilitySliderRole;
109 roleMap[QAccessible::ProgressBar] = NSAccessibilityProgressIndicatorRole;
110 roleMap[QAccessible::ComboBox] = NSAccessibilityComboBoxRole;
111 roleMap[QAccessible::RadioButton] = NSAccessibilityRadioButtonRole;
112 roleMap[QAccessible::CheckBox] = NSAccessibilityCheckBoxRole;
113 roleMap[QAccessible::StaticText] = NSAccessibilityStaticTextRole;
114 roleMap[QAccessible::Table] = NSAccessibilityTableRole;
115 roleMap[QAccessible::StatusBar] = NSAccessibilityStaticTextRole;
116 roleMap[QAccessible::Column] = NSAccessibilityColumnRole;
117 roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole;
118 roleMap[QAccessible::Row] = NSAccessibilityRowRole;
119 roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole;
120 roleMap[QAccessible::Button] = NSAccessibilityButtonRole;
121 roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole;
122 roleMap[QAccessible::Link] = NSAccessibilityLinkRole;
123 roleMap[QAccessible::Indicator] = NSAccessibilityValueIndicatorRole;
124 roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole;
125 roleMap[QAccessible::List] = NSAccessibilityListRole;
126 roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole;
127 roleMap[QAccessible::Cell] = NSAccessibilityCellRole;
128 roleMap[QAccessible::Client] = NSAccessibilityGroupRole;
129 roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole;
130 roleMap[QAccessible::Section] = NSAccessibilityGroupRole;
131 roleMap[QAccessible::WebDocument] = NSAccessibilityGroupRole;
132 roleMap[QAccessible::ColorChooser] = NSAccessibilityColorWellRole;
133 roleMap[QAccessible::Footer] = NSAccessibilityGroupRole;
134 roleMap[QAccessible::Form] = NSAccessibilityGroupRole;
135 roleMap[QAccessible::Heading] = @"AXHeading";
136 roleMap[QAccessible::Note] = NSAccessibilityGroupRole;
137 roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole;
138 roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
139 roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
140}
141
142/*
143 Returns a Cocoa accessibility role for the given interface, or
144 NSAccessibilityUnknownRole if no role mapping is found.
145*/
146NSString *macRole(QAccessibleInterface *interface)
147{
148 QAccessible::Role qtRole = interface->role();
149 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
150
151 if (roleMap.isEmpty())
152 populateRoleMap();
153
154 // MAC_ACCESSIBILTY_DEBUG() << "role for" << interface.object() << "interface role" << Qt::hex << qtRole;
155
156 if (roleMap.contains(qtRole)) {
157 // MAC_ACCESSIBILTY_DEBUG() << "return" << roleMap[qtRole];
158 if (roleMap[qtRole] == NSAccessibilityComboBoxRole && !interface->state().editable)
159 return NSAccessibilityMenuButtonRole;
160 if (roleMap[qtRole] == NSAccessibilityTextFieldRole && interface->state().multiLine)
161 return NSAccessibilityTextAreaRole;
162 return roleMap[qtRole];
163 }
164
165 // Treat unknown Qt roles as generic group container items. Returning
166 // NSAccessibilityUnknownRole is also possible but makes the screen
167 // reader focus on the item instead of passing focus to child items.
168 // MAC_ACCESSIBILTY_DEBUG() << "return NSAccessibilityGroupRole for unknown Qt role";
169 return NSAccessibilityGroupRole;
170}
171
172/*
173 Returns a Cocoa sub role for the given interface.
174*/
175NSString *macSubrole(QAccessibleInterface *interface)
176{
177 QAccessible::State s = interface->state();
178 if (s.searchEdit)
179 return NSAccessibilitySearchFieldSubrole;
180 if (s.passwordEdit)
181 return NSAccessibilitySecureTextFieldSubrole;
182 return nil;
183}
184
185/*
186 Cocoa accessibility supports ignoring elements, which means that
187 the elements are still present in the accessibility tree but is
188 not used by the screen reader.
189*/
190bool shouldBeIgnored(QAccessibleInterface *interface)
191{
192 // Cocoa accessibility does not have an attribute that corresponds to the Invisible/Offscreen
193 // state. Ignore interfaces with those flags set.
194 const QAccessible::State state = interface->state();
195 if (state.invisible ||
196 state.offscreen ||
197 state.invalid)
198 return true;
199
200 // Some roles are not interesting. In particular, container roles should be
201 // ignored in order to flatten the accessibility tree as seen by the user.
202 const QAccessible::Role role = interface->role();
203 if (role == QAccessible::Border || // QFrame
204 role == QAccessible::Application || // We use the system-provided application element.
205 role == QAccessible::ToolBar || // Access the tool buttons directly.
206 role == QAccessible::Pane || // Scroll areas.
207 role == QAccessible::Client) // The default for QWidget.
208 return true;
209
210 NSString *mac_role = macRole(interface);
211 if (mac_role == NSAccessibilityWindowRole || // We use the system-provided window elements.
212 mac_role == NSAccessibilityUnknownRole)
213 return true;
214
215 // Client is a generic role returned by plain QWidgets or other
216 // widgets that does not have separate QAccessible interface, such
217 // as the TabWidget. Return false unless macRole gives the interface
218 // a special role.
219 if (role == QAccessible::Client && mac_role == NSAccessibilityUnknownRole)
220 return true;
221
222 if (QObject * const object = interface->object()) {
223 const QString className = QLatin1StringView(object->metaObject()->className());
224
225 // VoiceOver focusing on tool tips can be confusing. The contents of the
226 // tool tip is available through the description attribute anyway, so
227 // we disable accessibility for tool tips.
228 if (className == "QTipLabel"_L1)
229 return true;
230 }
231
232 return false;
233}
234
235NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface)
236{
237 int numKids = interface->childCount();
238 // qDebug() << "Children for: " << axid << iface << " are: " << numKids;
239
240 NSMutableArray<QMacAccessibilityElement *> *kids = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numKids];
241 for (int i = 0; i < numKids; ++i) {
242 QAccessibleInterface *child = interface->child(i);
243 if (!child || !child->isValid() || child->state().invalid || child->state().invisible)
244 continue;
245
246 QAccessible::Id childId = QAccessible::uniqueId(child);
247 //qDebug() << " kid: " << childId << child;
248
249 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: childId];
250 if (element)
251 [kids addObject: element];
252 else
253 qWarning("QCocoaAccessibility: invalid child");
254 }
255 return NSAccessibilityUnignoredChildren(kids);
256}
257/*
258 Translates a predefined QAccessibleActionInterface action to a Mac action constant.
259 Returns 0 if the Qt Action has no mac equivalent. Ownership of the NSString is
260 not transferred.
261*/
262NSString *getTranslatedAction(const QString &qtAction)
263{
264 if (qtAction == QAccessibleActionInterface::pressAction())
265 return NSAccessibilityPressAction;
266 else if (qtAction == QAccessibleActionInterface::increaseAction())
267 return NSAccessibilityIncrementAction;
268 else if (qtAction == QAccessibleActionInterface::decreaseAction())
269 return NSAccessibilityDecrementAction;
270 else if (qtAction == QAccessibleActionInterface::showMenuAction())
271 return NSAccessibilityShowMenuAction;
272 else if (qtAction == QAccessibleActionInterface::setFocusAction()) // Not 100% sure on this one
273 return NSAccessibilityRaiseAction;
274 else if (qtAction == QAccessibleActionInterface::toggleAction())
275 return NSAccessibilityPressAction;
276
277 // Not translated:
278 //
279 // Qt:
280 // static const QString &checkAction();
281 // static const QString &uncheckAction();
282 //
283 // Cocoa:
284 // NSAccessibilityConfirmAction;
285 // NSAccessibilityPickAction;
286 // NSAccessibilityCancelAction;
287 // NSAccessibilityDeleteAction;
288
289 return nil;
290}
291
292
293/*
294 Translates between a Mac action constant and a QAccessibleActionInterface action
295 Returns an empty QString if there is no Qt predefined equivalent.
296*/
297QString translateAction(NSString *nsAction, QAccessibleInterface *interface)
298{
299 if ([nsAction compare: NSAccessibilityPressAction] == NSOrderedSame) {
300 if (interface->role() == QAccessible::CheckBox || interface->role() == QAccessible::RadioButton)
301 return QAccessibleActionInterface::toggleAction();
302 return QAccessibleActionInterface::pressAction();
303 } else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame)
304 return QAccessibleActionInterface::increaseAction();
305 else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame)
306 return QAccessibleActionInterface::decreaseAction();
307 else if ([nsAction compare: NSAccessibilityShowMenuAction] == NSOrderedSame)
308 return QAccessibleActionInterface::showMenuAction();
309 else if ([nsAction compare: NSAccessibilityRaiseAction] == NSOrderedSame)
310 return QAccessibleActionInterface::setFocusAction();
311
312 // See getTranslatedAction for not matched translations.
313
314 return QString();
315}
316
317bool hasValueAttribute(QAccessibleInterface *interface)
318{
320 const QAccessible::Role qtrole = interface->role();
321 if (qtrole == QAccessible::EditableText
322 || qtrole == QAccessible::StaticText
323 || interface->valueInterface()
324 || interface->state().checkable) {
325 return true;
326 }
327
328 return false;
329}
330
331id getValueAttribute(QAccessibleInterface *interface)
332{
333 const QAccessible::Role qtrole = interface->role();
334 if (qtrole == QAccessible::StaticText) {
335 return interface->text(QAccessible::Name).toNSString();
336 }
337 if (qtrole == QAccessible::EditableText) {
338 if (QAccessibleTextInterface *textInterface = interface->textInterface()) {
339
340 int begin = 0;
341 int end = textInterface->characterCount();
343 if (interface->state().passwordEdit) {
344 // return round password replacement chars
345 text = QString(end, QChar(0x2022));
346 } else {
347 // VoiceOver will read out the entire text string at once when returning
348 // text as a value. For large text edits the size of the returned string
349 // needs to be limited and text range attributes need to be used instead.
350 // NSTextEdit returns the first sentence as the value, Do the same here:
351 // ### call to textAfterOffset hangs. Booo!
352 //if (textInterface->characterCount() > 0)
353 // textInterface->textAfterOffset(0, QAccessible2::SentenceBoundary, &begin, &end);
354 text = textInterface->text(begin, end);
355 }
356 return text.toNSString();
357 }
358 }
359
360 if (QAccessibleValueInterface *valueInterface = interface->valueInterface()) {
361 return valueInterface->currentValue().toString().toNSString();
362 }
363
364 if (interface->state().checkable) {
365 if (interface->state().checkStateMixed)
366 return @(2);
367 return interface->state().checked ? @(1) : @(0);
368 }
369
370 return nil;
371}
372
373} // namespace QCocoaAccessible
374
375#endif // QT_CONFIG(accessibility)
376
378
bool isActive
\inmodule QtGui
\inmodule QtCore
Definition qchar.h:48
Definition qmap.h:186
\inmodule QtCore
Definition qobject.h:90
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString text
else opt state
[0]
Combined button and popup list for selecting options.
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char * interface
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition qlogging.h:162
GLuint GLuint end
GLuint object
[3]
struct _cl_event * event
GLdouble s
[6]
Definition qopenglext.h:235
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
#define Q_UNUSED(x)
static int compare(quint64 a, quint64 b)
const char className[16]
[1]
Definition qwizard.cpp:100
QLayoutItem * child
[0]