5#include <AppKit/AppKit.h>
10#include <QtGui/qaccessible.h>
11#include <QtGui/private/qaccessiblebridgeutils_p.h>
12#include <QtCore/qmap.h>
13#include <private/qcore_mac_p.h>
17using namespace Qt::StringLiterals;
19#if QT_CONFIG(accessibility)
21QCocoaAccessibility::QCocoaAccessibility()
26QCocoaAccessibility::~QCocoaAccessibility()
31void QCocoaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
33 if (!isActive() || !event->accessibleInterface() || !event->accessibleInterface()->isValid())
35 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: event->uniqueId()];
37 qWarning(
"QCocoaAccessibility::notifyAccessibilityUpdate: invalid element");
41 switch (event->type()) {
42 case QAccessible::Announcement: {
43 auto *announcementEvent =
static_cast<QAccessibleAnnouncementEvent *>(event);
44 auto priorityLevel = (announcementEvent->politeness() == QAccessible::AnnouncementPoliteness::Assertive)
45 ? NSAccessibilityPriorityHigh
46 : NSAccessibilityPriorityMedium;
47 NSDictionary *announcementInfo = @{
48 NSAccessibilityPriorityKey: [NSNumber numberWithInt:priorityLevel],
49 NSAccessibilityAnnouncementKey: announcementEvent->message().toNSString()
54 NSAccessibilityPostNotificationWithUserInfo(NSApp,
55 NSAccessibilityAnnouncementRequestedNotification,
59 case QAccessible::Focus: {
60 NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification);
63 case QAccessible::PopupMenuStart:
64 NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification);
66 case QAccessible::StateChanged:
67 case QAccessible::ValueChanged:
68 case QAccessible::TextInserted:
69 case QAccessible::TextRemoved:
70 case QAccessible::TextUpdated:
71 NSAccessibilityPostNotification(element, NSAccessibilityValueChangedNotification);
73 case QAccessible::TextCaretMoved:
74 case QAccessible::TextSelectionChanged:
75 NSAccessibilityPostNotification(element, NSAccessibilitySelectedTextChangedNotification);
77 case QAccessible::NameChanged:
78 NSAccessibilityPostNotification(element, NSAccessibilityTitleChangedNotification);
80 case QAccessible::TableModelChanged:
82 [element updateTableModel];
89void QCocoaAccessibility::setRootObject(QObject *o)
94void QCocoaAccessibility::initialize()
99void QCocoaAccessibility::cleanup()
104namespace QCocoaAccessible {
106typedef QMap<QAccessible::Role, NSString *> QMacAccessibiltyRoleMap;
107Q_GLOBAL_STATIC(QMacAccessibiltyRoleMap, qMacAccessibiltyRoleMap);
109static void populateRoleMap()
111 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
112 roleMap[QAccessible::MenuItem] = NSAccessibilityMenuItemRole;
113 roleMap[QAccessible::MenuBar] = NSAccessibilityMenuBarRole;
114 roleMap[QAccessible::ScrollBar] = NSAccessibilityScrollBarRole;
115 roleMap[QAccessible::Grip] = NSAccessibilityGrowAreaRole;
116 roleMap[QAccessible::Window] = NSAccessibilityWindowRole;
117 roleMap[QAccessible::Dialog] = NSAccessibilityWindowRole;
118 roleMap[QAccessible::AlertMessage] = NSAccessibilityWindowRole;
119 roleMap[QAccessible::ToolTip] = NSAccessibilityWindowRole;
120 roleMap[QAccessible::HelpBalloon] = NSAccessibilityWindowRole;
121 roleMap[QAccessible::PopupMenu] = NSAccessibilityMenuRole;
122 roleMap[QAccessible::Application] = NSAccessibilityApplicationRole;
123 roleMap[QAccessible::Pane] = NSAccessibilityGroupRole;
124 roleMap[QAccessible::Grouping] = NSAccessibilityGroupRole;
125 roleMap[QAccessible::Separator] = NSAccessibilitySplitterRole;
126 roleMap[QAccessible::ToolBar] = NSAccessibilityToolbarRole;
127 roleMap[QAccessible::PageTab] = NSAccessibilityRadioButtonRole;
128 roleMap[QAccessible::PageTabList] = NSAccessibilityTabGroupRole;
129 roleMap[QAccessible::ButtonMenu] = NSAccessibilityMenuButtonRole;
130 roleMap[QAccessible::ButtonDropDown] = NSAccessibilityPopUpButtonRole;
131 roleMap[QAccessible::SpinBox] = NSAccessibilityIncrementorRole;
132 roleMap[QAccessible::Slider] = NSAccessibilitySliderRole;
133 roleMap[QAccessible::ProgressBar] = NSAccessibilityProgressIndicatorRole;
134 roleMap[QAccessible::ComboBox] = NSAccessibilityComboBoxRole;
135 roleMap[QAccessible::RadioButton] = NSAccessibilityRadioButtonRole;
136 roleMap[QAccessible::CheckBox] = NSAccessibilityCheckBoxRole;
137 roleMap[QAccessible::Switch] = NSAccessibilityCheckBoxRole;
138 roleMap[QAccessible::StaticText] = NSAccessibilityStaticTextRole;
139 roleMap[QAccessible::Table] = NSAccessibilityTableRole;
140 roleMap[QAccessible::StatusBar] = NSAccessibilityStaticTextRole;
141 roleMap[QAccessible::Column] = NSAccessibilityColumnRole;
142 roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole;
143 roleMap[QAccessible::Row] = NSAccessibilityRowRole;
144 roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole;
145 roleMap[QAccessible::Button] = NSAccessibilityButtonRole;
146 roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole;
147 roleMap[QAccessible::Link] = NSAccessibilityLinkRole;
148 roleMap[QAccessible::Indicator] = NSAccessibilityValueIndicatorRole;
149 roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole;
150 roleMap[QAccessible::List] = NSAccessibilityListRole;
151 roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole;
152 roleMap[QAccessible::Cell] = NSAccessibilityCellRole;
153 roleMap[QAccessible::Client] = NSAccessibilityGroupRole;
154 roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole;
155 roleMap[QAccessible::Section] = NSAccessibilityGroupRole;
156 roleMap[QAccessible::WebDocument] = NSAccessibilityGroupRole;
157 roleMap[QAccessible::ColorChooser] = NSAccessibilityColorWellRole;
158 roleMap[QAccessible::Footer] = NSAccessibilityGroupRole;
159 roleMap[QAccessible::Form] = NSAccessibilityGroupRole;
160 roleMap[QAccessible::Heading] = @
"AXHeading";
161 roleMap[QAccessible::Note] = NSAccessibilityGroupRole;
162 roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole;
163 roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
164 roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
165 roleMap[QAccessible::BlockQuote] = NSAccessibilityGroupRole;
166 roleMap[QAccessible::LayeredPane] = NSAccessibilityGroupRole;
170
171
172
173NSString *macRole(QAccessibleInterface *interface)
175 QAccessible::Role qtRole = interface->role();
176 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
178 if (roleMap.isEmpty())
183 if (roleMap.contains(qtRole)) {
185 if (roleMap[qtRole] == NSAccessibilityComboBoxRole && !interface->state().editable)
186 return NSAccessibilityMenuButtonRole;
187 if (roleMap[qtRole] == NSAccessibilityTextFieldRole && interface->state().multiLine)
188 return NSAccessibilityTextAreaRole;
189 if (roleMap[qtRole] == NSAccessibilityButtonRole && interface->state().expandable)
190 return NSAccessibilityDisclosureTriangleRole;
191 return roleMap[qtRole];
198 return NSAccessibilityGroupRole;
202
203
204NSString *macSubrole(QAccessibleInterface *interface)
206 QAccessible::State s = interface->state();
208 return NSAccessibilitySearchFieldSubrole;
210 return NSAccessibilitySecureTextFieldSubrole;
211 if (interface->role() == QAccessible::PageTab)
212 return NSAccessibilityTabButtonSubrole;
213 if (interface->role() == QAccessible::Switch)
214 return NSAccessibilitySwitchSubrole;
219
220
221
222
223bool shouldBeIgnored(QAccessibleInterface *interface)
227 const QAccessible::State state = interface->state();
228 if (state.invisible || state.offscreen || state.invalid)
233 switch (interface->role()) {
234 case QAccessible::Border:
235 case QAccessible::Application:
236 case QAccessible::ToolBar:
237 case QAccessible::Pane:
238 case QAccessible::Client:
239 case QAccessible::PopupMenu:
245 NSString *mac_role = macRole(interface);
246 if (mac_role == NSAccessibilityWindowRole ||
247 mac_role == NSAccessibilityUnknownRole) {
251 if (
const QObject *object = interface->object()) {
252 const QByteArrayView className = object->metaObject()->className();
257 if (className ==
"QTipLabel"_ba)
264bool defaultUnignored(QAccessibleInterface *child)
266 if (child && child->isValid()) {
267 const auto state = child->state();
268 return !state.invalid && !state.invisible;
274
275
276
277
278
279
280bool isRepresentedByAppKit(QAccessibleInterface *interface)
282 const QAccessible::Role role = interface->role();
283 return role == QAccessible::Application || role == QAccessible::Window;
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304NSView *accessibleViewFor(QWindow *window)
307 const bool disable = qEnvironmentVariableIsSet(
"QT_MAC_A11Y_NO_SPLIT_ON_VIEW_BOUNDARY");
311 auto *platformWindow =
static_cast<QCocoaWindow *>(window->handle());
314 if (platformWindow->isForeignWindow())
315 platformWindow->updateAccessibleParent();
316 return platformWindow->view();
319NSArray<id> *unignoredChildren(QAccessibleInterface *interface,
320 const std::function<
bool(QAccessibleInterface *child)> &pred)
322 QWindow *window = QAccessibleBridgeUtils::windowFor(interface);
324 int numKids = interface->childCount();
325 NSMutableArray<id> *kids = [NSMutableArray<id> arrayWithCapacity:numKids];
326 for (
int i = 0; i < numKids; ++i) {
327 QAccessibleInterface *child = interface->child(i);
334 QWindow *childWindow = QAccessibleBridgeUtils::windowFor(child, interface, window);
335 if (childWindow != window) {
336 if (
auto *view = QCocoaAccessible::accessibleViewFor(childWindow)) {
337 [kids addObject:view];
342 QAccessible::Id childId = QAccessible::uniqueId(child);
344 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: childId];
346 [kids addObject: element];
348 qWarning(
"QCocoaAccessibility: invalid child");
350 return NSAccessibilityUnignoredChildren(kids);
354
355
356
357
358NSString *getTranslatedAction(
const QString &qtAction)
360 if (qtAction == QAccessibleActionInterface::pressAction())
361 return NSAccessibilityPressAction;
362 else if (qtAction == QAccessibleActionInterface::increaseAction())
363 return NSAccessibilityIncrementAction;
364 else if (qtAction == QAccessibleActionInterface::decreaseAction())
365 return NSAccessibilityDecrementAction;
366 else if (qtAction == QAccessibleActionInterface::showMenuAction())
367 return NSAccessibilityShowMenuAction;
368 else if (qtAction == QAccessibleActionInterface::setFocusAction())
369 return NSAccessibilityRaiseAction;
370 else if (qtAction == QAccessibleActionInterface::toggleAction())
371 return NSAccessibilityPressAction;
390
391
392
393QString translateAction(NSString *nsAction, QAccessibleInterface *interface)
395 if ([nsAction compare: NSAccessibilityPressAction] == NSOrderedSame) {
396 if (interface->role() == QAccessible::CheckBox
397 || interface->role() == QAccessible::RadioButton
398 || interface->role() == QAccessible::Switch) {
399 return QAccessibleActionInterface::toggleAction();
401 return QAccessibleActionInterface::pressAction();
402 }
else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame)
403 return QAccessibleActionInterface::increaseAction();
404 else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame)
405 return QAccessibleActionInterface::decreaseAction();
406 else if ([nsAction compare: NSAccessibilityShowMenuAction] == NSOrderedSame)
407 return QAccessibleActionInterface::showMenuAction();
408 else if ([nsAction compare: NSAccessibilityRaiseAction] == NSOrderedSame)
409 return QAccessibleActionInterface::setFocusAction();
416bool hasValueAttribute(QAccessibleInterface *interface)
419 const QAccessible::Role qtrole = interface->role();
420 if (qtrole == QAccessible::EditableText
421 || qtrole == QAccessible::StaticText
422 || interface->valueInterface()
423 || interface->state().checkable
424 || macRole(interface) == NSAccessibilityDisclosureTriangleRole) {
431id getValueAttribute(QAccessibleInterface *interface)
433 const QAccessible::Role qtrole = interface->role();
434 if (qtrole == QAccessible::StaticText) {
435 return interface->text(QAccessible::Name).toNSString();
437 if (qtrole == QAccessible::EditableText) {
438 if (QAccessibleTextInterface *textInterface = interface->textInterface()) {
441 int end = textInterface->characterCount();
443 if (interface->state().passwordEdit) {
445 text = QString(end, QChar(0x2022));
454 text = textInterface->text(begin, end);
456 return text.toNSString();
460 if (QAccessibleValueInterface *valueInterface = interface->valueInterface()) {
461 return valueInterface->currentValue().toString().toNSString();
464 if (interface->state().checkable) {
465 if (interface->state().checkStateMixed)
467 return interface->state().checked ? @(1) : @(0);
470 if (macRole(interface) == NSAccessibilityDisclosureTriangleRole) {
471 return interface->state().expanded ? @(1) : @(0);
Combined button and popup list for selecting options.