@@ -276,7 +276,10 @@ bool getComponentAsyncLayerBackedViewDisabled (juce::Component& comp) | |||||
} // namespace juce | } // namespace juce | ||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "native/accessibility/juce_mac_AccessibilitySharedCode.mm" | |||||
#if JUCE_IOS | #if JUCE_IOS | ||||
#include "native/accessibility/juce_ios_Accessibility.mm" | |||||
#include "native/juce_ios_UIViewComponentPeer.mm" | #include "native/juce_ios_UIViewComponentPeer.mm" | ||||
#include "native/juce_ios_Windowing.mm" | #include "native/juce_ios_Windowing.mm" | ||||
#include "native/juce_ios_FileChooser.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: | 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: | public: | ||||
using Holder = std::unique_ptr<NSAccessibilityElement<NSAccessibility>, Deleter>; | |||||
static Holder create (AccessibilityHandler& handler) | static Holder create (AccessibilityHandler& handler) | ||||
{ | { | ||||
#if JUCE_OBJC_HAS_AVAILABLE_FEATURE | #if JUCE_OBJC_HAS_AVAILABLE_FEATURE | ||||
@@ -87,10 +75,8 @@ private: | |||||
} | } | ||||
private: | private: | ||||
AccessibilityElement() : ObjCClass<NSAccessibilityElement<NSAccessibility>> ("JUCEAccessibilityElement_") | |||||
AccessibilityElement() | |||||
{ | { | ||||
addIvar<AccessibilityHandler*> ("handler"); | |||||
addMethod (@selector (accessibilityNotifiesWhenDestroyed), getAccessibilityNotifiesWhenDestroyed, "c@:"); | addMethod (@selector (accessibilityNotifiesWhenDestroyed), getAccessibilityNotifiesWhenDestroyed, "c@:"); | ||||
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:"); | addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:"); | ||||
addMethod (@selector (isAccessibilityEnabled), getIsAccessibilityEnabled, "c@:"); | addMethod (@selector (isAccessibilityEnabled), getIsAccessibilityEnabled, "c@:"); | ||||
@@ -160,30 +146,7 @@ private: | |||||
registerClass(); | 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 | static bool isSelectable (AccessibleState state) noexcept | ||||
{ | { | ||||
return state.isSelectable() || state.isMultiSelectable(); | 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 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) | static BOOL getIsAccessibilityEnabled (id self, SEL) | ||||
{ | { | ||||
if (auto* handler = getHandler (self)) | if (auto* handler = getHandler (self)) | ||||
@@ -317,23 +262,6 @@ private: | |||||
return nil; | 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) | static NSArray* getAccessibilitySelectedChildren (id self, SEL) | ||||
{ | { | ||||
return getSelectedChildren ([self accessibilityChildren]); | 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) | static NSRect getAccessibilityFrame (id self, SEL) | ||||
{ | { | ||||
if (auto* handler = getHandler (self)) | if (auto* handler = getHandler (self)) | ||||
@@ -481,26 +401,6 @@ private: | |||||
return NSAccessibilityUnknownRole; | 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) | static NSString* getAccessibilityLabel (id self, SEL) | ||||
{ | { | ||||
if (auto* handler = getHandler (self)) | if (auto* handler = getHandler (self)) | ||||
@@ -509,47 +409,6 @@ private: | |||||
return nil; | 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) | 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) | static NSArray* getAccessibilityRows (id self, SEL) | ||||
{ | { | ||||
NSMutableArray* rows = [[NSMutableArray new] autorelease]; | NSMutableArray* rows = [[NSMutableArray new] autorelease]; | ||||
@@ -737,14 +588,6 @@ private: | |||||
setSelectedChildren ([self accessibilityRows], selected); | 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) | static NSArray* getAccessibilityColumns (id self, SEL) | ||||
{ | { | ||||
NSMutableArray* columns = [[NSMutableArray new] autorelease]; | 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) | static NSInteger getAccessibilityIndex (id self, SEL) | ||||
{ | { | ||||
if (auto* handler = getHandler (self)) | 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 accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); } | ||||
static BOOL accessibilityPerformRaise (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; } | 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) | static BOOL accessibilityPerformDelete (id self, SEL) | ||||
{ | { | ||||
if (auto* handler = getHandler (self)) | 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) canBecomeFirstResponder; | ||||
- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text; | - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text; | ||||
- (BOOL) isAccessibilityElement; | |||||
- (id) accessibilityElementAtIndex: (NSInteger) index; | |||||
- (NSInteger) accessibilityElementCount; | |||||
- (NSInteger) indexOfAccessibilityElement: (id) element; | |||||
@end | @end | ||||
//============================================================================== | //============================================================================== | ||||
@@ -540,6 +545,30 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches; | |||||
nsStringToJuce (text)); | 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 | @end | ||||
//============================================================================== | //============================================================================== | ||||