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.

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