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