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.

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