/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { //============================================================================== struct AccessibleObjCClassDeleter { template void operator() (ElementType* element) const { juceFreeAccessibilityPlatformSpecificData (element); object_setInstanceVariable (element, "handler", nullptr); [element release]; } }; template class AccessibleObjCClass : public ObjCClass { public: using Holder = std::unique_ptr; protected: AccessibleObjCClass() : AccessibleObjCClass ("JUCEAccessibilityElement_") {} explicit AccessibleObjCClass (const char* name) : ObjCClass (name) { ObjCClass::template addIvar ("handler"); } //============================================================================== static AccessibilityHandler* getHandler (id self) { return getIvar (self, "handler"); } template static auto getInterface (id self, MemberFn fn) noexcept -> decltype ((std::declval().*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 id getAccessibilityValueFromInterfaces (const AccessibilityHandler& handler) { if (auto* textInterface = handler.getTextInterface()) return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })); if (auto* valueInterface = handler.getValueInterface()) return juceStringToNS (valueInterface->getCurrentValueAsString()); return nil; } //============================================================================== static BOOL getIsAccessibilityElement (id self, SEL) { if (auto* handler = getHandler (self)) return ! handler->isIgnored() && handler->getRole() != AccessibilityRole::window; return NO; } 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) { if (auto* handler = getHandler (self)) if (handler->getCurrentState().isCheckable() && handler->getActions().invoke (AccessibilityActionType::toggle)) return YES; 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 NSInteger getAccessibilityRowCount (id self, SEL) { if (auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface)) if (auto* tableInterface = tableHandler->getTableInterface()) return tableInterface->getNumRows(); return 0; } static NSInteger getAccessibilityColumnCount (id self, SEL) { if (auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface)) if (auto* tableInterface = tableHandler->getTableInterface()) return tableInterface->getNumColumns(); return 0; } template static NSRange getCellDimensions (id self, Getter getter) { const auto notFound = NSMakeRange (NSNotFound, 0); auto* handler = getHandler (self); if (handler == nullptr) return notFound; auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface); if (tableHandler == nullptr) return notFound; auto* tableInterface = tableHandler->getTableInterface(); if (tableInterface == nullptr) return notFound; const auto result = (tableInterface->*getter) (*handler); if (! result.hasValue()) return notFound; return NSMakeRange ((NSUInteger) result->begin, (NSUInteger) result->num); } static NSRange getAccessibilityRowIndexRange (id self, SEL) { return getCellDimensions (self, &AccessibilityTableInterface::getRowSpan); } static NSRange getAccessibilityColumnIndexRange (id self, SEL) { return getCellDimensions (self, &AccessibilityTableInterface::getColumnSpan); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass) }; }