5#include <AppKit/AppKit.h>
9#include <QtGui/qaccessible.h>
10#include <QtCore/qmap.h>
11#include <private/qcore_mac_p.h>
15using namespace Qt::StringLiterals;
17#if QT_CONFIG(accessibility)
19QCocoaAccessibility::QCocoaAccessibility()
24QCocoaAccessibility::~QCocoaAccessibility()
29void QCocoaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
31 if (!isActive() || !event->accessibleInterface() || !event->accessibleInterface()->isValid())
33 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: event->uniqueId()];
35 qWarning(
"QCocoaAccessibility::notifyAccessibilityUpdate: invalid element");
39 switch (event->type()) {
40 case QAccessible::Announcement: {
41 auto *announcementEvent =
static_cast<QAccessibleAnnouncementEvent *>(event);
42 auto priorityLevel = (announcementEvent->politeness() == QAccessible::AnnouncementPoliteness::Assertive)
43 ? NSAccessibilityPriorityHigh
44 : NSAccessibilityPriorityMedium;
45 NSDictionary *announcementInfo = @{
46 NSAccessibilityPriorityKey: [NSNumber numberWithInt:priorityLevel],
47 NSAccessibilityAnnouncementKey: announcementEvent->message().toNSString()
52 NSAccessibilityPostNotificationWithUserInfo(NSApp,
53 NSAccessibilityAnnouncementRequestedNotification,
57 case QAccessible::Focus: {
58 NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification);
61 case QAccessible::PopupMenuStart:
62 NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification);
64 case QAccessible::StateChanged:
65 case QAccessible::ValueChanged:
66 case QAccessible::TextInserted:
67 case QAccessible::TextRemoved:
68 case QAccessible::TextUpdated:
69 NSAccessibilityPostNotification(element, NSAccessibilityValueChangedNotification);
71 case QAccessible::TextCaretMoved:
72 case QAccessible::TextSelectionChanged:
73 NSAccessibilityPostNotification(element, NSAccessibilitySelectedTextChangedNotification);
75 case QAccessible::NameChanged:
76 NSAccessibilityPostNotification(element, NSAccessibilityTitleChangedNotification);
78 case QAccessible::TableModelChanged:
80 [element updateTableModel];
87void QCocoaAccessibility::setRootObject(QObject *o)
92void QCocoaAccessibility::initialize()
97void QCocoaAccessibility::cleanup()
102namespace QCocoaAccessible {
104typedef QMap<QAccessible::Role, NSString *> QMacAccessibiltyRoleMap;
105Q_GLOBAL_STATIC(QMacAccessibiltyRoleMap, qMacAccessibiltyRoleMap);
107static void populateRoleMap()
109 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
110 roleMap[QAccessible::MenuItem] = NSAccessibilityMenuItemRole;
111 roleMap[QAccessible::MenuBar] = NSAccessibilityMenuBarRole;
112 roleMap[QAccessible::ScrollBar] = NSAccessibilityScrollBarRole;
113 roleMap[QAccessible::Grip] = NSAccessibilityGrowAreaRole;
114 roleMap[QAccessible::Window] = NSAccessibilityWindowRole;
115 roleMap[QAccessible::Dialog] = NSAccessibilityWindowRole;
116 roleMap[QAccessible::AlertMessage] = NSAccessibilityWindowRole;
117 roleMap[QAccessible::ToolTip] = NSAccessibilityWindowRole;
118 roleMap[QAccessible::HelpBalloon] = NSAccessibilityWindowRole;
119 roleMap[QAccessible::PopupMenu] = NSAccessibilityMenuRole;
120 roleMap[QAccessible::Application] = NSAccessibilityApplicationRole;
121 roleMap[QAccessible::Pane] = NSAccessibilityGroupRole;
122 roleMap[QAccessible::Grouping] = NSAccessibilityGroupRole;
123 roleMap[QAccessible::Separator] = NSAccessibilitySplitterRole;
124 roleMap[QAccessible::ToolBar] = NSAccessibilityToolbarRole;
125 roleMap[QAccessible::PageTab] = NSAccessibilityRadioButtonRole;
126 roleMap[QAccessible::PageTabList] = NSAccessibilityTabGroupRole;
127 roleMap[QAccessible::ButtonMenu] = NSAccessibilityMenuButtonRole;
128 roleMap[QAccessible::ButtonDropDown] = NSAccessibilityPopUpButtonRole;
129 roleMap[QAccessible::SpinBox] = NSAccessibilityIncrementorRole;
130 roleMap[QAccessible::Slider] = NSAccessibilitySliderRole;
131 roleMap[QAccessible::ProgressBar] = NSAccessibilityProgressIndicatorRole;
132 roleMap[QAccessible::ComboBox] = NSAccessibilityComboBoxRole;
133 roleMap[QAccessible::RadioButton] = NSAccessibilityRadioButtonRole;
134 roleMap[QAccessible::CheckBox] = NSAccessibilityCheckBoxRole;
135 roleMap[QAccessible::StaticText] = NSAccessibilityStaticTextRole;
136 roleMap[QAccessible::Table] = NSAccessibilityTableRole;
137 roleMap[QAccessible::StatusBar] = NSAccessibilityStaticTextRole;
138 roleMap[QAccessible::Column] = NSAccessibilityColumnRole;
139 roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole;
140 roleMap[QAccessible::Row] = NSAccessibilityRowRole;
141 roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole;
142 roleMap[QAccessible::Button] = NSAccessibilityButtonRole;
143 roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole;
144 roleMap[QAccessible::Link] = NSAccessibilityLinkRole;
145 roleMap[QAccessible::Indicator] = NSAccessibilityValueIndicatorRole;
146 roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole;
147 roleMap[QAccessible::List] = NSAccessibilityListRole;
148 roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole;
149 roleMap[QAccessible::Cell] = NSAccessibilityCellRole;
150 roleMap[QAccessible::Client] = NSAccessibilityGroupRole;
151 roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole;
152 roleMap[QAccessible::Section] = NSAccessibilityGroupRole;
153 roleMap[QAccessible::WebDocument] = NSAccessibilityGroupRole;
154 roleMap[QAccessible::ColorChooser] = NSAccessibilityColorWellRole;
155 roleMap[QAccessible::Footer] = NSAccessibilityGroupRole;
156 roleMap[QAccessible::Form] = NSAccessibilityGroupRole;
157 roleMap[QAccessible::Heading] = @
"AXHeading";
158 roleMap[QAccessible::Note] = NSAccessibilityGroupRole;
159 roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole;
160 roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
161 roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
162 roleMap[QAccessible::BlockQuote] = NSAccessibilityGroupRole;
166
167
168
169NSString *macRole(QAccessibleInterface *interface)
171 QAccessible::Role qtRole = interface->role();
172 QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap();
174 if (roleMap.isEmpty())
179 if (roleMap.contains(qtRole)) {
181 if (roleMap[qtRole] == NSAccessibilityComboBoxRole && !interface->state().editable)
182 return NSAccessibilityMenuButtonRole;
183 if (roleMap[qtRole] == NSAccessibilityTextFieldRole && interface->state().multiLine)
184 return NSAccessibilityTextAreaRole;
185 return roleMap[qtRole];
192 return NSAccessibilityGroupRole;
196
197
198NSString *macSubrole(QAccessibleInterface *interface)
200 QAccessible::State s = interface->state();
202 return NSAccessibilitySearchFieldSubrole;
204 return NSAccessibilitySecureTextFieldSubrole;
205 if (interface->role() == QAccessible::PageTab)
206 return NSAccessibilityTabButtonSubrole;
211
212
213
214
215bool shouldBeIgnored(QAccessibleInterface *interface)
219 const QAccessible::State state = interface->state();
220 if (state.invisible || state.offscreen || state.invalid)
225 switch (interface->role()) {
226 case QAccessible::Border:
227 case QAccessible::Application:
228 case QAccessible::ToolBar:
229 case QAccessible::Pane:
230 case QAccessible::Client:
231 case QAccessible::PopupMenu:
237 NSString *mac_role = macRole(interface);
238 if (mac_role == NSAccessibilityWindowRole ||
239 mac_role == NSAccessibilityUnknownRole) {
243 if (
const QObject *object = interface->object()) {
244 const QByteArrayView className = object->metaObject()->className();
249 if (className ==
"QTipLabel"_ba)
256bool defaultUnignored(QAccessibleInterface *child)
258 if (child && child->isValid()) {
259 const auto state = child->state();
260 return !state.invalid && !state.invisible;
265NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface,
266 const std::function<
bool(QAccessibleInterface *child)> &pred)
268 int numKids = interface->childCount();
270 NSMutableArray<QMacAccessibilityElement *> *kids = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numKids];
271 for (
int i = 0; i < numKids; ++i) {
272 QAccessibleInterface *child = interface->child(i);
277 QAccessible::Id childId = QAccessible::uniqueId(child);
279 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId: childId];
281 [kids addObject: element];
283 qWarning(
"QCocoaAccessibility: invalid child");
285 return NSAccessibilityUnignoredChildren(kids);
289
290
291
292
293NSString *getTranslatedAction(
const QString &qtAction)
295 if (qtAction == QAccessibleActionInterface::pressAction())
296 return NSAccessibilityPressAction;
297 else if (qtAction == QAccessibleActionInterface::increaseAction())
298 return NSAccessibilityIncrementAction;
299 else if (qtAction == QAccessibleActionInterface::decreaseAction())
300 return NSAccessibilityDecrementAction;
301 else if (qtAction == QAccessibleActionInterface::showMenuAction())
302 return NSAccessibilityShowMenuAction;
303 else if (qtAction == QAccessibleActionInterface::setFocusAction())
304 return NSAccessibilityRaiseAction;
305 else if (qtAction == QAccessibleActionInterface::toggleAction())
306 return NSAccessibilityPressAction;
325
326
327
328QString translateAction(NSString *nsAction, QAccessibleInterface *interface)
330 if ([nsAction compare: NSAccessibilityPressAction] == NSOrderedSame) {
331 if (interface->role() == QAccessible::CheckBox || interface->role() == QAccessible::RadioButton)
332 return QAccessibleActionInterface::toggleAction();
333 return QAccessibleActionInterface::pressAction();
334 }
else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame)
335 return QAccessibleActionInterface::increaseAction();
336 else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame)
337 return QAccessibleActionInterface::decreaseAction();
338 else if ([nsAction compare: NSAccessibilityShowMenuAction] == NSOrderedSame)
339 return QAccessibleActionInterface::showMenuAction();
340 else if ([nsAction compare: NSAccessibilityRaiseAction] == NSOrderedSame)
341 return QAccessibleActionInterface::setFocusAction();
348bool hasValueAttribute(QAccessibleInterface *interface)
351 const QAccessible::Role qtrole = interface->role();
352 if (qtrole == QAccessible::EditableText
353 || qtrole == QAccessible::StaticText
354 || interface->valueInterface()
355 || interface->state().checkable) {
362id getValueAttribute(QAccessibleInterface *interface)
364 const QAccessible::Role qtrole = interface->role();
365 if (qtrole == QAccessible::StaticText) {
366 return interface->text(QAccessible::Name).toNSString();
368 if (qtrole == QAccessible::EditableText) {
369 if (QAccessibleTextInterface *textInterface = interface->textInterface()) {
372 int end = textInterface->characterCount();
374 if (interface->state().passwordEdit) {
376 text = QString(end, QChar(0x2022));
385 text = textInterface->text(begin, end);
387 return text.toNSString();
391 if (QAccessibleValueInterface *valueInterface = interface->valueInterface()) {
392 return valueInterface->currentValue().toString().toNSString();
395 if (interface->state().checkable) {
396 if (interface->state().checkStateMixed)
398 return interface->state().checked ? @(1) : @(0);