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.

943 lines
41KB

  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 : 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 juceStringToNS (handler->getCurrentState().isChecked() ? TRANS ("On") : TRANS ("Off"));
  221. return getAccessibilityValueFromInterfaces (*handler);
  222. }
  223. return nil;
  224. });
  225. addMethod (@selector (accessibilityTitle), getAccessibilityTitle);
  226. addMethod (@selector (accessibilityHelp), getAccessibilityHelp);
  227. addMethod (@selector (setAccessibilityValue:), setAccessibilityValue);
  228. addMethod (@selector (accessibilitySelectedChildren), [] (id self, SEL)
  229. {
  230. return getSelectedChildren ([self accessibilityChildren]);
  231. });
  232. addMethod (@selector (setAccessibilitySelectedChildren:), [] (id self, SEL, NSArray* selected)
  233. {
  234. setSelectedChildren ([self accessibilityChildren], selected);
  235. });
  236. addMethod (@selector (accessibilityOrientation), [] (id self, SEL)
  237. {
  238. if (auto* handler = getHandler (self))
  239. return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f
  240. ? NSAccessibilityOrientationHorizontal
  241. : NSAccessibilityOrientationVertical;
  242. return NSAccessibilityOrientationUnknown;
  243. });
  244. addMethod (@selector (accessibilityInsertionPointLineNumber), [] (id self, SEL) -> NSInteger
  245. {
  246. if (auto* textInterface = getTextInterface (self))
  247. return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()];
  248. return 0;
  249. });
  250. addMethod (@selector (accessibilityVisibleCharacterRange), [] (id self, SEL)
  251. {
  252. if (auto* textInterface = getTextInterface (self))
  253. return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() });
  254. return NSMakeRange (0, 0);
  255. });
  256. addMethod (@selector (accessibilityNumberOfCharacters), [] (id self, SEL)
  257. {
  258. if (auto* textInterface = getTextInterface (self))
  259. return textInterface->getTotalNumCharacters();
  260. return 0;
  261. });
  262. addMethod (@selector (accessibilitySelectedText), [] (id self, SEL) -> NSString*
  263. {
  264. if (auto* textInterface = getTextInterface (self))
  265. return juceStringToNS (textInterface->getText (textInterface->getSelection()));
  266. return nil;
  267. });
  268. addMethod (@selector (accessibilitySelectedTextRange), [] (id self, SEL)
  269. {
  270. if (auto* textInterface = getTextInterface (self))
  271. {
  272. const auto currentSelection = textInterface->getSelection();
  273. if (currentSelection.isEmpty())
  274. return NSMakeRange ((NSUInteger) textInterface->getTextInsertionOffset(), 0);
  275. return juceRangeToNS (currentSelection);
  276. }
  277. return NSMakeRange (0, 0);
  278. });
  279. addMethod (@selector (accessibilityAttributedStringForRange:), [] (id self, SEL, NSRange range) -> NSAttributedString*
  280. {
  281. NSString* string = [self accessibilityStringForRange: range];
  282. if (string != nil)
  283. return [[[NSAttributedString alloc] initWithString: string] autorelease];
  284. return nil;
  285. });
  286. addMethod (@selector (accessibilityRangeForLine:), [] (id self, SEL, NSInteger line)
  287. {
  288. if (auto* textInterface = getTextInterface (self))
  289. {
  290. auto text = textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
  291. auto lines = StringArray::fromLines (text);
  292. if (line < lines.size())
  293. {
  294. auto lineText = lines[(int) line];
  295. auto start = text.indexOf (lineText);
  296. if (start >= 0)
  297. return NSMakeRange ((NSUInteger) start, (NSUInteger) lineText.length());
  298. }
  299. }
  300. return NSMakeRange (0, 0);
  301. });
  302. addMethod (@selector (accessibilityStringForRange:), [] (id self, SEL, NSRange range) -> NSString*
  303. {
  304. if (auto* textInterface = getTextInterface (self))
  305. return juceStringToNS (textInterface->getText (nsRangeToJuce (range)));
  306. return nil;
  307. });
  308. addMethod (@selector (accessibilityRangeForPosition:), [] (id self, SEL, NSPoint position)
  309. {
  310. if (auto* handler = getHandler (self))
  311. {
  312. if (auto* textInterface = handler->getTextInterface())
  313. {
  314. auto screenPoint = roundToIntPoint (flippedScreenPoint (position));
  315. if (handler->getComponent().getScreenBounds().contains (screenPoint))
  316. {
  317. auto offset = textInterface->getOffsetAtPoint (screenPoint);
  318. if (offset >= 0)
  319. return NSMakeRange ((NSUInteger) offset, 1);
  320. }
  321. }
  322. }
  323. return NSMakeRange (0, 0);
  324. });
  325. addMethod (@selector (accessibilityRangeForIndex:), [] (id self, SEL, NSInteger index)
  326. {
  327. if (auto* textInterface = getTextInterface (self))
  328. if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters()))
  329. return NSMakeRange ((NSUInteger) index, 1);
  330. return NSMakeRange (0, 0);
  331. });
  332. addMethod (@selector (accessibilityFrameForRange:), [] (id self, SEL, NSRange range)
  333. {
  334. if (auto* textInterface = getTextInterface (self))
  335. return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds()));
  336. return NSZeroRect;
  337. });
  338. addMethod (@selector (accessibilityLineForIndex:), [] (id self, SEL, NSInteger index)
  339. {
  340. if (auto* textInterface = getTextInterface (self))
  341. {
  342. auto text = textInterface->getText ({ 0, (int) index });
  343. if (! text.isEmpty())
  344. return StringArray::fromLines (text).size() - 1;
  345. }
  346. return 0;
  347. });
  348. addMethod (@selector (setAccessibilitySelectedTextRange:), [] (id self, SEL, NSRange selectedRange)
  349. {
  350. if (auto* textInterface = getTextInterface (self))
  351. textInterface->setSelection (nsRangeToJuce (selectedRange));
  352. });
  353. addMethod (@selector (accessibilityRows), [] (id self, SEL) -> NSArray*
  354. {
  355. if (auto* tableInterface = getTableInterface (self))
  356. {
  357. auto* rows = [[NSMutableArray new] autorelease];
  358. for (int row = 0, numRows = tableInterface->getNumRows(); row < numRows; ++row)
  359. {
  360. if (auto* rowHandler = tableInterface->getRowHandler (row))
  361. {
  362. [rows addObject: static_cast<id> (rowHandler->getNativeImplementation())];
  363. }
  364. else
  365. {
  366. [rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
  367. frame: NSZeroRect
  368. label: @"Offscreen Row"
  369. parent: self]];
  370. }
  371. }
  372. return rows;
  373. }
  374. return nil;
  375. });
  376. addMethod (@selector (accessibilitySelectedRows), [] (id self, SEL)
  377. {
  378. return getSelectedChildren ([self accessibilityRows]);
  379. });
  380. addMethod (@selector (setAccessibilitySelectedRows:), [] (id self, SEL, NSArray* selected)
  381. {
  382. setSelectedChildren ([self accessibilityRows], selected);
  383. });
  384. addMethod (@selector (accessibilityHeader), [] (id self, SEL) -> id
  385. {
  386. if (auto* tableInterface = getTableInterface (self))
  387. if (auto* handler = tableInterface->getHeaderHandler())
  388. return static_cast<id> (handler->getNativeImplementation());
  389. return nil;
  390. });
  391. addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
  392. addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
  393. addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange);
  394. addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange);
  395. addMethod (@selector (accessibilityIndex), [] (id self, SEL) -> NSInteger
  396. {
  397. if (auto* handler = getHandler (self))
  398. {
  399. if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (handler, &AccessibilityHandler::getTableInterface))
  400. {
  401. if (auto* tableInterface = tableHandler->getTableInterface())
  402. {
  403. NSAccessibilityRole handlerRole = [self accessibilityRole];
  404. if ([handlerRole isEqual: NSAccessibilityRowRole])
  405. if (const auto span = tableInterface->getRowSpan (*handler))
  406. return span->begin;
  407. if ([handlerRole isEqual: NSAccessibilityColumnRole])
  408. if (const auto span = tableInterface->getColumnSpan (*handler))
  409. return span->begin;
  410. }
  411. }
  412. }
  413. return 0;
  414. });
  415. addMethod (@selector (accessibilityDisclosureLevel), [] (id self, SEL)
  416. {
  417. if (auto* handler = getHandler (self))
  418. if (auto* cellInterface = handler->getCellInterface())
  419. return cellInterface->getDisclosureLevel();
  420. return 0;
  421. });
  422. addMethod (@selector (accessibilityDisclosedRows), [] (id self, SEL) -> id
  423. {
  424. if (auto* handler = getHandler (self))
  425. {
  426. if (auto* cellInterface = handler->getCellInterface())
  427. {
  428. const auto rows = cellInterface->getDisclosedRows();
  429. auto* result = [NSMutableArray arrayWithCapacity: rows.size()];
  430. for (const auto& row : rows)
  431. {
  432. if (row != nullptr)
  433. [result addObject: static_cast<id> (row->getNativeImplementation())];
  434. else
  435. [result addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
  436. frame: NSZeroRect
  437. label: @"Offscreen Row"
  438. parent: self]];
  439. }
  440. return result;
  441. }
  442. }
  443. return nil;
  444. });
  445. addMethod (@selector (isAccessibilityExpanded), [] (id self, SEL) -> BOOL
  446. {
  447. if (auto* handler = getHandler (self))
  448. return handler->getCurrentState().isExpanded();
  449. return NO;
  450. });
  451. addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement);
  452. addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement);
  453. addMethod (@selector (accessibilityPerformDelete), [] (id self, SEL)
  454. {
  455. if (auto* handler = getHandler (self))
  456. {
  457. if (hasEditableText (*handler))
  458. {
  459. handler->getTextInterface()->setText ({});
  460. return YES;
  461. }
  462. if (auto* valueInterface = handler->getValueInterface())
  463. {
  464. if (! valueInterface->isReadOnly())
  465. {
  466. valueInterface->setValue ({});
  467. return YES;
  468. }
  469. }
  470. }
  471. return NO;
  472. });
  473. addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress);
  474. addMethod (@selector (accessibilityPerformShowMenu), [] (id self, SEL)
  475. {
  476. return performActionIfSupported (self, AccessibilityActionType::showMenu);
  477. });
  478. addMethod (@selector (accessibilityPerformRaise), [] (id self, SEL)
  479. {
  480. [self setAccessibilityFocused: YES]; return YES;
  481. });
  482. addMethod (@selector (isAccessibilitySelectorAllowed:), [] (id self, SEL, SEL selector) -> BOOL
  483. {
  484. if (auto* handler = getHandler (self))
  485. {
  486. const auto handlerRole = handler->getRole();
  487. const auto currentState = handler->getCurrentState();
  488. for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber),
  489. @selector (accessibilityVisibleCharacterRange),
  490. @selector (accessibilityNumberOfCharacters),
  491. @selector (accessibilitySelectedText),
  492. @selector (accessibilitySelectedTextRange),
  493. @selector (accessibilityAttributedStringForRange:),
  494. @selector (accessibilityRangeForLine:),
  495. @selector (accessibilityStringForRange:),
  496. @selector (accessibilityRangeForPosition:),
  497. @selector (accessibilityRangeForIndex:),
  498. @selector (accessibilityFrameForRange:),
  499. @selector (accessibilityLineForIndex:),
  500. @selector (setAccessibilitySelectedTextRange:) })
  501. {
  502. if (selector == textSelector)
  503. return handler->getTextInterface() != nullptr;
  504. }
  505. for (auto tableSelector : { @selector (accessibilityRowCount),
  506. @selector (accessibilityRows),
  507. @selector (accessibilitySelectedRows),
  508. @selector (accessibilityColumnCount),
  509. @selector (accessibilityHeader) })
  510. {
  511. if (selector == tableSelector)
  512. return handler->getTableInterface() != nullptr;
  513. }
  514. for (auto cellSelector : { @selector (accessibilityRowIndexRange),
  515. @selector (accessibilityColumnIndexRange),
  516. @selector (accessibilityIndex),
  517. @selector (accessibilityDisclosureLevel) })
  518. {
  519. if (selector == cellSelector)
  520. return handler->getCellInterface() != nullptr;
  521. }
  522. for (auto valueSelector : { @selector (accessibilityValue),
  523. @selector (setAccessibilityValue:),
  524. @selector (accessibilityPerformDelete),
  525. @selector (accessibilityPerformIncrement),
  526. @selector (accessibilityPerformDecrement) })
  527. {
  528. if (selector != valueSelector)
  529. continue;
  530. auto* valueInterface = handler->getValueInterface();
  531. if (selector == @selector (accessibilityValue))
  532. return valueInterface != nullptr
  533. || hasEditableText (*handler)
  534. || currentState.isCheckable();
  535. auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); };
  536. if (selector == @selector (setAccessibilityValue:)
  537. || selector == @selector (accessibilityPerformDelete))
  538. return hasEditableValue() || hasEditableText (*handler);
  539. auto isRanged = [valueInterface] { return valueInterface != nullptr && valueInterface->getRange().isValid(); };
  540. if (selector == @selector (accessibilityPerformIncrement)
  541. || selector == @selector (accessibilityPerformDecrement))
  542. return hasEditableValue() && isRanged();
  543. return NO;
  544. }
  545. for (auto actionSelector : { @selector (accessibilityPerformPress),
  546. @selector (accessibilityPerformShowMenu),
  547. @selector (accessibilityPerformRaise),
  548. @selector (setAccessibilityFocused:) })
  549. {
  550. if (selector != actionSelector)
  551. continue;
  552. if (selector == @selector (accessibilityPerformPress))
  553. return (handler->getCurrentState().isCheckable() && handler->getActions().contains (AccessibilityActionType::toggle))
  554. || handler->getActions().contains (AccessibilityActionType::press);
  555. if (selector == @selector (accessibilityPerformShowMenu))
  556. return handler->getActions().contains (AccessibilityActionType::showMenu);
  557. if (selector == @selector (accessibilityPerformRaise))
  558. return [[self accessibilityRole] isEqual: NSAccessibilityWindowRole];
  559. if (selector == @selector (setAccessibilityFocused:))
  560. return currentState.isFocusable();
  561. }
  562. if (selector == @selector (accessibilitySelectedChildren))
  563. return handlerRole == AccessibilityRole::popupMenu;
  564. if (selector == @selector (accessibilityOrientation))
  565. return handlerRole == AccessibilityRole::scrollBar;
  566. if (selector == @selector (isAccessibilityExpanded))
  567. return currentState.isExpandable();
  568. return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector);
  569. }
  570. return NO;
  571. });
  572. addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren);
  573. registerClass();
  574. }
  575. //==============================================================================
  576. static bool isSelectable (AccessibleState state) noexcept
  577. {
  578. return state.isSelectable() || state.isMultiSelectable();
  579. }
  580. static NSArray* getSelectedChildren (NSArray* children)
  581. {
  582. NSMutableArray* selected = [[NSMutableArray new] autorelease];
  583. for (id child in children)
  584. {
  585. if (auto* handler = getHandler (child))
  586. {
  587. const auto currentState = handler->getCurrentState();
  588. if (isSelectable (currentState) && currentState.isSelected())
  589. [selected addObject: child];
  590. }
  591. }
  592. return selected;
  593. }
  594. static void setSelected (id item, bool selected)
  595. {
  596. auto* handler = getHandler (item);
  597. if (handler == nullptr)
  598. return;
  599. const auto currentState = handler->getCurrentState();
  600. if (isSelectable (currentState))
  601. {
  602. if (currentState.isSelected() != selected)
  603. handler->getActions().invoke (AccessibilityActionType::toggle);
  604. }
  605. else if (currentState.isFocusable())
  606. {
  607. [item setAccessibilityFocused: selected];
  608. }
  609. }
  610. static void setSelectedChildren (NSArray* children, NSArray* selected)
  611. {
  612. for (id child in children)
  613. setSelected (child, [selected containsObject: child]);
  614. }
  615. //==============================================================================
  616. static id getAccessibilityWindow (id self, SEL)
  617. {
  618. return [[self accessibilityParent] accessibilityWindow];
  619. }
  620. static NSArray* getAccessibilityChildren (id self, SEL)
  621. {
  622. if (auto* handler = getHandler (self))
  623. {
  624. auto children = handler->getChildren();
  625. auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
  626. for (auto* childHandler : children)
  627. [accessibleChildren addObject: static_cast<id> (childHandler->getNativeImplementation())];
  628. if (auto* nativeChild = AccessibilityHandler::getNativeChildForComponent (handler->getComponent()))
  629. [accessibleChildren addObject: static_cast<id> (nativeChild)];
  630. return accessibleChildren;
  631. }
  632. return nil;
  633. }
  634. //==============================================================================
  635. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
  636. };
  637. //==============================================================================
  638. API_AVAILABLE (macos (10.10))
  639. AccessibilityElement::Holder accessibilityElement;
  640. //==============================================================================
  641. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
  642. };
  643. //==============================================================================
  644. AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
  645. {
  646. if (@available (macOS 10.10, *))
  647. return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
  648. return nullptr;
  649. }
  650. static bool areAnyAccessibilityClientsActive()
  651. {
  652. const String voiceOverKeyString ("voiceOverOnOffKey");
  653. const String applicationIDString ("com.apple.universalaccess");
  654. CFUniquePtr<CFStringRef> cfKey (voiceOverKeyString.toCFString());
  655. CFUniquePtr<CFStringRef> cfID (applicationIDString.toCFString());
  656. CFUniquePtr<CFPropertyListRef> value (CFPreferencesCopyAppValue (cfKey.get(), cfID.get()));
  657. if (value != nullptr)
  658. return CFBooleanGetValue ((CFBooleanRef) value.get());
  659. return false;
  660. }
  661. static void sendAccessibilityEvent (id accessibilityElement,
  662. NSAccessibilityNotificationName notification,
  663. NSDictionary* userInfo)
  664. {
  665. jassert (notification != NSAccessibilityNotificationName{});
  666. NSAccessibilityPostNotificationWithUserInfo (accessibilityElement, notification, userInfo);
  667. }
  668. static void sendHandlerNotification (const AccessibilityHandler& handler,
  669. NSAccessibilityNotificationName notification)
  670. {
  671. if (! areAnyAccessibilityClientsActive() || notification == NSAccessibilityNotificationName{})
  672. return;
  673. if (@available (macOS 10.9, *))
  674. {
  675. if (id accessibilityElement = static_cast<id> (handler.getNativeImplementation()))
  676. {
  677. sendAccessibilityEvent (accessibilityElement, notification,
  678. (notification == NSAccessibilityLayoutChangedNotification
  679. ? @{ NSAccessibilityUIElementsKey: @[ accessibilityElement ] }
  680. : nil));
  681. }
  682. }
  683. }
  684. static NSAccessibilityNotificationName layoutChangedNotification()
  685. {
  686. if (@available (macOS 10.9, *))
  687. return NSAccessibilityLayoutChangedNotification;
  688. static NSString* layoutChangedString = @"AXLayoutChanged";
  689. return layoutChangedString;
  690. }
  691. void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
  692. {
  693. auto notification = [eventType]
  694. {
  695. switch (eventType)
  696. {
  697. case Event::elementCreated: return NSAccessibilityCreatedNotification;
  698. case Event::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification;
  699. case Event::elementMovedOrResized: return layoutChangedNotification();
  700. case Event::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification;
  701. case Event::windowOpened: return NSAccessibilityWindowCreatedNotification;
  702. case Event::windowClosed: break;
  703. }
  704. return NSAccessibilityNotificationName{};
  705. }();
  706. sendHandlerNotification (handler, notification);
  707. }
  708. void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
  709. {
  710. auto notification = [eventType]
  711. {
  712. switch (eventType)
  713. {
  714. case AccessibilityEvent::textSelectionChanged: return NSAccessibilitySelectedTextChangedNotification;
  715. case AccessibilityEvent::rowSelectionChanged: return NSAccessibilitySelectedRowsChangedNotification;
  716. case AccessibilityEvent::textChanged:
  717. case AccessibilityEvent::valueChanged: return NSAccessibilityValueChangedNotification;
  718. case AccessibilityEvent::titleChanged: return NSAccessibilityTitleChangedNotification;
  719. case AccessibilityEvent::structureChanged: return layoutChangedNotification();
  720. }
  721. return NSAccessibilityNotificationName{};
  722. }();
  723. sendHandlerNotification (*this, notification);
  724. }
  725. void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
  726. {
  727. if (! areAnyAccessibilityClientsActive())
  728. return;
  729. if (@available (macOS 10.9, *))
  730. {
  731. auto nsPriority = [priority]
  732. {
  733. // The below doesn't get noticed by the @available check above
  734. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability")
  735. switch (priority)
  736. {
  737. case AnnouncementPriority::low: return NSAccessibilityPriorityLow;
  738. case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium;
  739. case AnnouncementPriority::high: return NSAccessibilityPriorityHigh;
  740. }
  741. jassertfalse;
  742. return NSAccessibilityPriorityLow;
  743. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  744. }();
  745. sendAccessibilityEvent (static_cast<id> ([NSApp mainWindow]),
  746. NSAccessibilityAnnouncementRequestedNotification,
  747. @{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
  748. NSAccessibilityPriorityKey: @(nsPriority) });
  749. }
  750. }
  751. } // namespace juce