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.

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