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.

933 lines
43KB

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