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.

571 lines
22KB

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