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