Browse Source

Accessibility: Add iOS support

v6.1.6
ed 3 years ago
parent
commit
30654fb8ec
5 changed files with 727 additions and 222 deletions
  1. +3
    -0
      modules/juce_gui_basics/juce_gui_basics.cpp
  2. +417
    -0
      modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm
  3. +3
    -222
      modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm
  4. +275
    -0
      modules/juce_gui_basics/native/accessibility/juce_mac_AccessibilitySharedCode.mm
  5. +29
    -0
      modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm

+ 3
- 0
modules/juce_gui_basics/juce_gui_basics.cpp View File

@@ -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"


+ 417
- 0
modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm View File

@@ -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

+ 3
- 222
modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm View File

@@ -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))


+ 275
- 0
modules/juce_gui_basics/native/accessibility/juce_mac_AccessibilitySharedCode.mm View File

@@ -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)
};
}

+ 29
- 0
modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm View File

@@ -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
//==============================================================================


Loading…
Cancel
Save