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.

970 lines
43KB

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