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.

964 lines
44KB

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