The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

585 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. static void juceFreeAccessibilityPlatformSpecificData (UIAccessibilityElement* element)
  19. {
  20. if (auto* container = juce::getIvar<UIAccessibilityElement*> (element, "container"))
  21. {
  22. object_setInstanceVariable (element, "container", nullptr);
  23. object_setInstanceVariable (container, "handler", nullptr);
  24. [container release];
  25. }
  26. }
  27. namespace juce
  28. {
  29. #if (defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0)
  30. #define JUCE_IOS_CONTAINER_API_AVAILABLE 1
  31. #endif
  32. constexpr auto juceUIAccessibilityContainerTypeNone =
  33. #if JUCE_IOS_CONTAINER_API_AVAILABLE
  34. UIAccessibilityContainerTypeNone;
  35. #else
  36. 0;
  37. #endif
  38. constexpr auto juceUIAccessibilityContainerTypeDataTable =
  39. #if JUCE_IOS_CONTAINER_API_AVAILABLE
  40. UIAccessibilityContainerTypeDataTable;
  41. #else
  42. 1;
  43. #endif
  44. constexpr auto juceUIAccessibilityContainerTypeList =
  45. #if JUCE_IOS_CONTAINER_API_AVAILABLE
  46. UIAccessibilityContainerTypeList;
  47. #else
  48. 2;
  49. #endif
  50. #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
  51. //==============================================================================
  52. static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler)
  53. {
  54. const auto children = handler.getChildren();
  55. NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
  56. [accessibleChildren addObject: (id) handler.getNativeImplementation()];
  57. for (auto* childHandler : children)
  58. {
  59. id accessibleElement = [&childHandler]
  60. {
  61. id native = (id) childHandler->getNativeImplementation();
  62. if (childHandler->getChildren().size() > 0)
  63. return [native accessibilityContainer];
  64. return native;
  65. }();
  66. if (accessibleElement != nil)
  67. [accessibleChildren addObject: accessibleElement];
  68. }
  69. return accessibleChildren;
  70. }
  71. //==============================================================================
  72. class AccessibilityHandler::AccessibilityNativeImpl
  73. {
  74. public:
  75. explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
  76. : accessibilityElement (AccessibilityElement::create (handler))
  77. {
  78. }
  79. UIAccessibilityElement* getAccessibilityElement() const noexcept
  80. {
  81. return accessibilityElement.get();
  82. }
  83. private:
  84. //==============================================================================
  85. class AccessibilityContainer : public ObjCClass<UIAccessibilityElement>
  86. {
  87. public:
  88. AccessibilityContainer()
  89. : ObjCClass ("JUCEUIAccessibilityElementContainer_")
  90. {
  91. addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
  92. addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
  93. addMethod (@selector (accessibilityElements), getAccessibilityElements, "@@:");
  94. #if JUCE_IOS_CONTAINER_API_AVAILABLE
  95. if (@available (iOS 11.0, *))
  96. addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType, "i@:");
  97. #endif
  98. addIvar<AccessibilityHandler*> ("handler");
  99. registerClass();
  100. }
  101. private:
  102. static AccessibilityHandler* getHandler (id self)
  103. {
  104. return getIvar<AccessibilityHandler*> (self, "handler");
  105. }
  106. static BOOL getIsAccessibilityElement (id, SEL)
  107. {
  108. return NO;
  109. }
  110. static CGRect getAccessibilityFrame (id self, SEL)
  111. {
  112. if (auto* handler = getHandler (self))
  113. return convertToCGRect (handler->getComponent().getScreenBounds());
  114. return CGRectZero;
  115. }
  116. static NSArray* getAccessibilityElements (id self, SEL)
  117. {
  118. if (auto* handler = getHandler (self))
  119. return getContainerAccessibilityElements (*handler);
  120. return nil;
  121. }
  122. static NSInteger getAccessibilityContainerType (id self, SEL)
  123. {
  124. if (auto* handler = getHandler (self))
  125. {
  126. if (handler->getTableInterface() != nullptr)
  127. return juceUIAccessibilityContainerTypeDataTable;
  128. const auto role = handler->getRole();
  129. if (role == AccessibilityRole::popupMenu
  130. || role == AccessibilityRole::list
  131. || role == AccessibilityRole::tree)
  132. {
  133. return juceUIAccessibilityContainerTypeList;
  134. }
  135. }
  136. return juceUIAccessibilityContainerTypeNone;
  137. }
  138. };
  139. //==============================================================================
  140. class AccessibilityElement : public AccessibleObjCClass<UIAccessibilityElement>
  141. {
  142. public:
  143. enum class Type { defaultElement, textElement };
  144. static Holder create (AccessibilityHandler& handler)
  145. {
  146. static AccessibilityElement cls { Type::defaultElement };
  147. static AccessibilityElement textCls { Type::textElement };
  148. id instance = (hasEditableText (handler) ? textCls : cls).createInstance();
  149. Holder element ([instance initWithAccessibilityContainer: (id) handler.getComponent().getWindowHandle()]);
  150. object_setInstanceVariable (element.get(), "handler", &handler);
  151. return element;
  152. }
  153. AccessibilityElement (Type elementType)
  154. {
  155. addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
  156. addMethod (@selector (accessibilityContainer), getAccessibilityContainer, "@@:");
  157. addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
  158. addMethod (@selector (accessibilityTraits), getAccessibilityTraits, "i@:");
  159. addMethod (@selector (accessibilityLabel), getAccessibilityTitle, "@@:");
  160. addMethod (@selector (accessibilityHint), getAccessibilityHelp, "@@:");
  161. addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
  162. addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
  163. addMethod (@selector (accessibilityElementDidBecomeFocused), onFocusGain, "v@:");
  164. addMethod (@selector (accessibilityElementDidLoseFocus), onFocusLoss, "v@:");
  165. addMethod (@selector (accessibilityElementIsFocused), isFocused, "c@:");
  166. addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal, "c@:");
  167. addMethod (@selector (accessibilityActivate), accessibilityPerformActivate, "c@:");
  168. addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement, "c@:");
  169. addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement, "c@:");
  170. addMethod (@selector (accessibilityPerformEscape), accessibilityPerformEscape, "c@:");
  171. #if JUCE_IOS_CONTAINER_API_AVAILABLE
  172. if (@available (iOS 11.0, *))
  173. {
  174. addMethod (@selector (accessibilityDataTableCellElementForRow:column:), getAccessibilityDataTableCellElementForRowColumn, "@@:ii");
  175. addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
  176. addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
  177. addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:");
  178. addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:");
  179. }
  180. #endif
  181. if (elementType == Type::textElement)
  182. {
  183. addMethod (@selector (accessibilityLineNumberForPoint:), getAccessibilityLineNumberForPoint, "i@:", @encode (CGPoint));
  184. addMethod (@selector (accessibilityContentForLineNumber:), getAccessibilityContentForLineNumber, "@@:i");
  185. addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber, @encode (CGRect), "@:i");
  186. addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent, "@@:");
  187. addProtocol (@protocol (UIAccessibilityReadingContent));
  188. }
  189. addIvar<UIAccessibilityElement*> ("container");
  190. registerClass();
  191. }
  192. private:
  193. //==============================================================================
  194. static UIAccessibilityElement* getContainer (id self)
  195. {
  196. return getIvar<UIAccessibilityElement*> (self, "container");
  197. }
  198. //==============================================================================
  199. static id getAccessibilityContainer (id self, SEL)
  200. {
  201. if (auto* handler = getHandler (self))
  202. {
  203. if (handler->getComponent().isOnDesktop())
  204. return (id) handler->getComponent().getWindowHandle();
  205. if (handler->getChildren().size() > 0)
  206. {
  207. if (UIAccessibilityElement* container = getContainer (self))
  208. return container;
  209. static AccessibilityContainer cls;
  210. id windowHandle = (id) handler->getComponent().getWindowHandle();
  211. UIAccessibilityElement* container = [cls.createInstance() initWithAccessibilityContainer: windowHandle];
  212. [container retain];
  213. object_setInstanceVariable (container, "handler", handler);
  214. object_setInstanceVariable (self, "container", container);
  215. return container;
  216. }
  217. if (auto* parent = handler->getParent())
  218. return [(id) parent->getNativeImplementation() accessibilityContainer];
  219. }
  220. return nil;
  221. }
  222. static CGRect getAccessibilityFrame (id self, SEL)
  223. {
  224. if (auto* handler = getHandler (self))
  225. return convertToCGRect (handler->getComponent().getScreenBounds());
  226. return CGRectZero;
  227. }
  228. static UIAccessibilityTraits getAccessibilityTraits (id self, SEL)
  229. {
  230. auto traits = UIAccessibilityTraits{};
  231. if (auto* handler = getHandler (self))
  232. {
  233. traits |= [&handler]
  234. {
  235. switch (handler->getRole())
  236. {
  237. case AccessibilityRole::button:
  238. case AccessibilityRole::toggleButton:
  239. case AccessibilityRole::radioButton:
  240. case AccessibilityRole::comboBox: return UIAccessibilityTraitButton;
  241. case AccessibilityRole::label:
  242. case AccessibilityRole::staticText: return UIAccessibilityTraitStaticText;
  243. case AccessibilityRole::image: return UIAccessibilityTraitImage;
  244. case AccessibilityRole::tableHeader: return UIAccessibilityTraitHeader;
  245. case AccessibilityRole::hyperlink: return UIAccessibilityTraitLink;
  246. case AccessibilityRole::editableText: return UIAccessibilityTraitKeyboardKey;
  247. case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
  248. case AccessibilityRole::slider:
  249. case AccessibilityRole::menuItem:
  250. case AccessibilityRole::menuBar:
  251. case AccessibilityRole::popupMenu:
  252. case AccessibilityRole::table:
  253. case AccessibilityRole::column:
  254. case AccessibilityRole::row:
  255. case AccessibilityRole::cell:
  256. case AccessibilityRole::list:
  257. case AccessibilityRole::listItem:
  258. case AccessibilityRole::tree:
  259. case AccessibilityRole::treeItem:
  260. case AccessibilityRole::progressBar:
  261. case AccessibilityRole::group:
  262. case AccessibilityRole::dialogWindow:
  263. case AccessibilityRole::window:
  264. case AccessibilityRole::scrollBar:
  265. case AccessibilityRole::tooltip:
  266. case AccessibilityRole::splashScreen:
  267. case AccessibilityRole::unspecified: break;
  268. }
  269. return UIAccessibilityTraitNone;
  270. }();
  271. const auto state = handler->getCurrentState();
  272. if (state.isSelected() || state.isChecked())
  273. traits |= UIAccessibilityTraitSelected;
  274. if (auto* valueInterface = getValueInterface (self))
  275. if (! valueInterface->isReadOnly() && valueInterface->getRange().isValid())
  276. traits |= UIAccessibilityTraitAdjustable;
  277. }
  278. return traits | sendSuperclassMessage<UIAccessibilityTraits> (self, @selector (accessibilityTraits));
  279. }
  280. static NSString* getAccessibilityValue (id self, SEL)
  281. {
  282. if (auto* handler = getHandler (self))
  283. {
  284. if (handler->getCurrentState().isCheckable())
  285. return handler->getCurrentState().isChecked() ? @"1" : @"0";
  286. return (NSString*) getAccessibilityValueFromInterfaces (*handler);
  287. }
  288. return nil;
  289. }
  290. static void onFocusGain (id self, SEL)
  291. {
  292. if (auto* handler = getHandler (self))
  293. {
  294. const WeakReference<Component> safeComponent (&handler->getComponent());
  295. performActionIfSupported (self, AccessibilityActionType::focus);
  296. if (safeComponent != nullptr)
  297. handler->grabFocus();
  298. }
  299. }
  300. static void onFocusLoss (id self, SEL)
  301. {
  302. if (auto* handler = getHandler (self))
  303. handler->giveAwayFocus();
  304. }
  305. static BOOL isFocused (id self, SEL)
  306. {
  307. if (auto* handler = getHandler (self))
  308. return handler->hasFocus (false);
  309. return NO;
  310. }
  311. static BOOL accessibilityPerformActivate (id self, SEL)
  312. {
  313. if (auto* handler = getHandler (self))
  314. {
  315. // occasionaly VoiceOver sends accessibilityActivate to the wrong element, so we first query
  316. // which element it thinks has focus and forward the event on to that element if it differs
  317. id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
  318. if (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
  319. return [focusedElement accessibilityActivate];
  320. if (handler->hasFocus (false))
  321. return accessibilityPerformPress (self, {});
  322. }
  323. return NO;
  324. }
  325. static BOOL accessibilityPerformEscape (id self, SEL)
  326. {
  327. if (auto* handler = getHandler (self))
  328. {
  329. if (auto* modal = Component::getCurrentlyModalComponent())
  330. {
  331. if (auto* modalHandler = modal->getAccessibilityHandler())
  332. {
  333. if (modalHandler == handler || modalHandler->isParentOf (handler))
  334. {
  335. modal->exitModalState (0);
  336. return YES;
  337. }
  338. }
  339. }
  340. }
  341. return NO;
  342. }
  343. static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
  344. {
  345. if (auto* tableInterface = getTableInterface (self))
  346. if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
  347. return (id) cellHandler->getNativeImplementation();
  348. return nil;
  349. }
  350. static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
  351. {
  352. if (auto* handler = getHandler (self))
  353. {
  354. if (auto* textInterface = handler->getTextInterface())
  355. {
  356. auto pointInt = roundToIntPoint (point);
  357. if (handler->getComponent().getScreenBounds().contains (pointInt))
  358. {
  359. auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
  360. for (int i = 0; i < textBounds.getNumRectangles(); ++i)
  361. if (textBounds.getRectangle (i).contains (pointInt))
  362. return (NSInteger) i;
  363. }
  364. }
  365. }
  366. return NSNotFound;
  367. }
  368. static NSString* getAccessibilityContentForLineNumber (id self, SEL, NSInteger lineNumber)
  369. {
  370. if (auto* textInterface = getTextInterface (self))
  371. {
  372. auto lines = StringArray::fromLines (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
  373. if ((int) lineNumber < lines.size())
  374. return juceStringToNS (lines[(int) lineNumber]);
  375. }
  376. return nil;
  377. }
  378. static CGRect getAccessibilityFrameForLineNumber (id self, SEL, NSInteger lineNumber)
  379. {
  380. if (auto* textInterface = getTextInterface (self))
  381. {
  382. auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
  383. if (lineNumber < textBounds.getNumRectangles())
  384. return convertToCGRect (textBounds.getRectangle ((int) lineNumber));
  385. }
  386. return CGRectZero;
  387. }
  388. static NSString* getAccessibilityPageContent (id self, SEL)
  389. {
  390. if (auto* textInterface = getTextInterface (self))
  391. return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
  392. return nil;
  393. }
  394. //==============================================================================
  395. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
  396. };
  397. //==============================================================================
  398. AccessibilityElement::Holder accessibilityElement;
  399. //==============================================================================
  400. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
  401. };
  402. //==============================================================================
  403. AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
  404. {
  405. return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
  406. }
  407. static bool areAnyAccessibilityClientsActive()
  408. {
  409. return UIAccessibilityIsVoiceOverRunning();
  410. }
  411. static void sendAccessibilityEvent (UIAccessibilityNotifications notification, id argument)
  412. {
  413. if (! areAnyAccessibilityClientsActive())
  414. return;
  415. jassert (notification != UIAccessibilityNotifications{});
  416. UIAccessibilityPostNotification (notification, argument);
  417. }
  418. void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
  419. {
  420. auto notification = [eventType]
  421. {
  422. switch (eventType)
  423. {
  424. case InternalAccessibilityEvent::elementCreated:
  425. case InternalAccessibilityEvent::elementDestroyed:
  426. case InternalAccessibilityEvent::elementMovedOrResized:
  427. case InternalAccessibilityEvent::focusChanged: return UIAccessibilityLayoutChangedNotification;
  428. case InternalAccessibilityEvent::windowOpened:
  429. case InternalAccessibilityEvent::windowClosed: return UIAccessibilityScreenChangedNotification;
  430. }
  431. return UIAccessibilityNotifications{};
  432. }();
  433. if (notification != UIAccessibilityNotifications{})
  434. {
  435. const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
  436. sendAccessibilityEvent (notification,
  437. moveToHandler ? (id) handler.getNativeImplementation() : nil);
  438. }
  439. }
  440. void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
  441. {
  442. auto notification = [eventType]
  443. {
  444. switch (eventType)
  445. {
  446. case AccessibilityEvent::textSelectionChanged:
  447. case AccessibilityEvent::rowSelectionChanged:
  448. case AccessibilityEvent::textChanged:
  449. case AccessibilityEvent::valueChanged:
  450. case AccessibilityEvent::titleChanged: break;
  451. case AccessibilityEvent::structureChanged: return UIAccessibilityLayoutChangedNotification;
  452. }
  453. return UIAccessibilityNotifications{};
  454. }();
  455. if (notification != UIAccessibilityNotifications{})
  456. sendAccessibilityEvent (notification, (id) getNativeImplementation());
  457. }
  458. void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
  459. {
  460. sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
  461. }
  462. } // namespace juce