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.

961 lines
42KB

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