/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ API_AVAILABLE (macos (10.10)) static void juceFreeAccessibilityPlatformSpecificData (NSAccessibilityElement*) {} namespace juce { #if ! defined (MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 using NSAccessibilityRole = NSString*; using NSAccessibilityNotificationName = NSString*; #endif #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1 //============================================================================== class AccessibilityHandler::AccessibilityNativeImpl { public: explicit AccessibilityNativeImpl (AccessibilityHandler& handler) { if (@available (macOS 10.10, *)) accessibilityElement = AccessibilityElement::create (handler); } API_AVAILABLE (macos (10.10)) NSAccessibilityElement* getAccessibilityElement() const noexcept { return accessibilityElement.get(); } private: //============================================================================== class API_AVAILABLE (macos (10.10)) AccessibilityElement : public AccessibleObjCClass> { public: static Holder create (AccessibilityHandler& handler) { if (@available (macOS 10.10, *)) { static AccessibilityElement cls; Holder element ([cls.createInstance() init]); object_setInstanceVariable (element.get(), "handler", &handler); return element; } return {}; } private: AccessibilityElement() { addMethod (@selector (accessibilityNotifiesWhenDestroyed), [] (id, SEL) { return YES; }); addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement); addMethod (@selector (isAccessibilityEnabled), [] (id self, SEL) -> BOOL { if (auto* handler = getHandler (self)) return handler->getComponent().isEnabled(); return NO; }); addMethod (@selector (accessibilityWindow), getAccessibilityWindow); addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow); addMethod (@selector (accessibilityChildren), getAccessibilityChildren); addMethod (@selector (isAccessibilityModal), getIsAccessibilityModal); addMethod (@selector (accessibilityFocusedUIElement), [] (id self, SEL) -> id { if (auto* handler = getHandler (self)) { if (auto* modal = Component::getCurrentlyModalComponent()) { const auto& handlerComponent = handler->getComponent(); if (! handlerComponent.isParentOf (modal) && handlerComponent.isCurrentlyBlockedByAnotherModalComponent()) { if (auto* modalHandler = modal->getAccessibilityHandler()) { if (auto* focusChild = modalHandler->getChildFocus()) return static_cast (focusChild->getNativeImplementation()); return static_cast (modalHandler->getNativeImplementation()); } } } if (auto* focusChild = handler->getChildFocus()) return static_cast (focusChild->getNativeImplementation()); } return nil; }); addMethod (@selector (accessibilityHitTest:), [] (id self, SEL, NSPoint point) -> id { if (auto* handler = getHandler (self)) { if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point)))) return static_cast (child->getNativeImplementation()); return self; } return nil; }); addMethod (@selector (accessibilityParent), [] (id self, SEL) -> id { if (auto* handler = getHandler (self)) { if (auto* parentHandler = handler->getParent()) return NSAccessibilityUnignoredAncestor (static_cast (parentHandler->getNativeImplementation())); return NSAccessibilityUnignoredAncestor (static_cast (handler->getComponent().getWindowHandle())); } return nil; }); addMethod (@selector (isAccessibilityFocused), [] (id self, SEL) { return [[self accessibilityWindow] accessibilityFocusedUIElement] == self; }); addMethod (@selector (setAccessibilityFocused:), [] (id self, SEL, BOOL focused) { if (auto* handler = getHandler (self)) { if (focused) { const WeakReference safeComponent (&handler->getComponent()); performActionIfSupported (self, AccessibilityActionType::focus); if (safeComponent != nullptr) handler->grabFocus(); } else { handler->giveAwayFocus(); } } }); addMethod (@selector (accessibilityFrame), [] (id self, SEL) { if (auto* handler = getHandler (self)) return flippedScreenRect (makeNSRect (handler->getComponent().getScreenBounds())); return NSZeroRect; }); addMethod (@selector (accessibilityRole), [] (id self, SEL) -> NSAccessibilityRole { if (auto* handler = getHandler (self)) { switch (handler->getRole()) { case AccessibilityRole::popupMenu: case AccessibilityRole::tooltip: case AccessibilityRole::splashScreen: case AccessibilityRole::dialogWindow: case AccessibilityRole::window: return NSAccessibilityWindowRole; case AccessibilityRole::tableHeader: case AccessibilityRole::unspecified: case AccessibilityRole::group: return NSAccessibilityGroupRole; case AccessibilityRole::label: case AccessibilityRole::staticText: return NSAccessibilityStaticTextRole; case AccessibilityRole::tree: case AccessibilityRole::list: return NSAccessibilityOutlineRole; case AccessibilityRole::listItem: case AccessibilityRole::treeItem: return NSAccessibilityRowRole; case AccessibilityRole::button: return NSAccessibilityButtonRole; case AccessibilityRole::toggleButton: return NSAccessibilityCheckBoxRole; case AccessibilityRole::radioButton: return NSAccessibilityRadioButtonRole; case AccessibilityRole::comboBox: return NSAccessibilityPopUpButtonRole; case AccessibilityRole::image: return NSAccessibilityImageRole; case AccessibilityRole::slider: return NSAccessibilitySliderRole; case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole; case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole; case AccessibilityRole::menuBar: return NSAccessibilityMenuRole; case AccessibilityRole::table: return NSAccessibilityOutlineRole; case AccessibilityRole::column: return NSAccessibilityColumnRole; case AccessibilityRole::row: return NSAccessibilityRowRole; case AccessibilityRole::cell: return NSAccessibilityCellRole; case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole; case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole; case AccessibilityRole::scrollBar: return NSAccessibilityScrollBarRole; case AccessibilityRole::ignored: break; } return NSAccessibilityUnknownRole; } return nil; }); addMethod (@selector (accessibilitySubrole), [] (id self, SEL) { if (auto* handler = getHandler (self)) { if (auto* textInterface = getTextInterface (self)) if (textInterface->isDisplayingProtectedText()) return NSAccessibilitySecureTextFieldSubrole; const auto handlerRole = handler->getRole(); if (handlerRole == AccessibilityRole::window) return NSAccessibilityStandardWindowSubrole; if (handlerRole == AccessibilityRole::dialogWindow) return NSAccessibilityDialogSubrole; if (handlerRole == AccessibilityRole::tooltip || handlerRole == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole; if (handlerRole == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole; if (handlerRole == AccessibilityRole::treeItem || handlerRole == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole; if (handlerRole == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole; const auto& handlerComponent = handler->getComponent(); if (auto* documentWindow = handlerComponent.findParentComponentOfClass()) { if (handlerRole == AccessibilityRole::button) { if (&handlerComponent == documentWindow->getCloseButton()) return NSAccessibilityCloseButtonSubrole; if (&handlerComponent == documentWindow->getMinimiseButton()) return NSAccessibilityMinimizeButtonSubrole; if (&handlerComponent == documentWindow->getMaximiseButton()) return NSAccessibilityFullScreenButtonSubrole; } } } return NSAccessibilityUnknownRole; }); addMethod (@selector (accessibilityLabel), [] (id self, SEL) -> NSString* { if (auto* handler = getHandler (self)) return juceStringToNS (handler->getDescription()); return nil; }); addMethod (@selector (accessibilityValue), [] (id self, SEL) -> id { if (auto* handler = getHandler (self)) { if (handler->getCurrentState().isCheckable()) return juceStringToNS (handler->getCurrentState().isChecked() ? TRANS ("On") : TRANS ("Off")); return getAccessibilityValueFromInterfaces (*handler); } return nil; }); addMethod (@selector (accessibilityTitle), getAccessibilityTitle); addMethod (@selector (accessibilityHelp), getAccessibilityHelp); addMethod (@selector (setAccessibilityValue:), setAccessibilityValue); addMethod (@selector (accessibilitySelectedChildren), [] (id self, SEL) { return getSelectedChildren ([self accessibilityChildren]); }); addMethod (@selector (setAccessibilitySelectedChildren:), [] (id self, SEL, NSArray* selected) { setSelectedChildren ([self accessibilityChildren], selected); }); addMethod (@selector (accessibilityOrientation), [] (id self, SEL) { if (auto* handler = getHandler (self)) return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f ? NSAccessibilityOrientationHorizontal : NSAccessibilityOrientationVertical; return NSAccessibilityOrientationUnknown; }); addMethod (@selector (accessibilityInsertionPointLineNumber), [] (id self, SEL) -> NSInteger { if (auto* textInterface = getTextInterface (self)) return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()]; return 0; }); addMethod (@selector (accessibilityVisibleCharacterRange), [] (id self, SEL) { if (auto* textInterface = getTextInterface (self)) return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() }); return NSMakeRange (0, 0); }); addMethod (@selector (accessibilityNumberOfCharacters), [] (id self, SEL) { if (auto* textInterface = getTextInterface (self)) return textInterface->getTotalNumCharacters(); return 0; }); addMethod (@selector (accessibilitySelectedText), [] (id self, SEL) -> NSString* { if (auto* textInterface = getTextInterface (self)) return juceStringToNS (textInterface->getText (textInterface->getSelection())); return nil; }); addMethod (@selector (accessibilitySelectedTextRange), [] (id self, SEL) { if (auto* textInterface = getTextInterface (self)) { const auto currentSelection = textInterface->getSelection(); if (currentSelection.isEmpty()) return NSMakeRange ((NSUInteger) textInterface->getTextInsertionOffset(), 0); return juceRangeToNS (currentSelection); } return NSMakeRange (0, 0); }); addMethod (@selector (accessibilityAttributedStringForRange:), [] (id self, SEL, NSRange range) -> NSAttributedString* { NSString* string = [self accessibilityStringForRange: range]; if (string != nil) return [[[NSAttributedString alloc] initWithString: string] autorelease]; return nil; }); addMethod (@selector (accessibilityRangeForLine:), [] (id self, SEL, NSInteger line) { if (auto* textInterface = getTextInterface (self)) { auto text = textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }); auto lines = StringArray::fromLines (text); if (line < lines.size()) { auto lineText = lines[(int) line]; auto start = text.indexOf (lineText); if (start >= 0) return NSMakeRange ((NSUInteger) start, (NSUInteger) lineText.length()); } } return NSMakeRange (0, 0); }); addMethod (@selector (accessibilityStringForRange:), [] (id self, SEL, NSRange range) -> NSString* { if (auto* textInterface = getTextInterface (self)) return juceStringToNS (textInterface->getText (nsRangeToJuce (range))); return nil; }); addMethod (@selector (accessibilityRangeForPosition:), [] (id self, SEL, NSPoint position) { if (auto* handler = getHandler (self)) { if (auto* textInterface = handler->getTextInterface()) { auto screenPoint = roundToIntPoint (flippedScreenPoint (position)); if (handler->getComponent().getScreenBounds().contains (screenPoint)) { auto offset = textInterface->getOffsetAtPoint (screenPoint); if (offset >= 0) return NSMakeRange ((NSUInteger) offset, 1); } } } return NSMakeRange (0, 0); }); addMethod (@selector (accessibilityRangeForIndex:), [] (id self, SEL, NSInteger index) { if (auto* textInterface = getTextInterface (self)) if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters())) return NSMakeRange ((NSUInteger) index, 1); return NSMakeRange (0, 0); }); addMethod (@selector (accessibilityFrameForRange:), [] (id self, SEL, NSRange range) { if (auto* textInterface = getTextInterface (self)) return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds())); return NSZeroRect; }); addMethod (@selector (accessibilityLineForIndex:), [] (id self, SEL, NSInteger index) { if (auto* textInterface = getTextInterface (self)) { auto text = textInterface->getText ({ 0, (int) index }); if (! text.isEmpty()) return StringArray::fromLines (text).size() - 1; } return 0; }); addMethod (@selector (setAccessibilitySelectedTextRange:), [] (id self, SEL, NSRange selectedRange) { if (auto* textInterface = getTextInterface (self)) textInterface->setSelection (nsRangeToJuce (selectedRange)); }); addMethod (@selector (accessibilityRows), [] (id self, SEL) -> NSArray* { if (auto* tableInterface = getTableInterface (self)) { auto* rows = [[NSMutableArray new] autorelease]; for (int row = 0, numRows = tableInterface->getNumRows(); row < numRows; ++row) { if (auto* rowHandler = tableInterface->getRowHandler (row)) { [rows addObject: static_cast (rowHandler->getNativeImplementation())]; } else { [rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole frame: NSZeroRect label: @"Offscreen Row" parent: self]]; } } return rows; } return nil; }); addMethod (@selector (accessibilitySelectedRows), [] (id self, SEL) { return getSelectedChildren ([self accessibilityRows]); }); addMethod (@selector (setAccessibilitySelectedRows:), [] (id self, SEL, NSArray* selected) { setSelectedChildren ([self accessibilityRows], selected); }); addMethod (@selector (accessibilityHeader), [] (id self, SEL) -> id { if (auto* tableInterface = getTableInterface (self)) if (auto* handler = tableInterface->getHeaderHandler()) return static_cast (handler->getNativeImplementation()); return nil; }); addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount); addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount); addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange); addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange); addMethod (@selector (accessibilityIndex), [] (id self, SEL) -> NSInteger { if (auto* handler = getHandler (self)) { if (auto* tableHandler = getEnclosingHandlerWithInterface (handler, &AccessibilityHandler::getTableInterface)) { if (auto* tableInterface = tableHandler->getTableInterface()) { NSAccessibilityRole handlerRole = [self accessibilityRole]; if ([handlerRole isEqual: NSAccessibilityRowRole]) if (const auto span = tableInterface->getRowSpan (*handler)) return span->begin; if ([handlerRole isEqual: NSAccessibilityColumnRole]) if (const auto span = tableInterface->getColumnSpan (*handler)) return span->begin; } } } return 0; }); addMethod (@selector (accessibilityDisclosureLevel), [] (id self, SEL) { if (auto* handler = getHandler (self)) if (auto* cellInterface = handler->getCellInterface()) return cellInterface->getDisclosureLevel(); return 0; }); addMethod (@selector (accessibilityDisclosedRows), [] (id self, SEL) -> id { if (auto* handler = getHandler (self)) { if (auto* cellInterface = handler->getCellInterface()) { const auto rows = cellInterface->getDisclosedRows(); auto* result = [NSMutableArray arrayWithCapacity: rows.size()]; for (const auto& row : rows) { if (row != nullptr) [result addObject: static_cast (row->getNativeImplementation())]; else [result addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole frame: NSZeroRect label: @"Offscreen Row" parent: self]]; } return result; } } return nil; }); addMethod (@selector (isAccessibilityExpanded), [] (id self, SEL) -> BOOL { if (auto* handler = getHandler (self)) return handler->getCurrentState().isExpanded(); return NO; }); addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement); addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement); addMethod (@selector (accessibilityPerformDelete), [] (id self, SEL) { if (auto* handler = getHandler (self)) { if (hasEditableText (*handler)) { handler->getTextInterface()->setText ({}); return YES; } if (auto* valueInterface = handler->getValueInterface()) { if (! valueInterface->isReadOnly()) { valueInterface->setValue ({}); return YES; } } } return NO; }); addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress); addMethod (@selector (accessibilityPerformShowMenu), [] (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); }); addMethod (@selector (accessibilityPerformRaise), [] (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; }); addMethod (@selector (isAccessibilitySelectorAllowed:), [] (id self, SEL, SEL selector) -> BOOL { if (auto* handler = getHandler (self)) { const auto handlerRole = handler->getRole(); const auto currentState = handler->getCurrentState(); for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber), @selector (accessibilityVisibleCharacterRange), @selector (accessibilityNumberOfCharacters), @selector (accessibilitySelectedText), @selector (accessibilitySelectedTextRange), @selector (accessibilityAttributedStringForRange:), @selector (accessibilityRangeForLine:), @selector (accessibilityStringForRange:), @selector (accessibilityRangeForPosition:), @selector (accessibilityRangeForIndex:), @selector (accessibilityFrameForRange:), @selector (accessibilityLineForIndex:), @selector (setAccessibilitySelectedTextRange:) }) { if (selector == textSelector) return handler->getTextInterface() != nullptr; } for (auto tableSelector : { @selector (accessibilityRowCount), @selector (accessibilityRows), @selector (accessibilitySelectedRows), @selector (accessibilityColumnCount), @selector (accessibilityHeader) }) { if (selector == tableSelector) return handler->getTableInterface() != nullptr; } for (auto cellSelector : { @selector (accessibilityRowIndexRange), @selector (accessibilityColumnIndexRange), @selector (accessibilityIndex), @selector (accessibilityDisclosureLevel) }) { if (selector == cellSelector) return handler->getCellInterface() != nullptr; } for (auto valueSelector : { @selector (accessibilityValue), @selector (setAccessibilityValue:), @selector (accessibilityPerformDelete), @selector (accessibilityPerformIncrement), @selector (accessibilityPerformDecrement) }) { if (selector != valueSelector) continue; auto* valueInterface = handler->getValueInterface(); if (selector == @selector (accessibilityValue)) return valueInterface != nullptr || hasEditableText (*handler) || currentState.isCheckable(); auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); }; if (selector == @selector (setAccessibilityValue:) || selector == @selector (accessibilityPerformDelete)) return hasEditableValue() || hasEditableText (*handler); auto isRanged = [valueInterface] { return valueInterface != nullptr && valueInterface->getRange().isValid(); }; if (selector == @selector (accessibilityPerformIncrement) || selector == @selector (accessibilityPerformDecrement)) return hasEditableValue() && isRanged(); return NO; } for (auto actionSelector : { @selector (accessibilityPerformPress), @selector (accessibilityPerformShowMenu), @selector (accessibilityPerformRaise), @selector (setAccessibilityFocused:) }) { if (selector != actionSelector) continue; if (selector == @selector (accessibilityPerformPress)) return (handler->getCurrentState().isCheckable() && handler->getActions().contains (AccessibilityActionType::toggle)) || handler->getActions().contains (AccessibilityActionType::press); if (selector == @selector (accessibilityPerformShowMenu)) return handler->getActions().contains (AccessibilityActionType::showMenu); if (selector == @selector (accessibilityPerformRaise)) return [[self accessibilityRole] isEqual: NSAccessibilityWindowRole]; if (selector == @selector (setAccessibilityFocused:)) return currentState.isFocusable(); } if (selector == @selector (accessibilitySelectedChildren)) return handlerRole == AccessibilityRole::popupMenu; if (selector == @selector (accessibilityOrientation)) return handlerRole == AccessibilityRole::scrollBar; if (selector == @selector (isAccessibilityExpanded)) return currentState.isExpandable(); return sendSuperclassMessage (self, @selector (isAccessibilitySelectorAllowed:), selector); } return NO; }); #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren); #endif registerClass(); } //============================================================================== static bool isSelectable (AccessibleState state) noexcept { return state.isSelectable() || state.isMultiSelectable(); } static NSArray* getSelectedChildren (NSArray* children) { NSMutableArray* selected = [[NSMutableArray new] autorelease]; for (id child in children) { if (auto* handler = getHandler (child)) { const auto currentState = handler->getCurrentState(); if (isSelectable (currentState) && currentState.isSelected()) [selected addObject: child]; } } return selected; } static void setSelected (id item, bool selected) { auto* handler = getHandler (item); if (handler == nullptr) return; const auto currentState = handler->getCurrentState(); if (isSelectable (currentState)) { if (currentState.isSelected() != selected) handler->getActions().invoke (AccessibilityActionType::toggle); } else if (currentState.isFocusable()) { [item setAccessibilityFocused: selected]; } } static void setSelectedChildren (NSArray* children, NSArray* selected) { for (id child in children) setSelected (child, [selected containsObject: child]); } //============================================================================== static id getAccessibilityWindow (id self, SEL) { return [[self accessibilityParent] accessibilityWindow]; } static NSArray* getAccessibilityChildren (id self, SEL) { if (auto* handler = getHandler (self)) { auto children = handler->getChildren(); auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()]; for (auto* childHandler : children) [accessibleChildren addObject: static_cast (childHandler->getNativeImplementation())]; return accessibleChildren; } return nil; } //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement) }; //============================================================================== API_AVAILABLE (macos (10.10)) AccessibilityElement::Holder accessibilityElement; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl) }; //============================================================================== AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { if (@available (macOS 10.10, *)) return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement(); return nullptr; } static bool areAnyAccessibilityClientsActive() { const String voiceOverKeyString ("voiceOverOnOffKey"); const String applicationIDString ("com.apple.universalaccess"); CFUniquePtr cfKey (voiceOverKeyString.toCFString()); CFUniquePtr cfID (applicationIDString.toCFString()); CFUniquePtr value (CFPreferencesCopyAppValue (cfKey.get(), cfID.get())); if (value != nullptr) return CFBooleanGetValue ((CFBooleanRef) value.get()); return false; } static void sendAccessibilityEvent (id accessibilityElement, NSAccessibilityNotificationName notification, NSDictionary* userInfo) { jassert (notification != NSAccessibilityNotificationName{}); NSAccessibilityPostNotificationWithUserInfo (accessibilityElement, notification, userInfo); } static void sendHandlerNotification (const AccessibilityHandler& handler, NSAccessibilityNotificationName notification) { if (! areAnyAccessibilityClientsActive() || notification == NSAccessibilityNotificationName{}) return; if (@available (macOS 10.9, *)) { if (id accessibilityElement = static_cast (handler.getNativeImplementation())) { sendAccessibilityEvent (accessibilityElement, notification, (notification == NSAccessibilityLayoutChangedNotification ? @{ NSAccessibilityUIElementsKey: @[ accessibilityElement ] } : nil)); } } } static NSAccessibilityNotificationName layoutChangedNotification() { if (@available (macOS 10.9, *)) return NSAccessibilityLayoutChangedNotification; static NSString* layoutChangedString = @"AXLayoutChanged"; return layoutChangedString; } void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType) { auto notification = [eventType] { switch (eventType) { case InternalAccessibilityEvent::elementCreated: return NSAccessibilityCreatedNotification; case InternalAccessibilityEvent::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification; case InternalAccessibilityEvent::elementMovedOrResized: return layoutChangedNotification(); case InternalAccessibilityEvent::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification; case InternalAccessibilityEvent::windowOpened: return NSAccessibilityWindowCreatedNotification; case InternalAccessibilityEvent::windowClosed: break; } return NSAccessibilityNotificationName{}; }(); sendHandlerNotification (handler, notification); } void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const { auto notification = [eventType] { switch (eventType) { case AccessibilityEvent::textSelectionChanged: return NSAccessibilitySelectedTextChangedNotification; case AccessibilityEvent::rowSelectionChanged: return NSAccessibilitySelectedRowsChangedNotification; case AccessibilityEvent::textChanged: case AccessibilityEvent::valueChanged: return NSAccessibilityValueChangedNotification; case AccessibilityEvent::titleChanged: return NSAccessibilityTitleChangedNotification; case AccessibilityEvent::structureChanged: return layoutChangedNotification(); } return NSAccessibilityNotificationName{}; }(); sendHandlerNotification (*this, notification); } void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority) { if (! areAnyAccessibilityClientsActive()) return; if (@available (macOS 10.9, *)) { auto nsPriority = [priority] { // The below doesn't get noticed by the @available check above JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability") switch (priority) { case AnnouncementPriority::low: return NSAccessibilityPriorityLow; case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium; case AnnouncementPriority::high: return NSAccessibilityPriorityHigh; } jassertfalse; return NSAccessibilityPriorityLow; JUCE_END_IGNORE_WARNINGS_GCC_LIKE }(); sendAccessibilityEvent (static_cast ([NSApp mainWindow]), NSAccessibilityAnnouncementRequestedNotification, @{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString), NSAccessibilityPriorityKey: @(nsPriority) }); } } } // namespace juce