| @@ -276,7 +276,10 @@ bool getComponentAsyncLayerBackedViewDisabled (juce::Component& comp) | |||
| } // namespace juce | |||
| #if JUCE_MAC || JUCE_IOS | |||
| #include "native/accessibility/juce_mac_AccessibilitySharedCode.mm" | |||
| #if JUCE_IOS | |||
| #include "native/accessibility/juce_ios_Accessibility.mm" | |||
| #include "native/juce_ios_UIViewComponentPeer.mm" | |||
| #include "native/juce_ios_Windowing.mm" | |||
| #include "native/juce_ios_FileChooser.mm" | |||
| @@ -0,0 +1,417 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #if (defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) | |||
| #define JUCE_IOS_CONTAINER_API_AVAILABLE 1 | |||
| #endif | |||
| 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 | |||
| #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1 | |||
| //============================================================================== | |||
| class AccessibilityHandler::AccessibilityNativeImpl | |||
| { | |||
| public: | |||
| explicit AccessibilityNativeImpl (AccessibilityHandler& handler) | |||
| : accessibilityElement (AccessibilityElement::create (handler)) | |||
| {} | |||
| UIAccessibilityElement* getAccessibilityElement() const noexcept | |||
| { | |||
| return accessibilityElement.get(); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| class AccessibilityElement : public AccessibleObjCClass<UIAccessibilityElement> | |||
| { | |||
| public: | |||
| static Holder create (AccessibilityHandler& handler) | |||
| { | |||
| static AccessibilityElement cls; | |||
| Holder element ([cls.createInstance() initWithAccessibilityContainer: (id) handler.getComponent().getWindowHandle()]); | |||
| object_setInstanceVariable (element.get(), "handler", &handler); | |||
| return element; | |||
| } | |||
| private: | |||
| AccessibilityElement() | |||
| { | |||
| addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:"); | |||
| addMethod (@selector (accessibilityContainer), getAccessibilityContainer, "@@:"); | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
| addMethod (@selector (accessibilityHitTest:), accessibilityHitTest, "@@:", @encode (CGPoint)); | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:"); | |||
| addMethod (@selector (accessibilityTraits), getAccessibilityTraits, "i@:"); | |||
| addMethod (@selector (accessibilityLabel), getAccessibilityTitle, "@@:"); | |||
| addMethod (@selector (accessibilityHint), getAccessibilityHelp, "@@:"); | |||
| addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:"); | |||
| addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@"); | |||
| addMethod (@selector (accessibilityElements), getAccessibilityChildren, "@@:"); | |||
| addMethod (@selector (accessibilityElementDidBecomeFocused), onFocusGain, "v@:"); | |||
| addMethod (@selector (accessibilityElementDidLoseFocus), onFocusLoss, "v@:"); | |||
| addMethod (@selector (accessibilityElementIsFocused), isFocused, "c@:"); | |||
| addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal, "c@:"); | |||
| addMethod (@selector (accessibilityActivate), accessibilityPerformPress, "c@:"); | |||
| addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement, "c@:"); | |||
| addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement, "c@:"); | |||
| addMethod (@selector (accessibilityLineNumberForPoint:), getAccessibilityLineNumberForPoint, "i@:", @encode (CGPoint)); | |||
| addMethod (@selector (accessibilityContentForLineNumber:), getAccessibilityContentForLineNumber, "@@:i"); | |||
| addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber, @encode (CGRect), "@:i"); | |||
| addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent, "@@:"); | |||
| addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType, "i@:"); | |||
| addMethod (@selector (accessibilityDataTableCellElementForRow:column:), getAccessibilityDataTableCellElementForRowColumn, "@@:ii"); | |||
| addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:"); | |||
| addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:"); | |||
| addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:"); | |||
| addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:"); | |||
| addProtocol (@protocol (UIAccessibilityReadingContent)); | |||
| registerClass(); | |||
| } | |||
| //============================================================================== | |||
| static id getAccessibilityContainer (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (auto* parent = handler->getParent()) | |||
| return (id) parent->getNativeImplementation(); | |||
| return (id) handler->getComponent().getWindowHandle(); | |||
| } | |||
| return nil; | |||
| } | |||
| static id accessibilityHitTest (id self, SEL, CGPoint point) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (auto* child = handler->getChildAt (roundToIntPoint (point))) | |||
| return (id) child->getNativeImplementation(); | |||
| return self; | |||
| } | |||
| 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::ignored: return UIAccessibilityTraitNotEnabled; | |||
| case AccessibilityRole::slider: | |||
| case AccessibilityRole::editableText: | |||
| 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; | |||
| }(); | |||
| if (handler->getCurrentState().isSelected()) | |||
| traits |= UIAccessibilityTraitSelected; | |||
| if (auto* valueInterface = getValueInterface (self)) | |||
| if (! valueInterface->isReadOnly() && valueInterface->getRange().isValid()) | |||
| traits |= UIAccessibilityTraitAdjustable; | |||
| } | |||
| return traits | sendSuperclassMessage<UIAccessibilityTraits> (self, @selector (accessibilityTraits)); | |||
| } | |||
| 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 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; | |||
| } | |||
| static NSInteger getAccessibilityContainerType (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (getTableInterface (self) != nullptr) | |||
| return juceUIAccessibilityContainerTypeDataTable; | |||
| const auto role = handler->getRole(); | |||
| if (role == AccessibilityRole::popupMenu | |||
| || role == AccessibilityRole::list | |||
| || role == AccessibilityRole::tree) | |||
| { | |||
| return juceUIAccessibilityContainerTypeList; | |||
| } | |||
| } | |||
| return juceUIAccessibilityContainerTypeNone; | |||
| } | |||
| 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; | |||
| } | |||
| //============================================================================== | |||
| 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(); | |||
| } | |||
| 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{}; | |||
| }(); | |||
| const auto shouldMoveToHandler = (eventType == InternalAccessibilityEvent::elementCreated | |||
| || eventType == InternalAccessibilityEvent::windowOpened | |||
| || (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false))); | |||
| if (notification != UIAccessibilityNotifications{}) | |||
| sendAccessibilityEvent (notification, shouldMoveToHandler ? (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 | |||
| @@ -56,21 +56,9 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| class AccessibilityElement : public ObjCClass<NSAccessibilityElement<NSAccessibility>> | |||
| class AccessibilityElement : public AccessibleObjCClass<NSAccessibilityElement<NSAccessibility>> | |||
| { | |||
| private: | |||
| struct Deleter | |||
| { | |||
| void operator() (NSAccessibilityElement<NSAccessibility>* element) const | |||
| { | |||
| object_setInstanceVariable (element, "handler", nullptr); | |||
| [element release]; | |||
| } | |||
| }; | |||
| public: | |||
| using Holder = std::unique_ptr<NSAccessibilityElement<NSAccessibility>, Deleter>; | |||
| static Holder create (AccessibilityHandler& handler) | |||
| { | |||
| #if JUCE_OBJC_HAS_AVAILABLE_FEATURE | |||
| @@ -87,10 +75,8 @@ private: | |||
| } | |||
| private: | |||
| AccessibilityElement() : ObjCClass<NSAccessibilityElement<NSAccessibility>> ("JUCEAccessibilityElement_") | |||
| AccessibilityElement() | |||
| { | |||
| addIvar<AccessibilityHandler*> ("handler"); | |||
| addMethod (@selector (accessibilityNotifiesWhenDestroyed), getAccessibilityNotifiesWhenDestroyed, "c@:"); | |||
| addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:"); | |||
| addMethod (@selector (isAccessibilityEnabled), getIsAccessibilityEnabled, "c@:"); | |||
| @@ -160,30 +146,7 @@ private: | |||
| registerClass(); | |||
| } | |||
| private: | |||
| static AccessibilityHandler* getHandler (id self) { return getIvar<AccessibilityHandler*> (self, "handler"); } | |||
| template <typename MemberFn> | |||
| static auto getInterface (id self, MemberFn fn) noexcept -> decltype ((std::declval<AccessibilityHandler>().*fn)()) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return (handler->*fn)(); | |||
| return nullptr; | |||
| } | |||
| static AccessibilityTextInterface* getTextInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTextInterface); } | |||
| static AccessibilityValueInterface* getValueInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getValueInterface); } | |||
| static AccessibilityTableInterface* getTableInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTableInterface); } | |||
| static AccessibilityCellInterface* getCellInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getCellInterface); } | |||
| static bool hasEditableText (AccessibilityHandler& handler) noexcept | |||
| { | |||
| return handler.getRole() == AccessibilityRole::editableText | |||
| && handler.getTextInterface() != nullptr | |||
| && ! handler.getTextInterface()->isReadOnly(); | |||
| } | |||
| //============================================================================== | |||
| static bool isSelectable (AccessibleState state) noexcept | |||
| { | |||
| return state.isSelectable() || state.isMultiSelectable(); | |||
| @@ -229,27 +192,9 @@ private: | |||
| } | |||
| } | |||
| static BOOL performActionIfSupported (id self, AccessibilityActionType actionType) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| if (handler->getActions().invoke (actionType)) | |||
| return YES; | |||
| return NO; | |||
| } | |||
| //============================================================================== | |||
| static BOOL getAccessibilityNotifiesWhenDestroyed (id, SEL) { return YES; } | |||
| static BOOL getIsAccessibilityElement (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return ! handler->isIgnored() | |||
| && handler->getRole() != AccessibilityRole::window; | |||
| return NO; | |||
| } | |||
| static BOOL getIsAccessibilityEnabled (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| @@ -317,23 +262,6 @@ private: | |||
| return nil; | |||
| } | |||
| static NSArray* getAccessibilityChildren (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| auto children = handler->getChildren(); | |||
| NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()]; | |||
| for (auto* childHandler : children) | |||
| [accessibleChildren addObject: (id) childHandler->getNativeImplementation()]; | |||
| return accessibleChildren; | |||
| } | |||
| return nil; | |||
| } | |||
| static NSArray* getAccessibilitySelectedChildren (id self, SEL) | |||
| { | |||
| return getSelectedChildren ([self accessibilityChildren]); | |||
| @@ -379,14 +307,6 @@ private: | |||
| } | |||
| } | |||
| static BOOL getIsAccessibilityModal (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return handler->getComponent().isCurrentlyModal(); | |||
| return NO; | |||
| } | |||
| static NSRect getAccessibilityFrame (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| @@ -481,26 +401,6 @@ private: | |||
| return NSAccessibilityUnknownRole; | |||
| } | |||
| static NSString* getAccessibilityTitle (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| auto title = handler->getTitle(); | |||
| if (title.isEmpty() && handler->getComponent().isOnDesktop()) | |||
| title = getAccessibleApplicationOrPluginName(); | |||
| NSString* nsString = juceStringToNS (title); | |||
| if (nsString != nil && [[self accessibilityValue] isEqual: nsString]) | |||
| return @""; | |||
| return nsString; | |||
| } | |||
| return nil; | |||
| } | |||
| static NSString* getAccessibilityLabel (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| @@ -509,47 +409,6 @@ private: | |||
| return nil; | |||
| } | |||
| static NSString* getAccessibilityHelp (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return juceStringToNS (handler->getHelp()); | |||
| return nil; | |||
| } | |||
| static id getAccessibilityValue (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (auto* textInterface = handler->getTextInterface()) | |||
| return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })); | |||
| if (handler->getCurrentState().isCheckable()) | |||
| return handler->getCurrentState().isChecked() ? @(1) : @(0); | |||
| if (auto* valueInterface = handler->getValueInterface()) | |||
| return juceStringToNS (valueInterface->getCurrentValueAsString()); | |||
| } | |||
| return nil; | |||
| } | |||
| static void setAccessibilityValue (id self, SEL, NSString* value) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (hasEditableText (*handler)) | |||
| { | |||
| handler->getTextInterface()->setText (nsStringToJuce (value)); | |||
| return; | |||
| } | |||
| if (auto* valueInterface = handler->getValueInterface()) | |||
| if (! valueInterface->isReadOnly()) | |||
| valueInterface->setValueAsString (nsStringToJuce (value)); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| static NSInteger getAccessibilityInsertionPointLineNumber (id self, SEL) | |||
| { | |||
| @@ -694,14 +553,6 @@ private: | |||
| } | |||
| //============================================================================== | |||
| static NSInteger getAccessibilityRowCount (id self, SEL) | |||
| { | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| return tableInterface->getNumRows(); | |||
| return 0; | |||
| } | |||
| static NSArray* getAccessibilityRows (id self, SEL) | |||
| { | |||
| NSMutableArray* rows = [[NSMutableArray new] autorelease]; | |||
| @@ -737,14 +588,6 @@ private: | |||
| setSelectedChildren ([self accessibilityRows], selected); | |||
| } | |||
| static NSInteger getAccessibilityColumnCount (id self, SEL) | |||
| { | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| return tableInterface->getNumColumns(); | |||
| return 0; | |||
| } | |||
| static NSArray* getAccessibilityColumns (id self, SEL) | |||
| { | |||
| NSMutableArray* columns = [[NSMutableArray new] autorelease]; | |||
| @@ -781,24 +624,6 @@ private: | |||
| } | |||
| //============================================================================== | |||
| static NSRange getAccessibilityRowIndexRange (id self, SEL) | |||
| { | |||
| if (auto* cellInterface = getCellInterface (self)) | |||
| return NSMakeRange ((NSUInteger) cellInterface->getRowIndex(), | |||
| (NSUInteger) cellInterface->getRowSpan()); | |||
| return NSMakeRange (0, 0); | |||
| } | |||
| static NSRange getAccessibilityColumnIndexRange (id self, SEL) | |||
| { | |||
| if (auto* cellInterface = getCellInterface (self)) | |||
| return NSMakeRange ((NSUInteger) cellInterface->getColumnIndex(), | |||
| (NSUInteger) cellInterface->getColumnSpan()); | |||
| return NSMakeRange (0, 0); | |||
| } | |||
| static NSInteger getAccessibilityIndex (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| @@ -836,53 +661,9 @@ private: | |||
| } | |||
| //============================================================================== | |||
| static BOOL accessibilityPerformPress (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::press); } | |||
| static BOOL accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); } | |||
| static BOOL accessibilityPerformRaise (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; } | |||
| static BOOL accessibilityPerformIncrement (id self, SEL) | |||
| { | |||
| if (auto* valueInterface = getValueInterface (self)) | |||
| { | |||
| if (! valueInterface->isReadOnly()) | |||
| { | |||
| auto range = valueInterface->getRange(); | |||
| if (range.isValid()) | |||
| { | |||
| valueInterface->setValue (jlimit (range.getMinimumValue(), | |||
| range.getMaximumValue(), | |||
| valueInterface->getCurrentValue() + range.getInterval())); | |||
| return YES; | |||
| } | |||
| } | |||
| } | |||
| return NO; | |||
| } | |||
| static BOOL accessibilityPerformDecrement (id self, SEL) | |||
| { | |||
| if (auto* valueInterface = getValueInterface (self)) | |||
| { | |||
| if (! valueInterface->isReadOnly()) | |||
| { | |||
| auto range = valueInterface->getRange(); | |||
| if (range.isValid()) | |||
| { | |||
| valueInterface->setValue (jlimit (range.getMinimumValue(), | |||
| range.getMaximumValue(), | |||
| valueInterface->getCurrentValue() - range.getInterval())); | |||
| return YES; | |||
| } | |||
| } | |||
| } | |||
| return NO; | |||
| } | |||
| static BOOL accessibilityPerformDelete (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| @@ -0,0 +1,275 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| template <typename Base> | |||
| class AccessibleObjCClass : public ObjCClass<Base> | |||
| { | |||
| private: | |||
| struct Deleter | |||
| { | |||
| void operator() (Base* element) const | |||
| { | |||
| object_setInstanceVariable (element, "handler", nullptr); | |||
| [element release]; | |||
| } | |||
| }; | |||
| public: | |||
| using Holder = std::unique_ptr<Base, Deleter>; | |||
| protected: | |||
| AccessibleObjCClass() : ObjCClass<Base> ("JUCEAccessibilityElement_") | |||
| { | |||
| ObjCClass<Base>::template addIvar<AccessibilityHandler*> ("handler"); | |||
| } | |||
| //============================================================================== | |||
| static AccessibilityHandler* getHandler (id self) | |||
| { | |||
| return getIvar<AccessibilityHandler*> (self, "handler"); | |||
| } | |||
| template <typename MemberFn> | |||
| static auto getInterface (id self, MemberFn fn) noexcept -> decltype ((std::declval<AccessibilityHandler>().*fn)()) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return (handler->*fn)(); | |||
| return nullptr; | |||
| } | |||
| static AccessibilityTextInterface* getTextInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTextInterface); } | |||
| static AccessibilityValueInterface* getValueInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getValueInterface); } | |||
| static AccessibilityTableInterface* getTableInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTableInterface); } | |||
| static AccessibilityCellInterface* getCellInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getCellInterface); } | |||
| static bool hasEditableText (AccessibilityHandler& handler) noexcept | |||
| { | |||
| return handler.getRole() == AccessibilityRole::editableText | |||
| && handler.getTextInterface() != nullptr | |||
| && ! handler.getTextInterface()->isReadOnly(); | |||
| } | |||
| //============================================================================== | |||
| static BOOL getIsAccessibilityElement (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return ! handler->isIgnored() | |||
| && handler->getRole() != AccessibilityRole::window; | |||
| return NO; | |||
| } | |||
| static id getAccessibilityValue (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (auto* textInterface = handler->getTextInterface()) | |||
| return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })); | |||
| if (handler->getCurrentState().isCheckable()) | |||
| { | |||
| return handler->getCurrentState().isChecked() | |||
| #if JUCE_IOS | |||
| ? @"1" : @"0"; | |||
| #else | |||
| ? @(1) : @(0); | |||
| #endif | |||
| } | |||
| if (auto* valueInterface = handler->getValueInterface()) | |||
| return juceStringToNS (valueInterface->getCurrentValueAsString()); | |||
| } | |||
| return nil; | |||
| } | |||
| static void setAccessibilityValue (id self, SEL, NSString* value) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (hasEditableText (*handler)) | |||
| { | |||
| handler->getTextInterface()->setText (nsStringToJuce (value)); | |||
| return; | |||
| } | |||
| if (auto* valueInterface = handler->getValueInterface()) | |||
| if (! valueInterface->isReadOnly()) | |||
| valueInterface->setValueAsString (nsStringToJuce (value)); | |||
| } | |||
| } | |||
| static BOOL performActionIfSupported (id self, AccessibilityActionType actionType) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| if (handler->getActions().invoke (actionType)) | |||
| return YES; | |||
| return NO; | |||
| } | |||
| static BOOL accessibilityPerformPress (id self, SEL) | |||
| { | |||
| return performActionIfSupported (self, AccessibilityActionType::press); | |||
| } | |||
| static BOOL accessibilityPerformIncrement (id self, SEL) | |||
| { | |||
| if (auto* valueInterface = getValueInterface (self)) | |||
| { | |||
| if (! valueInterface->isReadOnly()) | |||
| { | |||
| auto range = valueInterface->getRange(); | |||
| if (range.isValid()) | |||
| { | |||
| valueInterface->setValue (jlimit (range.getMinimumValue(), | |||
| range.getMaximumValue(), | |||
| valueInterface->getCurrentValue() + range.getInterval())); | |||
| return YES; | |||
| } | |||
| } | |||
| } | |||
| return NO; | |||
| } | |||
| static BOOL accessibilityPerformDecrement (id self, SEL) | |||
| { | |||
| if (auto* valueInterface = getValueInterface (self)) | |||
| { | |||
| if (! valueInterface->isReadOnly()) | |||
| { | |||
| auto range = valueInterface->getRange(); | |||
| if (range.isValid()) | |||
| { | |||
| valueInterface->setValue (jlimit (range.getMinimumValue(), | |||
| range.getMaximumValue(), | |||
| valueInterface->getCurrentValue() - range.getInterval())); | |||
| return YES; | |||
| } | |||
| } | |||
| } | |||
| return NO; | |||
| } | |||
| static NSString* getAccessibilityTitle (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| auto title = handler->getTitle(); | |||
| if (title.isEmpty() && handler->getComponent().isOnDesktop()) | |||
| title = getAccessibleApplicationOrPluginName(); | |||
| NSString* nsString = juceStringToNS (title); | |||
| if (nsString != nil && [[self accessibilityValue] isEqual: nsString]) | |||
| return @""; | |||
| return nsString; | |||
| } | |||
| return nil; | |||
| } | |||
| static NSString* getAccessibilityHelp (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return juceStringToNS (handler->getHelp()); | |||
| return nil; | |||
| } | |||
| static BOOL getIsAccessibilityModal (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| return handler->getComponent().isCurrentlyModal(); | |||
| return NO; | |||
| } | |||
| static NSArray* getAccessibilityChildren (id self, SEL) | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| auto children = handler->getChildren(); | |||
| NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()]; | |||
| for (auto* childHandler : children) | |||
| [accessibleChildren addObject: (id) childHandler->getNativeImplementation()]; | |||
| return accessibleChildren; | |||
| } | |||
| return nil; | |||
| } | |||
| static NSInteger getAccessibilityRowCount (id self, SEL) | |||
| { | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| return tableInterface->getNumRows(); | |||
| return 0; | |||
| } | |||
| static NSInteger getAccessibilityColumnCount (id self, SEL) | |||
| { | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| return tableInterface->getNumColumns(); | |||
| return 0; | |||
| } | |||
| static NSRange getAccessibilityRowIndexRange (id self, SEL) | |||
| { | |||
| if (auto* cellInterface = getCellInterface (self)) | |||
| return NSMakeRange ((NSUInteger) cellInterface->getRowIndex(), | |||
| (NSUInteger) cellInterface->getRowSpan()); | |||
| return NSMakeRange (0, 0); | |||
| } | |||
| static NSRange getAccessibilityColumnIndexRange (id self, SEL) | |||
| { | |||
| if (auto* cellInterface = getCellInterface (self)) | |||
| return NSMakeRange ((NSUInteger) cellInterface->getColumnIndex(), | |||
| (NSUInteger) cellInterface->getColumnSpan()); | |||
| return NSMakeRange (0, 0); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass) | |||
| }; | |||
| } | |||
| @@ -136,6 +136,11 @@ using namespace juce; | |||
| - (BOOL) canBecomeFirstResponder; | |||
| - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text; | |||
| - (BOOL) isAccessibilityElement; | |||
| - (id) accessibilityElementAtIndex: (NSInteger) index; | |||
| - (NSInteger) accessibilityElementCount; | |||
| - (NSInteger) indexOfAccessibilityElement: (id) element; | |||
| @end | |||
| //============================================================================== | |||
| @@ -540,6 +545,30 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches; | |||
| nsStringToJuce (text)); | |||
| } | |||
| - (BOOL) isAccessibilityElement | |||
| { | |||
| return NO; | |||
| } | |||
| - (id) accessibilityElementAtIndex: (NSInteger) index | |||
| { | |||
| if (owner != nullptr) | |||
| if (auto* handler = owner->getComponent().getAccessibilityHandler()) | |||
| return (id) handler->getNativeImplementation(); | |||
| return nil; | |||
| } | |||
| - (NSInteger) accessibilityElementCount | |||
| { | |||
| return owner != nullptr ? 1 : 0; | |||
| } | |||
| - (NSInteger) indexOfAccessibilityElement: (id) element | |||
| { | |||
| return 0; | |||
| } | |||
| @end | |||
| //============================================================================== | |||