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.

533 lines
21KB

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