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.

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