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.

555 lines
22KB

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