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.

589 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. addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange);
  180. addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange);
  181. }
  182. #endif
  183. if (elementType == Type::textElement)
  184. {
  185. addMethod (@selector (accessibilityLineNumberForPoint:), getAccessibilityLineNumberForPoint);
  186. addMethod (@selector (accessibilityContentForLineNumber:), getAccessibilityContentForLineNumber);
  187. addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber);
  188. addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent);
  189. addProtocol (@protocol (UIAccessibilityReadingContent));
  190. }
  191. addIvar<UIAccessibilityElement*> ("container");
  192. registerClass();
  193. }
  194. private:
  195. //==============================================================================
  196. static UIAccessibilityElement* getContainer (id self)
  197. {
  198. return getIvar<UIAccessibilityElement*> (self, "container");
  199. }
  200. //==============================================================================
  201. static id getAccessibilityContainer (id self, SEL)
  202. {
  203. if (auto* handler = getHandler (self))
  204. {
  205. if (handler->getComponent().isOnDesktop())
  206. return (id) handler->getComponent().getWindowHandle();
  207. if (handler->getChildren().size() > 0)
  208. {
  209. if (UIAccessibilityElement* container = getContainer (self))
  210. return container;
  211. static AccessibilityContainer cls;
  212. id windowHandle = (id) handler->getComponent().getWindowHandle();
  213. UIAccessibilityElement* container = [cls.createInstance() initWithAccessibilityContainer: windowHandle];
  214. [container retain];
  215. object_setInstanceVariable (container, "handler", handler);
  216. object_setInstanceVariable (self, "container", container);
  217. return container;
  218. }
  219. if (auto* parent = handler->getParent())
  220. return [(id) parent->getNativeImplementation() accessibilityContainer];
  221. }
  222. return nil;
  223. }
  224. static CGRect getAccessibilityFrame (id self, SEL)
  225. {
  226. if (auto* handler = getHandler (self))
  227. return convertToCGRect (handler->getComponent().getScreenBounds());
  228. return CGRectZero;
  229. }
  230. static UIAccessibilityTraits getAccessibilityTraits (id self, SEL)
  231. {
  232. auto traits = UIAccessibilityTraits{};
  233. if (auto* handler = getHandler (self))
  234. {
  235. traits |= [&handler]
  236. {
  237. switch (handler->getRole())
  238. {
  239. case AccessibilityRole::button:
  240. case AccessibilityRole::toggleButton:
  241. case AccessibilityRole::radioButton:
  242. case AccessibilityRole::comboBox: return UIAccessibilityTraitButton;
  243. case AccessibilityRole::label:
  244. case AccessibilityRole::staticText: return UIAccessibilityTraitStaticText;
  245. case AccessibilityRole::image: return UIAccessibilityTraitImage;
  246. case AccessibilityRole::tableHeader: return UIAccessibilityTraitHeader;
  247. case AccessibilityRole::hyperlink: return UIAccessibilityTraitLink;
  248. case AccessibilityRole::editableText: return UIAccessibilityTraitKeyboardKey;
  249. case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
  250. case AccessibilityRole::slider:
  251. case AccessibilityRole::menuItem:
  252. case AccessibilityRole::menuBar:
  253. case AccessibilityRole::popupMenu:
  254. case AccessibilityRole::table:
  255. case AccessibilityRole::column:
  256. case AccessibilityRole::row:
  257. case AccessibilityRole::cell:
  258. case AccessibilityRole::list:
  259. case AccessibilityRole::listItem:
  260. case AccessibilityRole::tree:
  261. case AccessibilityRole::treeItem:
  262. case AccessibilityRole::progressBar:
  263. case AccessibilityRole::group:
  264. case AccessibilityRole::dialogWindow:
  265. case AccessibilityRole::window:
  266. case AccessibilityRole::scrollBar:
  267. case AccessibilityRole::tooltip:
  268. case AccessibilityRole::splashScreen:
  269. case AccessibilityRole::unspecified: break;
  270. }
  271. return UIAccessibilityTraitNone;
  272. }();
  273. const auto state = handler->getCurrentState();
  274. if (state.isSelected() || state.isChecked())
  275. traits |= UIAccessibilityTraitSelected;
  276. if (auto* valueInterface = getValueInterface (self))
  277. if (! valueInterface->isReadOnly() && valueInterface->getRange().isValid())
  278. traits |= UIAccessibilityTraitAdjustable;
  279. }
  280. return traits | sendSuperclassMessage<UIAccessibilityTraits> (self, @selector (accessibilityTraits));
  281. }
  282. static NSString* getAccessibilityValue (id self, SEL)
  283. {
  284. if (auto* handler = getHandler (self))
  285. {
  286. if (handler->getCurrentState().isCheckable())
  287. return handler->getCurrentState().isChecked() ? @"1" : @"0";
  288. return (NSString*) getAccessibilityValueFromInterfaces (*handler);
  289. }
  290. return nil;
  291. }
  292. static void onFocusGain (id self, SEL)
  293. {
  294. if (auto* handler = getHandler (self))
  295. {
  296. const WeakReference<Component> safeComponent (&handler->getComponent());
  297. performActionIfSupported (self, AccessibilityActionType::focus);
  298. if (safeComponent != nullptr)
  299. handler->grabFocus();
  300. }
  301. }
  302. static void onFocusLoss (id self, SEL)
  303. {
  304. if (auto* handler = getHandler (self))
  305. handler->giveAwayFocus();
  306. }
  307. static BOOL isFocused (id self, SEL)
  308. {
  309. if (auto* handler = getHandler (self))
  310. return handler->hasFocus (false);
  311. return NO;
  312. }
  313. static BOOL accessibilityPerformActivate (id self, SEL)
  314. {
  315. if (auto* handler = getHandler (self))
  316. {
  317. // occasionaly VoiceOver sends accessibilityActivate to the wrong element, so we first query
  318. // which element it thinks has focus and forward the event on to that element if it differs
  319. id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
  320. if (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
  321. return [focusedElement accessibilityActivate];
  322. if (handler->hasFocus (false))
  323. return accessibilityPerformPress (self, {});
  324. }
  325. return NO;
  326. }
  327. static BOOL accessibilityPerformEscape (id self, SEL)
  328. {
  329. if (auto* handler = getHandler (self))
  330. {
  331. if (auto* modal = Component::getCurrentlyModalComponent())
  332. {
  333. if (auto* modalHandler = modal->getAccessibilityHandler())
  334. {
  335. if (modalHandler == handler || modalHandler->isParentOf (handler))
  336. {
  337. modal->exitModalState (0);
  338. return YES;
  339. }
  340. }
  341. }
  342. }
  343. return NO;
  344. }
  345. static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
  346. {
  347. if (auto* tableInterface = getTableInterface (self))
  348. if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
  349. return (id) cellHandler->getNativeImplementation();
  350. return nil;
  351. }
  352. static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
  353. {
  354. if (auto* handler = getHandler (self))
  355. {
  356. if (auto* textInterface = handler->getTextInterface())
  357. {
  358. auto pointInt = roundToIntPoint (point);
  359. if (handler->getComponent().getScreenBounds().contains (pointInt))
  360. {
  361. auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
  362. for (int i = 0; i < textBounds.getNumRectangles(); ++i)
  363. if (textBounds.getRectangle (i).contains (pointInt))
  364. return (NSInteger) i;
  365. }
  366. }
  367. }
  368. return NSNotFound;
  369. }
  370. static NSString* getAccessibilityContentForLineNumber (id self, SEL, NSInteger lineNumber)
  371. {
  372. if (auto* textInterface = getTextInterface (self))
  373. {
  374. auto lines = StringArray::fromLines (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
  375. if ((int) lineNumber < lines.size())
  376. return juceStringToNS (lines[(int) lineNumber]);
  377. }
  378. return nil;
  379. }
  380. static CGRect getAccessibilityFrameForLineNumber (id self, SEL, NSInteger lineNumber)
  381. {
  382. if (auto* textInterface = getTextInterface (self))
  383. {
  384. auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
  385. if (lineNumber < textBounds.getNumRectangles())
  386. return convertToCGRect (textBounds.getRectangle ((int) lineNumber));
  387. }
  388. return CGRectZero;
  389. }
  390. static NSString* getAccessibilityPageContent (id self, SEL)
  391. {
  392. if (auto* textInterface = getTextInterface (self))
  393. return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
  394. return nil;
  395. }
  396. //==============================================================================
  397. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
  398. };
  399. //==============================================================================
  400. AccessibilityElement::Holder accessibilityElement;
  401. //==============================================================================
  402. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
  403. };
  404. //==============================================================================
  405. AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
  406. {
  407. return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
  408. }
  409. static bool areAnyAccessibilityClientsActive()
  410. {
  411. return UIAccessibilityIsVoiceOverRunning();
  412. }
  413. static void sendAccessibilityEvent (UIAccessibilityNotifications notification, id argument)
  414. {
  415. if (! areAnyAccessibilityClientsActive())
  416. return;
  417. jassert (notification != UIAccessibilityNotifications{});
  418. UIAccessibilityPostNotification (notification, argument);
  419. }
  420. void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
  421. {
  422. auto notification = [eventType]
  423. {
  424. switch (eventType)
  425. {
  426. case InternalAccessibilityEvent::elementCreated:
  427. case InternalAccessibilityEvent::elementDestroyed:
  428. case InternalAccessibilityEvent::elementMovedOrResized:
  429. case InternalAccessibilityEvent::focusChanged: return UIAccessibilityLayoutChangedNotification;
  430. case InternalAccessibilityEvent::windowOpened:
  431. case InternalAccessibilityEvent::windowClosed: return UIAccessibilityScreenChangedNotification;
  432. }
  433. return UIAccessibilityNotifications{};
  434. }();
  435. if (notification != UIAccessibilityNotifications{})
  436. {
  437. const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
  438. sendAccessibilityEvent (notification,
  439. moveToHandler ? (id) handler.getNativeImplementation() : nil);
  440. }
  441. }
  442. void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
  443. {
  444. auto notification = [eventType]
  445. {
  446. switch (eventType)
  447. {
  448. case AccessibilityEvent::textSelectionChanged:
  449. case AccessibilityEvent::rowSelectionChanged:
  450. case AccessibilityEvent::textChanged:
  451. case AccessibilityEvent::valueChanged:
  452. case AccessibilityEvent::titleChanged: break;
  453. case AccessibilityEvent::structureChanged: return UIAccessibilityLayoutChangedNotification;
  454. }
  455. return UIAccessibilityNotifications{};
  456. }();
  457. if (notification != UIAccessibilityNotifications{})
  458. sendAccessibilityEvent (notification, (id) getNativeImplementation());
  459. }
  460. void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
  461. {
  462. sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
  463. }
  464. } // namespace juce