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;
134 roleMap[QAccessible::Dial] = NSAccessibilitySliderRole;
135 roleMap[QAccessible::ProgressBar] = NSAccessibilityProgressIndicatorRole;
136 roleMap[QAccessible::ComboBox] = NSAccessibilityComboBoxRole;
137 roleMap[QAccessible::RadioButton] = NSAccessibilityRadioButtonRole;
138 roleMap[QAccessible::CheckBox] = NSAccessibilityCheckBoxRole;
139 roleMap[QAccessible::Switch] = NSAccessibilityCheckBoxRole;
140 roleMap[QAccessible::StaticText] = NSAccessibilityStaticTextRole;
141 roleMap[QAccessible::Table] = NSAccessibilityTableRole;
142 roleMap[QAccessible::StatusBar] = NSAccessibilityStaticTextRole;
143 roleMap[QAccessible::Column] = NSAccessibilityColumnRole;
144 roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole;
145 roleMap[QAccessible::Row] = NSAccessibilityRowRole;
146 roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole;
147 roleMap[QAccessible::Button] = NSAccessibilityButtonRole;
148 roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole;
149 roleMap[QAccessible::Link] = NSAccessibilityLinkRole;
150 roleMap[QAccessible::Indicator] = NSAccessibilityValueIndicatorRole;
151 roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole;
152 roleMap[QAccessible::List] = NSAccessibilityListRole;
153 roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole;
154 roleMap[QAccessible::Cell] = NSAccessibilityCellRole;
155 roleMap[QAccessible::Client] = NSAccessibilityGroupRole;
156 roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole;
157 roleMap[QAccessible::Section] = NSAccessibilityGroupRole;
158 roleMap[QAccessible::WebDocument] = NSAccessibilityGroupRole;
159 roleMap[QAccessible::ColorChooser] = NSAccessibilityColorWellRole;
160 roleMap[QAccessible::Footer] = NSAccessibilityGroupRole;
161 roleMap[QAccessible::Form] = NSAccessibilityGroupRole;
162 roleMap[QAccessible::Heading] = @
"AXHeading";
163 roleMap[QAccessible::Note] = NSAccessibilityGroupRole;
164 roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole;
165 roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
166 roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
167 roleMap[QAccessible::BlockQuote] = NSAccessibilityGroupRole;
168 roleMap[QAccessible::LayeredPane] = NSAccessibilityGroupRole;
172
173
174
175NSString *macRole(QAccessibleInterface *interface)
177 QAccessible::Role qtRole = interface->role();
178 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
180 if (roleMap.isEmpty())
185 if (roleMap.contains(qtRole)) {
187 if (roleMap[qtRole] == NSAccessibilityComboBoxRole && !interface->state().editable)
188 return NSAccessibilityMenuButtonRole;
189 if (roleMap[qtRole] == NSAccessibilityTextFieldRole && interface->state().multiLine)
190 return NSAccessibilityTextAreaRole;
191 if (roleMap[qtRole] == NSAccessibilityButtonRole && interface->state().expandable)
192 return NSAccessibilityDisclosureTriangleRole;
193 return roleMap[qtRole];
200 return NSAccessibilityGroupRole;
204
205
206NSString *macSubrole(QAccessibleInterface *interface)
208 QAccessible::State s = interface->state();
210 return NSAccessibilitySearchFieldSubrole;
212 return NSAccessibilitySecureTextFieldSubrole;
213 if (interface->role() == QAccessible::PageTab)
214 return NSAccessibilityTabButtonSubrole;
215 if (interface->role() == QAccessible::Switch)
216 return NSAccessibilitySwitchSubrole;
221
222
223
224
225bool shouldBeIgnored(QAccessibleInterface *interface)
229 const QAccessible::State state = interface->state();
230 if (state.invisible || state.offscreen || state.invalid)
235 switch (interface->role()) {
236 case QAccessible::Border:
237 case QAccessible::Application:
238 case QAccessible::ToolBar:
239 case QAccessible::Pane:
240 case QAccessible::Client:
241 case QAccessible::PopupMenu:
247 NSString *mac_role = macRole(interface);
248 if (mac_role == NSAccessibilityWindowRole ||
249 mac_role == NSAccessibilityUnknownRole) {
253 if (
const QObject *object = interface->object()) {
254 const QByteArrayView className = object->metaObject()->className();
259 if (className ==
"QTipLabel"_ba)
266bool defaultUnignored(QAccessibleInterface *child)
268 if (child && child->isValid()) {
269 const auto state = child->state();
270 return !state.invalid && !state.invisible;
276
277
278
279
280
281
282bool isRepresentedByAppKit(QAccessibleInterface *interface)
284 const QAccessible::Role role = interface->role();
285 return role == QAccessible::Application || role == QAccessible::Window;
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306NSView *accessibleViewFor(QWindow *window)
309 const bool disable = qEnvironmentVariableIsSet(
"QT_MAC_A11Y_NO_SPLIT_ON_VIEW_BOUNDARY");
313 auto *platformWindow =
static_cast<QCocoaWindow *>(window->handle());
316 if (platformWindow->isForeignWindow())
317 platformWindow->updateAccessibleParent();
318 return platformWindow->view();
321NSArray<id> *unignoredChildren(QAccessibleInterface *interface,
322 const std::function<
bool(QAccessibleInterface *child)> &pred)
324 QWindow *window = QAccessibleBridgeUtils::windowFor(interface);
326 int numKids = interface->childCount();
327 NSMutableArray<id> *kids = [NSMutableArray<id> arrayWithCapacity:numKids];
328 for (
int i = 0; i < numKids; ++i) {
329 QAccessibleInterface *child = interface->child(i);
336 QWindow *childWindow = QAccessibleBridgeUtils::windowFor(child, interface, window);
337 if (childWindow != window) {
338 if (
auto *view = QCocoaAccessible::accessibleViewFor(childWindow)) {
339 [kids addObject:view];
344 QAccessible::Id childId = QAccessible::uniqueId(child);
346 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: childId];
348 [kids addObject: element];
350 qWarning(
"QCocoaAccessibility: invalid child");
352 return NSAccessibilityUnignoredChildren(kids);
356
357
358
359
360NSString *getTranslatedAction(
const QString &qtAction)
362 if (qtAction == QAccessibleActionInterface::pressAction())
363 return NSAccessibilityPressAction;
364 else if (qtAction == QAccessibleActionInterface::increaseAction())
365 return NSAccessibilityIncrementAction;
366 else if (qtAction == QAccessibleActionInterface::decreaseAction())
367 return NSAccessibilityDecrementAction;
368 else if (qtAction == QAccessibleActionInterface::showMenuAction())
369 return NSAccessibilityShowMenuAction;
370 else if (qtAction == QAccessibleActionInterface::setFocusAction())
371 return NSAccessibilityRaiseAction;
372 else if (qtAction == QAccessibleActionInterface::toggleAction())
373 return NSAccessibilityPressAction;
392
393
394
395QString translateAction(NSString *nsAction, QAccessibleInterface *interface)
397 if ([nsAction compare: NSAccessibilityPressAction] == NSOrderedSame) {
398 if (interface->role() == QAccessible::CheckBox
399 || interface->role() == QAccessible::RadioButton
400 || interface->role() == QAccessible::Switch) {
401 return QAccessibleActionInterface::toggleAction();
403 return QAccessibleActionInterface::pressAction();
404 }
else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame)
405 return QAccessibleActionInterface::increaseAction();
406 else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame)
407 return QAccessibleActionInterface::decreaseAction();
408 else if ([nsAction compare: NSAccessibilityShowMenuAction] == NSOrderedSame)
409 return QAccessibleActionInterface::showMenuAction();
410 else if ([nsAction compare: NSAccessibilityRaiseAction] == NSOrderedSame)
411 return QAccessibleActionInterface::setFocusAction();
418bool hasValueAttribute(QAccessibleInterface *interface)
421 const QAccessible::Role qtrole = interface->role();
422 if (qtrole == QAccessible::EditableText
423 || qtrole == QAccessible::StaticText
424 || interface->valueInterface()
425 || interface->state().checkable
426 || macRole(interface) == NSAccessibilityDisclosureTriangleRole) {
433id getValueAttribute(QAccessibleInterface *interface)
435 const QAccessible::Role qtrole = interface->role();
436 if (qtrole == QAccessible::StaticText) {
437 return interface->text(QAccessible::Name).toNSString();
439 if (qtrole == QAccessible::EditableText) {
440 if (QAccessibleTextInterface *textInterface = interface->textInterface()) {
443 int end = textInterface->characterCount();
445 if (interface->state().passwordEdit) {
447 text = QString(end, QChar(0x2022));
456 text = textInterface->text(begin, end);
458 return text.toNSString();
462 if (QAccessibleValueInterface *valueInterface = interface->valueInterface()) {
463 return valueInterface->currentValue().toString().toNSString();
466 if (interface->state().checkable) {
467 if (interface->state().checkStateMixed)
469 return interface->state().checked ? @(1) : @(0);
472 if (macRole(interface) == NSAccessibilityDisclosureTriangleRole) {
473 return interface->state().expanded ? @(1) : @(0);
Combined button and popup list for selecting options.