|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2020 - 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 6 End-User License
- Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
-
- End User License Agreement: www.juce.com/juce-6-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.
-
- ==============================================================================
- */
-
- static void juceFreeAccessibilityPlatformSpecificData (UIAccessibilityElement* element)
- {
- if (auto* container = juce::getIvar<UIAccessibilityElement*> (element, "container"))
- {
- object_setInstanceVariable (element, "container", nullptr);
- object_setInstanceVariable (container, "handler", nullptr);
-
- [container release];
- }
- }
-
- namespace juce
- {
-
- #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
- #define JUCE_IOS_CONTAINER_API_AVAILABLE 1
- #endif
-
- JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new")
-
- constexpr auto juceUIAccessibilityContainerTypeNone =
- #if JUCE_IOS_CONTAINER_API_AVAILABLE
- UIAccessibilityContainerTypeNone;
- #else
- 0;
- #endif
-
- constexpr auto juceUIAccessibilityContainerTypeDataTable =
- #if JUCE_IOS_CONTAINER_API_AVAILABLE
- UIAccessibilityContainerTypeDataTable;
- #else
- 1;
- #endif
-
- constexpr auto juceUIAccessibilityContainerTypeList =
- #if JUCE_IOS_CONTAINER_API_AVAILABLE
- UIAccessibilityContainerTypeList;
- #else
- 2;
- #endif
-
- JUCE_END_IGNORE_WARNINGS_GCC_LIKE
-
- #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
-
- //==============================================================================
- static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler)
- {
- const auto children = handler.getChildren();
-
- NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
-
- [accessibleChildren addObject: (id) handler.getNativeImplementation()];
-
- for (auto* childHandler : children)
- {
- id accessibleElement = [&childHandler]
- {
- id native = (id) childHandler->getNativeImplementation();
-
- if (childHandler->getChildren().size() > 0)
- return [native accessibilityContainer];
-
- return native;
- }();
-
- if (accessibleElement != nil)
- [accessibleChildren addObject: accessibleElement];
- }
-
- return accessibleChildren;
- }
-
- //==============================================================================
- class AccessibilityHandler::AccessibilityNativeImpl
- {
- public:
- explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
- : accessibilityElement (AccessibilityElement::create (handler))
- {
- }
-
- UIAccessibilityElement* getAccessibilityElement() const noexcept
- {
- return accessibilityElement.get();
- }
-
- private:
- //==============================================================================
- class AccessibilityContainer : public ObjCClass<UIAccessibilityElement>
- {
- public:
- AccessibilityContainer()
- : ObjCClass ("JUCEUIAccessibilityElementContainer_")
- {
- addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement);
- addMethod (@selector (accessibilityFrame), getAccessibilityFrame);
- addMethod (@selector (accessibilityElements), getAccessibilityElements);
-
- #if JUCE_IOS_CONTAINER_API_AVAILABLE
- if (@available (iOS 11.0, *))
- addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType);
- #endif
-
- addIvar<AccessibilityHandler*> ("handler");
-
- registerClass();
- }
-
- private:
- static AccessibilityHandler* getHandler (id self)
- {
- return getIvar<AccessibilityHandler*> (self, "handler");
- }
-
- static BOOL getIsAccessibilityElement (id, SEL)
- {
- return NO;
- }
-
- static CGRect getAccessibilityFrame (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- return convertToCGRect (handler->getComponent().getScreenBounds());
-
- return CGRectZero;
- }
-
- static NSArray* getAccessibilityElements (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- return getContainerAccessibilityElements (*handler);
-
- return nil;
- }
-
- static NSInteger getAccessibilityContainerType (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- {
- if (handler->getTableInterface() != nullptr)
- return juceUIAccessibilityContainerTypeDataTable;
-
- const auto role = handler->getRole();
-
- if (role == AccessibilityRole::popupMenu
- || role == AccessibilityRole::list
- || role == AccessibilityRole::tree)
- {
- return juceUIAccessibilityContainerTypeList;
- }
- }
-
- return juceUIAccessibilityContainerTypeNone;
- }
- };
-
- //==============================================================================
- class AccessibilityElement : public AccessibleObjCClass<UIAccessibilityElement>
- {
- public:
- enum class Type { defaultElement, textElement };
-
- static Holder create (AccessibilityHandler& handler)
- {
- static AccessibilityElement cls { Type::defaultElement };
- static AccessibilityElement textCls { Type::textElement };
-
- id instance = (hasEditableText (handler) ? textCls : cls).createInstance();
-
- Holder element ([instance initWithAccessibilityContainer: (id) handler.getComponent().getWindowHandle()]);
- object_setInstanceVariable (element.get(), "handler", &handler);
- return element;
- }
-
- AccessibilityElement (Type elementType)
- {
- addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement);
- addMethod (@selector (accessibilityContainer), getAccessibilityContainer);
- addMethod (@selector (accessibilityFrame), getAccessibilityFrame);
- addMethod (@selector (accessibilityTraits), getAccessibilityTraits);
- addMethod (@selector (accessibilityLabel), getAccessibilityTitle);
- addMethod (@selector (accessibilityHint), getAccessibilityHelp);
- addMethod (@selector (accessibilityValue), getAccessibilityValue);
- addMethod (@selector (setAccessibilityValue:), setAccessibilityValue);
-
- addMethod (@selector (accessibilityElementDidBecomeFocused), onFocusGain);
- addMethod (@selector (accessibilityElementDidLoseFocus), onFocusLoss);
- addMethod (@selector (accessibilityElementIsFocused), isFocused);
- addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal);
-
- addMethod (@selector (accessibilityActivate), accessibilityPerformActivate);
- addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement);
- addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement);
- addMethod (@selector (accessibilityPerformEscape), accessibilityPerformEscape);
-
- #if JUCE_IOS_CONTAINER_API_AVAILABLE
- if (@available (iOS 11.0, *))
- {
- addMethod (@selector (accessibilityDataTableCellElementForRow:column:), getAccessibilityDataTableCellElementForRowColumn);
- addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
- addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
- addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange);
- addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange);
- }
- #endif
-
- if (elementType == Type::textElement)
- {
- addMethod (@selector (accessibilityLineNumberForPoint:), getAccessibilityLineNumberForPoint);
- addMethod (@selector (accessibilityContentForLineNumber:), getAccessibilityContentForLineNumber);
- addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber);
- addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent);
-
- addProtocol (@protocol (UIAccessibilityReadingContent));
- }
-
- addIvar<UIAccessibilityElement*> ("container");
-
- registerClass();
- }
-
- private:
- //==============================================================================
- static UIAccessibilityElement* getContainer (id self)
- {
- return getIvar<UIAccessibilityElement*> (self, "container");
- }
-
- //==============================================================================
- static id getAccessibilityContainer (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- {
- if (handler->getComponent().isOnDesktop())
- return (id) handler->getComponent().getWindowHandle();
-
- if (handler->getChildren().size() > 0)
- {
- if (UIAccessibilityElement* container = getContainer (self))
- return container;
-
- static AccessibilityContainer cls;
-
- id windowHandle = (id) handler->getComponent().getWindowHandle();
- UIAccessibilityElement* container = [cls.createInstance() initWithAccessibilityContainer: windowHandle];
-
- [container retain];
-
- object_setInstanceVariable (container, "handler", handler);
- object_setInstanceVariable (self, "container", container);
-
- return container;
- }
-
- if (auto* parent = handler->getParent())
- return [(id) parent->getNativeImplementation() accessibilityContainer];
- }
-
- return nil;
- }
-
- static CGRect getAccessibilityFrame (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- return convertToCGRect (handler->getComponent().getScreenBounds());
-
- return CGRectZero;
- }
-
- static UIAccessibilityTraits getAccessibilityTraits (id self, SEL)
- {
- auto traits = UIAccessibilityTraits{};
-
- if (auto* handler = getHandler (self))
- {
- traits |= [&handler]
- {
- switch (handler->getRole())
- {
- case AccessibilityRole::button:
- case AccessibilityRole::toggleButton:
- case AccessibilityRole::radioButton:
- case AccessibilityRole::comboBox: return UIAccessibilityTraitButton;
-
- case AccessibilityRole::label:
- case AccessibilityRole::staticText: return UIAccessibilityTraitStaticText;
-
- case AccessibilityRole::image: return UIAccessibilityTraitImage;
- case AccessibilityRole::tableHeader: return UIAccessibilityTraitHeader;
- case AccessibilityRole::hyperlink: return UIAccessibilityTraitLink;
- case AccessibilityRole::editableText: return UIAccessibilityTraitKeyboardKey;
- case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
-
- case AccessibilityRole::slider:
- case AccessibilityRole::menuItem:
- case AccessibilityRole::menuBar:
- case AccessibilityRole::popupMenu:
- case AccessibilityRole::table:
- case AccessibilityRole::column:
- case AccessibilityRole::row:
- case AccessibilityRole::cell:
- case AccessibilityRole::list:
- case AccessibilityRole::listItem:
- case AccessibilityRole::tree:
- case AccessibilityRole::treeItem:
- case AccessibilityRole::progressBar:
- case AccessibilityRole::group:
- case AccessibilityRole::dialogWindow:
- case AccessibilityRole::window:
- case AccessibilityRole::scrollBar:
- case AccessibilityRole::tooltip:
- case AccessibilityRole::splashScreen:
- case AccessibilityRole::unspecified: break;
- }
-
- return UIAccessibilityTraitNone;
- }();
-
- const auto state = handler->getCurrentState();
-
- if (state.isSelected() || state.isChecked())
- traits |= UIAccessibilityTraitSelected;
-
- if (auto* valueInterface = getValueInterface (self))
- if (! valueInterface->isReadOnly() && valueInterface->getRange().isValid())
- traits |= UIAccessibilityTraitAdjustable;
- }
-
- return traits | sendSuperclassMessage<UIAccessibilityTraits> (self, @selector (accessibilityTraits));
- }
-
- static NSString* getAccessibilityValue (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- {
- if (handler->getCurrentState().isCheckable())
- return handler->getCurrentState().isChecked() ? @"1" : @"0";
-
- return (NSString*) getAccessibilityValueFromInterfaces (*handler);
- }
-
- return nil;
- }
-
- static void onFocusGain (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- {
- const WeakReference<Component> safeComponent (&handler->getComponent());
-
- performActionIfSupported (self, AccessibilityActionType::focus);
-
- if (safeComponent != nullptr)
- handler->grabFocus();
- }
- }
-
- static void onFocusLoss (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- handler->giveAwayFocus();
- }
-
- static BOOL isFocused (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- return handler->hasFocus (false);
-
- return NO;
- }
-
- static BOOL accessibilityPerformActivate (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- {
- // occasionaly VoiceOver sends accessibilityActivate to the wrong element, so we first query
- // which element it thinks has focus and forward the event on to that element if it differs
- id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
-
- if (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
- return [focusedElement accessibilityActivate];
-
- if (handler->hasFocus (false))
- return accessibilityPerformPress (self, {});
- }
-
- return NO;
- }
-
- static BOOL accessibilityPerformEscape (id self, SEL)
- {
- if (auto* handler = getHandler (self))
- {
- if (auto* modal = Component::getCurrentlyModalComponent())
- {
- if (auto* modalHandler = modal->getAccessibilityHandler())
- {
- if (modalHandler == handler || modalHandler->isParentOf (handler))
- {
- modal->exitModalState (0);
- return YES;
- }
- }
- }
- }
-
- return NO;
- }
-
- static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
- {
- if (auto* tableInterface = getTableInterface (self))
- if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
- return (id) cellHandler->getNativeImplementation();
-
- return nil;
- }
-
- static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
- {
- if (auto* handler = getHandler (self))
- {
- if (auto* textInterface = handler->getTextInterface())
- {
- auto pointInt = roundToIntPoint (point);
-
- if (handler->getComponent().getScreenBounds().contains (pointInt))
- {
- auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
-
- for (int i = 0; i < textBounds.getNumRectangles(); ++i)
- if (textBounds.getRectangle (i).contains (pointInt))
- return (NSInteger) i;
- }
- }
- }
-
- return NSNotFound;
- }
-
- static NSString* getAccessibilityContentForLineNumber (id self, SEL, NSInteger lineNumber)
- {
- if (auto* textInterface = getTextInterface (self))
- {
- auto lines = StringArray::fromLines (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
-
- if ((int) lineNumber < lines.size())
- return juceStringToNS (lines[(int) lineNumber]);
- }
-
- return nil;
- }
-
- static CGRect getAccessibilityFrameForLineNumber (id self, SEL, NSInteger lineNumber)
- {
- if (auto* textInterface = getTextInterface (self))
- {
- auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
-
- if (lineNumber < textBounds.getNumRectangles())
- return convertToCGRect (textBounds.getRectangle ((int) lineNumber));
- }
-
- return CGRectZero;
- }
-
- static NSString* getAccessibilityPageContent (id self, SEL)
- {
- if (auto* textInterface = getTextInterface (self))
- return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
-
- return nil;
- }
-
- //==============================================================================
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
- };
-
- //==============================================================================
- AccessibilityElement::Holder accessibilityElement;
-
- //==============================================================================
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
- };
-
- //==============================================================================
- AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
- {
- return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
- }
-
- static bool areAnyAccessibilityClientsActive()
- {
- return UIAccessibilityIsVoiceOverRunning();
- }
-
- static void sendAccessibilityEvent (UIAccessibilityNotifications notification, id argument)
- {
- if (! areAnyAccessibilityClientsActive())
- return;
-
- jassert (notification != UIAccessibilityNotifications{});
-
- UIAccessibilityPostNotification (notification, argument);
- }
-
- void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
- {
- auto notification = [eventType]
- {
- switch (eventType)
- {
- case InternalAccessibilityEvent::elementCreated:
- case InternalAccessibilityEvent::elementDestroyed:
- case InternalAccessibilityEvent::elementMovedOrResized:
- case InternalAccessibilityEvent::focusChanged: return UIAccessibilityLayoutChangedNotification;
-
- case InternalAccessibilityEvent::windowOpened:
- case InternalAccessibilityEvent::windowClosed: return UIAccessibilityScreenChangedNotification;
- }
-
- return UIAccessibilityNotifications{};
- }();
-
- if (notification != UIAccessibilityNotifications{})
- {
- const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
-
- sendAccessibilityEvent (notification,
- moveToHandler ? (id) handler.getNativeImplementation() : nil);
- }
-
- }
-
- void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
- {
- auto notification = [eventType]
- {
- switch (eventType)
- {
- case AccessibilityEvent::textSelectionChanged:
- case AccessibilityEvent::rowSelectionChanged:
- case AccessibilityEvent::textChanged:
- case AccessibilityEvent::valueChanged:
- case AccessibilityEvent::titleChanged: break;
-
- case AccessibilityEvent::structureChanged: return UIAccessibilityLayoutChangedNotification;
- }
-
- return UIAccessibilityNotifications{};
- }();
-
- if (notification != UIAccessibilityNotifications{})
- sendAccessibilityEvent (notification, (id) getNativeImplementation());
- }
-
- void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
- {
- sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
- }
-
- } // namespace juce
|