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.

542 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 void onFocusGain (id self, SEL)
  272. {
  273. if (auto* handler = getHandler (self))
  274. {
  275. const WeakReference<Component> safeComponent (&handler->getComponent());
  276. performActionIfSupported (self, AccessibilityActionType::focus);
  277. if (safeComponent != nullptr)
  278. handler->grabFocus();
  279. }
  280. }
  281. static void onFocusLoss (id self, SEL)
  282. {
  283. if (auto* handler = getHandler (self))
  284. handler->giveAwayFocus();
  285. }
  286. static BOOL isFocused (id self, SEL)
  287. {
  288. if (auto* handler = getHandler (self))
  289. return handler->hasFocus (false);
  290. return NO;
  291. }
  292. static BOOL accessibilityPerformActivate (id self, SEL)
  293. {
  294. if (auto* handler = getHandler (self))
  295. {
  296. // occasionaly VoiceOver sends accessibilityActivate to the wrong element, so we first query
  297. // which element it thinks has focus and forward the event on to that element if it differs
  298. id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
  299. if (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
  300. return [focusedElement accessibilityActivate];
  301. if (handler->hasFocus (false))
  302. return accessibilityPerformPress (self, {});
  303. }
  304. return NO;
  305. }
  306. static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
  307. {
  308. if (auto* tableInterface = getTableInterface (self))
  309. if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
  310. return (id) cellHandler->getNativeImplementation();
  311. return nil;
  312. }
  313. static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
  314. {
  315. if (auto* handler = getHandler (self))
  316. {
  317. if (auto* textInterface = handler->getTextInterface())
  318. {
  319. auto pointInt = roundToIntPoint (point);
  320. if (handler->getComponent().getScreenBounds().contains (pointInt))
  321. {
  322. auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
  323. for (int i = 0; i < textBounds.getNumRectangles(); ++i)
  324. if (textBounds.getRectangle (i).contains (pointInt))
  325. return (NSInteger) i;
  326. }
  327. }
  328. }
  329. return NSNotFound;
  330. }
  331. static NSString* getAccessibilityContentForLineNumber (id self, SEL, NSInteger lineNumber)
  332. {
  333. if (auto* textInterface = getTextInterface (self))
  334. {
  335. auto lines = StringArray::fromLines (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
  336. if ((int) lineNumber < lines.size())
  337. return juceStringToNS (lines[(int) lineNumber]);
  338. }
  339. return nil;
  340. }
  341. static CGRect getAccessibilityFrameForLineNumber (id self, SEL, NSInteger lineNumber)
  342. {
  343. if (auto* textInterface = getTextInterface (self))
  344. {
  345. auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
  346. if (lineNumber < textBounds.getNumRectangles())
  347. return convertToCGRect (textBounds.getRectangle ((int) lineNumber));
  348. }
  349. return CGRectZero;
  350. }
  351. static NSString* getAccessibilityPageContent (id self, SEL)
  352. {
  353. if (auto* textInterface = getTextInterface (self))
  354. return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
  355. return nil;
  356. }
  357. //==============================================================================
  358. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
  359. };
  360. //==============================================================================
  361. AccessibilityElement::Holder accessibilityElement;
  362. //==============================================================================
  363. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
  364. };
  365. //==============================================================================
  366. AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
  367. {
  368. return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
  369. }
  370. static bool areAnyAccessibilityClientsActive()
  371. {
  372. return UIAccessibilityIsVoiceOverRunning();
  373. }
  374. static void sendAccessibilityEvent (UIAccessibilityNotifications notification, id argument)
  375. {
  376. if (! areAnyAccessibilityClientsActive())
  377. return;
  378. jassert (notification != UIAccessibilityNotifications{});
  379. UIAccessibilityPostNotification (notification, argument);
  380. }
  381. void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
  382. {
  383. auto notification = [eventType]
  384. {
  385. switch (eventType)
  386. {
  387. case InternalAccessibilityEvent::elementCreated:
  388. case InternalAccessibilityEvent::elementDestroyed:
  389. case InternalAccessibilityEvent::elementMovedOrResized:
  390. case InternalAccessibilityEvent::focusChanged: return UIAccessibilityLayoutChangedNotification;
  391. case InternalAccessibilityEvent::windowOpened:
  392. case InternalAccessibilityEvent::windowClosed: return UIAccessibilityScreenChangedNotification;
  393. }
  394. return UIAccessibilityNotifications{};
  395. }();
  396. if (notification != UIAccessibilityNotifications{})
  397. {
  398. const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
  399. sendAccessibilityEvent (notification,
  400. moveToHandler ? (id) handler.getNativeImplementation() : nil);
  401. }
  402. }
  403. void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
  404. {
  405. auto notification = [eventType]
  406. {
  407. switch (eventType)
  408. {
  409. case AccessibilityEvent::textSelectionChanged:
  410. case AccessibilityEvent::rowSelectionChanged:
  411. case AccessibilityEvent::textChanged:
  412. case AccessibilityEvent::valueChanged:
  413. case AccessibilityEvent::titleChanged: break;
  414. case AccessibilityEvent::structureChanged: return UIAccessibilityLayoutChangedNotification;
  415. }
  416. return UIAccessibilityNotifications{};
  417. }();
  418. if (notification != UIAccessibilityNotifications{})
  419. sendAccessibilityEvent (notification, (id) getNativeImplementation());
  420. }
  421. void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
  422. {
  423. sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
  424. }
  425. } // namespace juce