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.

950 lines
43KB

  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 MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
  21. using NSAccessibilityRole = NSString*;
  22. using NSAccessibilityNotificationName = NSString*;
  23. #endif
  24. #if (! defined MAC_OS_X_VERSION_10_9) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
  25. const NSAccessibilityNotificationName NSAccessibilityLayoutChangedNotificationJuce = @"AXLayoutChanged";
  26. #else
  27. const NSAccessibilityNotificationName NSAccessibilityLayoutChangedNotificationJuce = NSAccessibilityLayoutChangedNotification;
  28. #endif
  29. #if JUCE_OBJC_HAS_AVAILABLE_FEATURE || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10)
  30. #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
  31. //==============================================================================
  32. class AccessibilityHandler::AccessibilityNativeImpl
  33. {
  34. public:
  35. explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
  36. : accessibilityElement (AccessibilityElement::create (handler))
  37. {}
  38. NSAccessibilityElement<NSAccessibility>* getAccessibilityElement() const noexcept
  39. {
  40. return accessibilityElement.get();
  41. }
  42. private:
  43. //==============================================================================
  44. class AccessibilityElement : public AccessibleObjCClass<NSAccessibilityElement<NSAccessibility>>
  45. {
  46. public:
  47. static Holder create (AccessibilityHandler& handler)
  48. {
  49. #if JUCE_OBJC_HAS_AVAILABLE_FEATURE
  50. if (@available (macOS 10.10, *))
  51. #endif
  52. {
  53. static AccessibilityElement cls;
  54. Holder element ([cls.createInstance() init]);
  55. object_setInstanceVariable (element.get(), "handler", &handler);
  56. return element;
  57. }
  58. return {};
  59. }
  60. private:
  61. AccessibilityElement()
  62. {
  63. addMethod (@selector (accessibilityNotifiesWhenDestroyed), getAccessibilityNotifiesWhenDestroyed, "c@:");
  64. addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
  65. addMethod (@selector (isAccessibilityEnabled), getIsAccessibilityEnabled, "c@:");
  66. addMethod (@selector (accessibilityWindow), getAccessibilityWindow, "@@:");
  67. addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow, "@@:");
  68. addMethod (@selector (accessibilityFocusedUIElement), getAccessibilityFocusedUIElement, "@@:");
  69. addMethod (@selector (accessibilityHitTest:), accessibilityHitTest, "@@:", @encode (NSPoint));
  70. addMethod (@selector (accessibilityParent), getAccessibilityParent, "@@:");
  71. addMethod (@selector (accessibilityChildren), getAccessibilityChildren, "@@:");
  72. addMethod (@selector (isAccessibilityFocused), getIsAccessibilityFocused, "c@:");
  73. addMethod (@selector (setAccessibilityFocused:), setAccessibilityFocused, "v@:c");
  74. addMethod (@selector (isAccessibilityModal), getIsAccessibilityModal, "c@:");
  75. addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (NSRect), "@:");
  76. addMethod (@selector (accessibilityRole), getAccessibilityRole, "@@:");
  77. addMethod (@selector (accessibilitySubrole), getAccessibilitySubrole, "@@:");
  78. addMethod (@selector (accessibilityTitle), getAccessibilityTitle, "@@:");
  79. addMethod (@selector (accessibilityLabel), getAccessibilityLabel, "@@:");
  80. addMethod (@selector (accessibilityHelp), getAccessibilityHelp, "@@:");
  81. addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
  82. addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
  83. addMethod (@selector (accessibilitySelectedChildren), getAccessibilitySelectedChildren, "@@:");
  84. addMethod (@selector (setAccessibilitySelectedChildren:), setAccessibilitySelectedChildren, "v@:@");
  85. addMethod (@selector (accessibilityOrientation), getAccessibilityOrientation, "i@:@");
  86. addMethod (@selector (accessibilityInsertionPointLineNumber), getAccessibilityInsertionPointLineNumber, "i@:");
  87. addMethod (@selector (accessibilityVisibleCharacterRange), getAccessibilityVisibleCharacterRange, @encode (NSRange), "@:");
  88. addMethod (@selector (accessibilityNumberOfCharacters), getAccessibilityNumberOfCharacters, "i@:");
  89. addMethod (@selector (accessibilitySelectedText), getAccessibilitySelectedText, "@@:");
  90. addMethod (@selector (accessibilitySelectedTextRange), getAccessibilitySelectedTextRange, @encode (NSRange), "@:");
  91. addMethod (@selector (accessibilityAttributedStringForRange:), getAccessibilityAttributedStringForRange, "@@:", @encode (NSRange));
  92. addMethod (@selector (accessibilityRangeForLine:), getAccessibilityRangeForLine, @encode (NSRange), "@:i");
  93. addMethod (@selector (accessibilityStringForRange:), getAccessibilityStringForRange, "@@:", @encode (NSRange));
  94. addMethod (@selector (accessibilityRangeForPosition:), getAccessibilityRangeForPosition, @encode (NSRange), "@:", @encode (NSPoint));
  95. addMethod (@selector (accessibilityRangeForIndex:), getAccessibilityRangeForIndex, @encode (NSRange), "@:i");
  96. addMethod (@selector (accessibilityFrameForRange:), getAccessibilityFrameForRange, @encode (NSRect), "@:", @encode (NSRange));
  97. addMethod (@selector (accessibilityLineForIndex:), getAccessibilityLineForIndex, "i@:i");
  98. addMethod (@selector (setAccessibilitySelectedTextRange:), setAccessibilitySelectedTextRange, "v@:", @encode (NSRange));
  99. addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
  100. addMethod (@selector (accessibilityRows), getAccessibilityRows, "@@:");
  101. addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows, "@@:");
  102. addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows, "v@:@");
  103. addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
  104. addMethod (@selector (accessibilityColumns), getAccessibilityColumns, "@@:");
  105. addMethod (@selector (accessibilitySelectedColumns), getAccessibilitySelectedColumns, "@@:");
  106. addMethod (@selector (setAccessibilitySelectedColumns:), setAccessibilitySelectedColumns, "v@:@");
  107. addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:");
  108. addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:");
  109. addMethod (@selector (accessibilityIndex), getAccessibilityIndex, "i@:");
  110. addMethod (@selector (accessibilityDisclosureLevel), getAccessibilityDisclosureLevel, "i@:");
  111. addMethod (@selector (isAccessibilityExpanded), getIsAccessibilityExpanded, "c@:");
  112. addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement, "c@:");
  113. addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement, "c@:");
  114. addMethod (@selector (accessibilityPerformDelete), accessibilityPerformDelete, "c@:");
  115. addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress, "c@:");
  116. addMethod (@selector (accessibilityPerformShowMenu), accessibilityPerformShowMenu, "c@:");
  117. addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:");
  118. addMethod (@selector (isAccessibilitySelectorAllowed:), getIsAccessibilitySelectorAllowed, "c@:@");
  119. #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
  120. addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren, "@@:");
  121. #endif
  122. registerClass();
  123. }
  124. //==============================================================================
  125. static bool isSelectable (AccessibleState state) noexcept
  126. {
  127. return state.isSelectable() || state.isMultiSelectable();
  128. }
  129. static NSArray* getSelectedChildren (NSArray* children)
  130. {
  131. NSMutableArray* selected = [[NSMutableArray new] autorelease];
  132. for (id child in children)
  133. {
  134. if (auto* handler = getHandler (child))
  135. {
  136. const auto currentState = handler->getCurrentState();
  137. if (isSelectable (currentState) && currentState.isSelected())
  138. [selected addObject: child];
  139. }
  140. }
  141. return selected;
  142. }
  143. static void setSelectedChildren (NSArray* children, NSArray* selected)
  144. {
  145. for (id child in children)
  146. {
  147. if (auto* handler = getHandler (child))
  148. {
  149. const auto currentState = handler->getCurrentState();
  150. const BOOL isSelected = [selected containsObject: child];
  151. if (isSelectable (currentState))
  152. {
  153. if (currentState.isSelected() != isSelected)
  154. handler->getActions().invoke (AccessibilityActionType::toggle);
  155. }
  156. else if (currentState.isFocusable())
  157. {
  158. [child setAccessibilityFocused: isSelected];
  159. }
  160. }
  161. }
  162. }
  163. //==============================================================================
  164. static BOOL getAccessibilityNotifiesWhenDestroyed (id, SEL) { return YES; }
  165. static BOOL getIsAccessibilityEnabled (id self, SEL)
  166. {
  167. if (auto* handler = getHandler (self))
  168. return handler->getComponent().isEnabled();
  169. return NO;
  170. }
  171. static id getAccessibilityWindow (id self, SEL)
  172. {
  173. return [[self accessibilityParent] accessibilityWindow];
  174. }
  175. static id getAccessibilityFocusedUIElement (id self, SEL)
  176. {
  177. if (auto* handler = getHandler (self))
  178. {
  179. if (auto* modal = Component::getCurrentlyModalComponent())
  180. {
  181. const auto& component = handler->getComponent();
  182. if (! component.isParentOf (modal)
  183. && component.isCurrentlyBlockedByAnotherModalComponent())
  184. {
  185. if (auto* modalHandler = modal->getAccessibilityHandler())
  186. {
  187. if (auto* focusChild = modalHandler->getChildFocus())
  188. return (id) focusChild->getNativeImplementation();
  189. return (id) modalHandler->getNativeImplementation();
  190. }
  191. }
  192. }
  193. if (auto* focusChild = handler->getChildFocus())
  194. return (id) focusChild->getNativeImplementation();
  195. }
  196. return nil;
  197. }
  198. static id accessibilityHitTest (id self, SEL, NSPoint point)
  199. {
  200. if (auto* handler = getHandler (self))
  201. {
  202. if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
  203. return (id) child->getNativeImplementation();
  204. return self;
  205. }
  206. return nil;
  207. }
  208. static id getAccessibilityParent (id self, SEL)
  209. {
  210. if (auto* handler = getHandler (self))
  211. {
  212. if (auto* parentHandler = handler->getParent())
  213. return NSAccessibilityUnignoredAncestor ((id) parentHandler->getNativeImplementation());
  214. return NSAccessibilityUnignoredAncestor ((id) handler->getComponent().getWindowHandle());
  215. }
  216. return nil;
  217. }
  218. static NSArray* getAccessibilityChildren (id self, SEL)
  219. {
  220. if (auto* handler = getHandler (self))
  221. {
  222. auto children = handler->getChildren();
  223. auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
  224. for (auto* childHandler : children)
  225. [accessibleChildren addObject: (id) childHandler->getNativeImplementation()];
  226. return accessibleChildren;
  227. }
  228. return nil;
  229. }
  230. static NSArray* getAccessibilitySelectedChildren (id self, SEL)
  231. {
  232. return getSelectedChildren ([self accessibilityChildren]);
  233. }
  234. static void setAccessibilitySelectedChildren (id self, SEL, NSArray* selected)
  235. {
  236. setSelectedChildren ([self accessibilityChildren], selected);
  237. }
  238. static NSAccessibilityOrientation getAccessibilityOrientation (id self, SEL)
  239. {
  240. if (auto* handler = getHandler (self))
  241. return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f
  242. ? NSAccessibilityOrientationHorizontal
  243. : NSAccessibilityOrientationVertical;
  244. return NSAccessibilityOrientationUnknown;
  245. }
  246. static BOOL getIsAccessibilityFocused (id self, SEL)
  247. {
  248. return [[self accessibilityWindow] accessibilityFocusedUIElement] == self;
  249. }
  250. static void setAccessibilityFocused (id self, SEL, BOOL focused)
  251. {
  252. if (auto* handler = getHandler (self))
  253. {
  254. if (focused)
  255. {
  256. const WeakReference<Component> safeComponent (&handler->getComponent());
  257. performActionIfSupported (self, AccessibilityActionType::focus);
  258. if (safeComponent != nullptr)
  259. handler->grabFocus();
  260. }
  261. else
  262. {
  263. handler->giveAwayFocus();
  264. }
  265. }
  266. }
  267. static NSRect getAccessibilityFrame (id self, SEL)
  268. {
  269. if (auto* handler = getHandler (self))
  270. return flippedScreenRect (makeNSRect (handler->getComponent().getScreenBounds()));
  271. return NSZeroRect;
  272. }
  273. static NSAccessibilityRole getAccessibilityRole (id self, SEL)
  274. {
  275. if (auto* handler = getHandler (self))
  276. {
  277. switch (handler->getRole())
  278. {
  279. case AccessibilityRole::popupMenu:
  280. case AccessibilityRole::tooltip:
  281. case AccessibilityRole::splashScreen:
  282. case AccessibilityRole::dialogWindow:
  283. case AccessibilityRole::window: return NSAccessibilityWindowRole;
  284. case AccessibilityRole::tableHeader:
  285. case AccessibilityRole::unspecified:
  286. case AccessibilityRole::group: return NSAccessibilityGroupRole;
  287. case AccessibilityRole::label:
  288. case AccessibilityRole::staticText: return NSAccessibilityStaticTextRole;
  289. case AccessibilityRole::tree:
  290. case AccessibilityRole::list: return NSAccessibilityOutlineRole;
  291. case AccessibilityRole::listItem:
  292. case AccessibilityRole::treeItem: return NSAccessibilityRowRole;
  293. case AccessibilityRole::button: return NSAccessibilityButtonRole;
  294. case AccessibilityRole::toggleButton: return NSAccessibilityCheckBoxRole;
  295. case AccessibilityRole::radioButton: return NSAccessibilityRadioButtonRole;
  296. case AccessibilityRole::comboBox: return NSAccessibilityPopUpButtonRole;
  297. case AccessibilityRole::image: return NSAccessibilityImageRole;
  298. case AccessibilityRole::slider: return NSAccessibilitySliderRole;
  299. case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole;
  300. case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole;
  301. case AccessibilityRole::menuBar: return NSAccessibilityMenuRole;
  302. case AccessibilityRole::table: return NSAccessibilityListRole;
  303. case AccessibilityRole::column: return NSAccessibilityColumnRole;
  304. case AccessibilityRole::row: return NSAccessibilityRowRole;
  305. case AccessibilityRole::cell: return NSAccessibilityCellRole;
  306. case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole;
  307. case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole;
  308. case AccessibilityRole::scrollBar: return NSAccessibilityScrollBarRole;
  309. case AccessibilityRole::ignored: break;
  310. }
  311. return NSAccessibilityUnknownRole;
  312. }
  313. return nil;
  314. }
  315. static NSAccessibilityRole getAccessibilitySubrole (id self, SEL)
  316. {
  317. if (auto* handler = getHandler (self))
  318. {
  319. if (auto* textInterface = getTextInterface (self))
  320. if (textInterface->isDisplayingProtectedText())
  321. return NSAccessibilitySecureTextFieldSubrole;
  322. const auto role = handler->getRole();
  323. if (role == AccessibilityRole::window) return NSAccessibilityStandardWindowSubrole;
  324. if (role == AccessibilityRole::dialogWindow) return NSAccessibilityDialogSubrole;
  325. if (role == AccessibilityRole::tooltip
  326. || role == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole;
  327. if (role == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole;
  328. if (role == AccessibilityRole::treeItem
  329. || role == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole;
  330. if (role == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole;
  331. const auto& component = handler->getComponent();
  332. if (auto* documentWindow = component.findParentComponentOfClass<DocumentWindow>())
  333. {
  334. if (role == AccessibilityRole::button)
  335. {
  336. if (&component == documentWindow->getCloseButton()) return NSAccessibilityCloseButtonSubrole;
  337. if (&component == documentWindow->getMinimiseButton()) return NSAccessibilityMinimizeButtonSubrole;
  338. if (&component == documentWindow->getMaximiseButton()) return NSAccessibilityFullScreenButtonSubrole;
  339. }
  340. }
  341. }
  342. return NSAccessibilityUnknownRole;
  343. }
  344. static NSString* getAccessibilityLabel (id self, SEL)
  345. {
  346. if (auto* handler = getHandler (self))
  347. return juceStringToNS (handler->getDescription());
  348. return nil;
  349. }
  350. //==============================================================================
  351. static NSInteger getAccessibilityInsertionPointLineNumber (id self, SEL)
  352. {
  353. if (auto* textInterface = getTextInterface (self))
  354. return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()];
  355. return 0;
  356. }
  357. static NSRange getAccessibilityVisibleCharacterRange (id self, SEL)
  358. {
  359. if (auto* textInterface = getTextInterface (self))
  360. return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() });
  361. return NSMakeRange (0, 0);
  362. }
  363. static NSInteger getAccessibilityNumberOfCharacters (id self, SEL)
  364. {
  365. if (auto* textInterface = getTextInterface (self))
  366. return textInterface->getTotalNumCharacters();
  367. return 0;
  368. }
  369. static NSString* getAccessibilitySelectedText (id self, SEL)
  370. {
  371. if (auto* textInterface = getTextInterface (self))
  372. return juceStringToNS (textInterface->getText (textInterface->getSelection()));
  373. return nil;
  374. }
  375. static NSRange getAccessibilitySelectedTextRange (id self, SEL)
  376. {
  377. if (auto* textInterface = getTextInterface (self))
  378. {
  379. const auto currentSelection = textInterface->getSelection();
  380. if (currentSelection.isEmpty())
  381. return NSMakeRange ((NSUInteger) textInterface->getTextInsertionOffset(), 0);
  382. return juceRangeToNS (currentSelection);
  383. }
  384. return NSMakeRange (0, 0);
  385. }
  386. static NSAttributedString* getAccessibilityAttributedStringForRange (id self, SEL, NSRange range)
  387. {
  388. NSString* string = [self accessibilityStringForRange: range];
  389. if (string != nil)
  390. return [[[NSAttributedString alloc] initWithString: string] autorelease];
  391. return nil;
  392. }
  393. static NSRange getAccessibilityRangeForLine (id self, SEL, NSInteger line)
  394. {
  395. if (auto* textInterface = getTextInterface (self))
  396. {
  397. auto text = textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
  398. auto lines = StringArray::fromLines (text);
  399. if (line < lines.size())
  400. {
  401. auto lineText = lines[(int) line];
  402. auto start = text.indexOf (lineText);
  403. if (start >= 0)
  404. return NSMakeRange ((NSUInteger) start, (NSUInteger) lineText.length());
  405. }
  406. }
  407. return NSMakeRange (0, 0);
  408. }
  409. static NSString* getAccessibilityStringForRange (id self, SEL, NSRange range)
  410. {
  411. if (auto* textInterface = getTextInterface (self))
  412. return juceStringToNS (textInterface->getText (nsRangeToJuce (range)));
  413. return nil;
  414. }
  415. static NSRange getAccessibilityRangeForPosition (id self, SEL, NSPoint position)
  416. {
  417. if (auto* handler = getHandler (self))
  418. {
  419. if (auto* textInterface = handler->getTextInterface())
  420. {
  421. auto screenPoint = roundToIntPoint (flippedScreenPoint (position));
  422. if (handler->getComponent().getScreenBounds().contains (screenPoint))
  423. {
  424. auto offset = textInterface->getOffsetAtPoint (screenPoint);
  425. if (offset >= 0)
  426. return NSMakeRange ((NSUInteger) offset, 1);
  427. }
  428. }
  429. }
  430. return NSMakeRange (0, 0);
  431. }
  432. static NSRange getAccessibilityRangeForIndex (id self, SEL, NSInteger index)
  433. {
  434. if (auto* textInterface = getTextInterface (self))
  435. if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters()))
  436. return NSMakeRange ((NSUInteger) index, 1);
  437. return NSMakeRange (0, 0);
  438. }
  439. static NSRect getAccessibilityFrameForRange (id self, SEL, NSRange range)
  440. {
  441. if (auto* textInterface = getTextInterface (self))
  442. return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds()));
  443. return NSZeroRect;
  444. }
  445. static NSInteger getAccessibilityLineForIndex (id self, SEL, NSInteger index)
  446. {
  447. if (auto* textInterface = getTextInterface (self))
  448. {
  449. auto text = textInterface->getText ({ 0, (int) index });
  450. if (! text.isEmpty())
  451. return StringArray::fromLines (text).size() - 1;
  452. }
  453. return 0;
  454. }
  455. static void setAccessibilitySelectedTextRange (id self, SEL, NSRange selectedRange)
  456. {
  457. if (auto* textInterface = getTextInterface (self))
  458. textInterface->setSelection (nsRangeToJuce (selectedRange));
  459. }
  460. //==============================================================================
  461. static NSArray* getAccessibilityRows (id self, SEL)
  462. {
  463. NSMutableArray* rows = [[NSMutableArray new] autorelease];
  464. if (auto* tableInterface = getTableInterface (self))
  465. {
  466. for (int row = 0; row < tableInterface->getNumRows(); ++row)
  467. {
  468. if (auto* handler = tableInterface->getCellHandler (row, 0))
  469. {
  470. [rows addObject: (id) handler->getNativeImplementation()];
  471. }
  472. else
  473. {
  474. [rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
  475. frame: NSZeroRect
  476. label: @"Offscreen Row"
  477. parent: self]];
  478. }
  479. }
  480. }
  481. return rows;
  482. }
  483. static NSArray* getAccessibilitySelectedRows (id self, SEL)
  484. {
  485. return getSelectedChildren ([self accessibilityRows]);
  486. }
  487. static void setAccessibilitySelectedRows (id self, SEL, NSArray* selected)
  488. {
  489. setSelectedChildren ([self accessibilityRows], selected);
  490. }
  491. static NSArray* getAccessibilityColumns (id self, SEL)
  492. {
  493. NSMutableArray* columns = [[NSMutableArray new] autorelease];
  494. if (auto* tableInterface = getTableInterface (self))
  495. {
  496. for (int column = 0; column < tableInterface->getNumColumns(); ++column)
  497. {
  498. if (auto* handler = tableInterface->getCellHandler (0, column))
  499. {
  500. [columns addObject: (id) handler->getNativeImplementation()];
  501. }
  502. else
  503. {
  504. [columns addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityColumnRole
  505. frame: NSZeroRect
  506. label: @"Offscreen Column"
  507. parent: self]];
  508. }
  509. }
  510. }
  511. return columns;
  512. }
  513. static NSArray* getAccessibilitySelectedColumns (id self, SEL)
  514. {
  515. return getSelectedChildren ([self accessibilityColumns]);
  516. }
  517. static void setAccessibilitySelectedColumns (id self, SEL, NSArray* selected)
  518. {
  519. setSelectedChildren ([self accessibilityColumns], selected);
  520. }
  521. //==============================================================================
  522. static NSInteger getAccessibilityIndex (id self, SEL)
  523. {
  524. if (auto* handler = getHandler (self))
  525. {
  526. if (auto* cellInterface = handler->getCellInterface())
  527. {
  528. NSAccessibilityRole role = [self accessibilityRole];
  529. if ([role isEqual: NSAccessibilityRowRole])
  530. return cellInterface->getRowIndex();
  531. if ([role isEqual: NSAccessibilityColumnRole])
  532. return cellInterface->getColumnIndex();
  533. }
  534. }
  535. return 0;
  536. }
  537. static NSInteger getAccessibilityDisclosureLevel (id self, SEL)
  538. {
  539. if (auto* handler = getHandler (self))
  540. if (auto* cellInterface = handler->getCellInterface())
  541. return cellInterface->getDisclosureLevel();
  542. return 0;
  543. }
  544. static BOOL getIsAccessibilityExpanded (id self, SEL)
  545. {
  546. if (auto* handler = getHandler (self))
  547. return handler->getCurrentState().isExpanded();
  548. return NO;
  549. }
  550. //==============================================================================
  551. static BOOL accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); }
  552. static BOOL accessibilityPerformRaise (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; }
  553. static BOOL accessibilityPerformDelete (id self, SEL)
  554. {
  555. if (auto* handler = getHandler (self))
  556. {
  557. if (hasEditableText (*handler))
  558. {
  559. handler->getTextInterface()->setText ({});
  560. return YES;
  561. }
  562. if (auto* valueInterface = handler->getValueInterface())
  563. {
  564. if (! valueInterface->isReadOnly())
  565. {
  566. valueInterface->setValue ({});
  567. return YES;
  568. }
  569. }
  570. }
  571. return NO;
  572. }
  573. //==============================================================================
  574. static BOOL getIsAccessibilitySelectorAllowed (id self, SEL, SEL selector)
  575. {
  576. if (auto* handler = getHandler (self))
  577. {
  578. const auto role = handler->getRole();
  579. const auto currentState = handler->getCurrentState();
  580. for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber),
  581. @selector (accessibilityVisibleCharacterRange),
  582. @selector (accessibilityNumberOfCharacters),
  583. @selector (accessibilitySelectedText),
  584. @selector (accessibilitySelectedTextRange),
  585. @selector (accessibilityAttributedStringForRange:),
  586. @selector (accessibilityRangeForLine:),
  587. @selector (accessibilityStringForRange:),
  588. @selector (accessibilityRangeForPosition:),
  589. @selector (accessibilityRangeForIndex:),
  590. @selector (accessibilityFrameForRange:),
  591. @selector (accessibilityLineForIndex:),
  592. @selector (setAccessibilitySelectedTextRange:) })
  593. {
  594. if (selector == textSelector)
  595. return handler->getTextInterface() != nullptr;
  596. }
  597. for (auto tableSelector : { @selector (accessibilityRowCount),
  598. @selector (accessibilityRows),
  599. @selector (accessibilitySelectedRows),
  600. @selector (accessibilityColumnCount),
  601. @selector (accessibilityColumns),
  602. @selector (accessibilitySelectedColumns) })
  603. {
  604. if (selector == tableSelector)
  605. return handler->getTableInterface() != nullptr;
  606. }
  607. for (auto cellSelector : { @selector (accessibilityRowIndexRange),
  608. @selector (accessibilityColumnIndexRange),
  609. @selector (accessibilityIndex),
  610. @selector (accessibilityDisclosureLevel) })
  611. {
  612. if (selector == cellSelector)
  613. return handler->getCellInterface() != nullptr;
  614. }
  615. for (auto valueSelector : { @selector (accessibilityValue),
  616. @selector (setAccessibilityValue:),
  617. @selector (accessibilityPerformDelete),
  618. @selector (accessibilityPerformIncrement),
  619. @selector (accessibilityPerformDecrement) })
  620. {
  621. if (selector != valueSelector)
  622. continue;
  623. auto* valueInterface = handler->getValueInterface();
  624. if (selector == @selector (accessibilityValue))
  625. return valueInterface != nullptr
  626. || hasEditableText (*handler)
  627. || currentState.isCheckable();
  628. auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); };
  629. if (selector == @selector (setAccessibilityValue:)
  630. || selector == @selector (accessibilityPerformDelete))
  631. return hasEditableValue() || hasEditableText (*handler);
  632. auto isRanged = [valueInterface] { return valueInterface != nullptr && valueInterface->getRange().isValid(); };
  633. if (selector == @selector (accessibilityPerformIncrement)
  634. || selector == @selector (accessibilityPerformDecrement))
  635. return hasEditableValue() && isRanged();
  636. return NO;
  637. }
  638. for (auto actionSelector : { @selector (accessibilityPerformPress),
  639. @selector (accessibilityPerformShowMenu),
  640. @selector (accessibilityPerformRaise),
  641. @selector (setAccessibilityFocused:) })
  642. {
  643. if (selector != actionSelector)
  644. continue;
  645. if (selector == @selector (accessibilityPerformPress))
  646. return handler->getActions().contains (AccessibilityActionType::press);
  647. if (selector == @selector (accessibilityPerformShowMenu))
  648. return handler->getActions().contains (AccessibilityActionType::showMenu);
  649. if (selector == @selector (accessibilityPerformRaise))
  650. return [[self accessibilityRole] isEqual: NSAccessibilityWindowRole];
  651. if (selector == @selector (setAccessibilityFocused:))
  652. return currentState.isFocusable();
  653. }
  654. if (selector == @selector (accessibilitySelectedChildren))
  655. return role == AccessibilityRole::popupMenu;
  656. if (selector == @selector (accessibilityOrientation))
  657. return role == AccessibilityRole::scrollBar;
  658. if (selector == @selector (isAccessibilityExpanded))
  659. return currentState.isExpandable();
  660. return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector);
  661. }
  662. return NO;
  663. }
  664. //==============================================================================
  665. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
  666. };
  667. //==============================================================================
  668. AccessibilityElement::Holder accessibilityElement;
  669. //==============================================================================
  670. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
  671. };
  672. //==============================================================================
  673. AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
  674. {
  675. return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
  676. }
  677. static bool areAnyAccessibilityClientsActive()
  678. {
  679. const String voiceOverKeyString ("voiceOverOnOffKey");
  680. const String applicationIDString ("com.apple.universalaccess");
  681. CFUniquePtr<CFStringRef> cfKey (voiceOverKeyString.toCFString());
  682. CFUniquePtr<CFStringRef> cfID (applicationIDString.toCFString());
  683. CFUniquePtr<CFPropertyListRef> value (CFPreferencesCopyAppValue (cfKey.get(), cfID.get()));
  684. if (value != nullptr)
  685. return CFBooleanGetValue ((CFBooleanRef) value.get());
  686. return false;
  687. }
  688. static void sendAccessibilityEvent (id accessibilityElement,
  689. NSAccessibilityNotificationName notification,
  690. NSDictionary* userInfo)
  691. {
  692. jassert (notification != NSAccessibilityNotificationName{});
  693. NSAccessibilityPostNotificationWithUserInfo (accessibilityElement, notification, userInfo);
  694. }
  695. static void sendHandlerNotification (const AccessibilityHandler& handler,
  696. NSAccessibilityNotificationName notification)
  697. {
  698. if (! areAnyAccessibilityClientsActive() || notification == NSAccessibilityNotificationName{})
  699. return;
  700. if (id accessibilityElement = (id) handler.getNativeImplementation())
  701. {
  702. sendAccessibilityEvent (accessibilityElement, notification,
  703. (notification == NSAccessibilityLayoutChangedNotification
  704. ? @{ NSAccessibilityUIElementsKey: @[ accessibilityElement ] }
  705. : nil));
  706. }
  707. }
  708. void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
  709. {
  710. auto notification = [eventType]
  711. {
  712. switch (eventType)
  713. {
  714. case InternalAccessibilityEvent::elementCreated: return NSAccessibilityCreatedNotification;
  715. case InternalAccessibilityEvent::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification;
  716. case InternalAccessibilityEvent::elementMovedOrResized: return NSAccessibilityLayoutChangedNotificationJuce;
  717. case InternalAccessibilityEvent::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification;
  718. case InternalAccessibilityEvent::windowOpened: return NSAccessibilityWindowCreatedNotification;
  719. case InternalAccessibilityEvent::windowClosed: break;
  720. }
  721. return NSAccessibilityNotificationName{};
  722. }();
  723. sendHandlerNotification (handler, notification);
  724. }
  725. void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
  726. {
  727. auto notification = [eventType]
  728. {
  729. switch (eventType)
  730. {
  731. case AccessibilityEvent::textSelectionChanged: return NSAccessibilitySelectedTextChangedNotification;
  732. case AccessibilityEvent::rowSelectionChanged: return NSAccessibilitySelectedRowsChangedNotification;
  733. case AccessibilityEvent::textChanged:
  734. case AccessibilityEvent::valueChanged: return NSAccessibilityValueChangedNotification;
  735. case AccessibilityEvent::titleChanged: return NSAccessibilityTitleChangedNotification;
  736. case AccessibilityEvent::structureChanged: return NSAccessibilityLayoutChangedNotificationJuce;
  737. }
  738. return NSAccessibilityNotificationName{};
  739. }();
  740. sendHandlerNotification (*this, notification);
  741. }
  742. void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
  743. {
  744. if (! areAnyAccessibilityClientsActive())
  745. return;
  746. #if JUCE_OBJC_HAS_AVAILABLE_FEATURE
  747. if (@available (macOS 10.10, *))
  748. #endif
  749. {
  750. auto nsPriority = [priority]
  751. {
  752. switch (priority)
  753. {
  754. case AnnouncementPriority::low: return NSAccessibilityPriorityLow;
  755. case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium;
  756. case AnnouncementPriority::high: return NSAccessibilityPriorityHigh;
  757. }
  758. jassertfalse;
  759. return NSAccessibilityPriorityLow;
  760. }();
  761. sendAccessibilityEvent ((id) [NSApp mainWindow],
  762. NSAccessibilityAnnouncementRequestedNotification,
  763. @{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
  764. NSAccessibilityPriorityKey: @(nsPriority) });
  765. }
  766. }
  767. #endif
  768. } // namespace juce