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.

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