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.

593 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. //==============================================================================
  21. class UIATextProvider : public UIAProviderBase,
  22. public ComBaseClassHelper<ITextProvider2>
  23. {
  24. public:
  25. explicit UIATextProvider (AccessibilityNativeHandle* nativeHandle)
  26. : UIAProviderBase (nativeHandle)
  27. {
  28. }
  29. //==============================================================================
  30. JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
  31. {
  32. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  33. if (iid == _uuidof (IUnknown) || iid == _uuidof (ITextProvider))
  34. return castToType<ITextProvider> (result);
  35. if (iid == _uuidof (ITextProvider2))
  36. return castToType<ITextProvider2> (result);
  37. *result = nullptr;
  38. return E_NOINTERFACE;
  39. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  40. }
  41. //=============================================================================
  42. JUCE_COMRESULT get_DocumentRange (ITextRangeProvider** pRetVal) override
  43. {
  44. return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  45. {
  46. *pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
  47. return S_OK;
  48. });
  49. }
  50. JUCE_COMRESULT get_SupportedTextSelection (SupportedTextSelection* pRetVal) override
  51. {
  52. return withCheckedComArgs (pRetVal, *this, [&]
  53. {
  54. *pRetVal = SupportedTextSelection_Single;
  55. return S_OK;
  56. });
  57. }
  58. JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
  59. {
  60. return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  61. {
  62. *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
  63. if (pRetVal != nullptr)
  64. {
  65. auto selection = textInterface.getSelection();
  66. auto hasSelection = ! selection.isEmpty();
  67. auto cursorPos = textInterface.getTextInsertionOffset();
  68. auto* rangeProvider = new UIATextRangeProvider (*this,
  69. { hasSelection ? selection.getStart() : cursorPos,
  70. hasSelection ? selection.getEnd() : cursorPos });
  71. LONG pos = 0;
  72. auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
  73. if (FAILED (hr))
  74. return E_FAIL;
  75. rangeProvider->Release();
  76. }
  77. return S_OK;
  78. });
  79. }
  80. JUCE_COMRESULT GetVisibleRanges (SAFEARRAY** pRetVal) override
  81. {
  82. return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  83. {
  84. *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
  85. if (pRetVal != nullptr)
  86. {
  87. auto* rangeProvider = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
  88. LONG pos = 0;
  89. auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
  90. if (FAILED (hr))
  91. return E_FAIL;
  92. rangeProvider->Release();
  93. }
  94. return S_OK;
  95. });
  96. }
  97. JUCE_COMRESULT RangeFromChild (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override
  98. {
  99. return withCheckedComArgs (pRetVal, *this, []
  100. {
  101. return S_OK;
  102. });
  103. }
  104. JUCE_COMRESULT RangeFromPoint (UiaPoint point, ITextRangeProvider** pRetVal) override
  105. {
  106. return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  107. {
  108. auto offset = textInterface.getOffsetAtPoint ({ roundToInt (point.x), roundToInt (point.y) });
  109. if (offset > 0)
  110. *pRetVal = new UIATextRangeProvider (*this, { offset, offset });
  111. return S_OK;
  112. });
  113. }
  114. //==============================================================================
  115. JUCE_COMRESULT GetCaretRange (BOOL* isActive, ITextRangeProvider** pRetVal) override
  116. {
  117. return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  118. {
  119. *isActive = getHandler().hasFocus (false);
  120. auto cursorPos = textInterface.getTextInsertionOffset();
  121. *pRetVal = new UIATextRangeProvider (*this, { cursorPos, cursorPos });
  122. return S_OK;
  123. });
  124. }
  125. JUCE_COMRESULT RangeFromAnnotation (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override
  126. {
  127. return withCheckedComArgs (pRetVal, *this, []
  128. {
  129. return S_OK;
  130. });
  131. }
  132. private:
  133. //==============================================================================
  134. template <typename Value, typename Callback>
  135. JUCE_COMRESULT withTextInterface (Value* pRetVal, Callback&& callback) const
  136. {
  137. return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
  138. {
  139. if (auto* textInterface = getHandler().getTextInterface())
  140. return callback (*textInterface);
  141. return (HRESULT) UIA_E_NOTSUPPORTED;
  142. });
  143. }
  144. //==============================================================================
  145. class UIATextRangeProvider : public UIAProviderBase,
  146. public ComBaseClassHelper<ITextRangeProvider>
  147. {
  148. public:
  149. UIATextRangeProvider (UIATextProvider& textProvider, Range<int> range)
  150. : UIAProviderBase (textProvider.getHandler().getNativeImplementation()),
  151. owner (&textProvider),
  152. selectionRange (range)
  153. {
  154. }
  155. //==============================================================================
  156. Range<int> getSelectionRange() const noexcept { return selectionRange; }
  157. //==============================================================================
  158. JUCE_COMRESULT AddToSelection() override
  159. {
  160. return Select();
  161. }
  162. JUCE_COMRESULT Clone (ITextRangeProvider** pRetVal) override
  163. {
  164. return withCheckedComArgs (pRetVal, *this, [&]
  165. {
  166. *pRetVal = new UIATextRangeProvider (*owner, selectionRange);
  167. return S_OK;
  168. });
  169. }
  170. JUCE_COMRESULT Compare (ITextRangeProvider* range, BOOL* pRetVal) override
  171. {
  172. return withCheckedComArgs (pRetVal, *this, [&]
  173. {
  174. *pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange());
  175. return S_OK;
  176. });
  177. }
  178. JUCE_COMRESULT CompareEndpoints (TextPatternRangeEndpoint endpoint,
  179. ITextRangeProvider* targetRange,
  180. TextPatternRangeEndpoint targetEndpoint,
  181. int* pRetVal) override
  182. {
  183. if (targetRange == nullptr)
  184. return E_INVALIDARG;
  185. return withCheckedComArgs (pRetVal, *this, [&]
  186. {
  187. auto offset = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
  188. : selectionRange.getEnd());
  189. auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
  190. auto otherOffset = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
  191. : otherRange.getEnd());
  192. *pRetVal = offset - otherOffset;
  193. return S_OK;
  194. });
  195. }
  196. JUCE_COMRESULT ExpandToEnclosingUnit (TextUnit unit) override
  197. {
  198. if (! isElementValid())
  199. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  200. if (auto* textInterface = owner->getHandler().getTextInterface())
  201. {
  202. const auto boundaryType = getBoundaryType (unit);
  203. const auto start = AccessibilityTextHelpers::findTextBoundary (*textInterface,
  204. selectionRange.getStart(),
  205. boundaryType,
  206. AccessibilityTextHelpers::Direction::backwards);
  207. const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface,
  208. start,
  209. boundaryType,
  210. AccessibilityTextHelpers::Direction::forwards);
  211. selectionRange = Range<int> (start, end);
  212. return S_OK;
  213. }
  214. return (HRESULT) UIA_E_NOTSUPPORTED;
  215. }
  216. JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ITextRangeProvider** pRetVal) override
  217. {
  218. return withCheckedComArgs (pRetVal, *this, []
  219. {
  220. return S_OK;
  221. });
  222. }
  223. JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
  224. ITextRangeProvider** pRetVal) override
  225. {
  226. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  227. {
  228. auto selectionText = textInterface.getText (selectionRange);
  229. String textToSearchFor (text);
  230. auto offset = (backward ? (ignoreCase ? selectionText.lastIndexOfIgnoreCase (textToSearchFor) : selectionText.lastIndexOf (textToSearchFor))
  231. : (ignoreCase ? selectionText.indexOfIgnoreCase (textToSearchFor) : selectionText.indexOf (textToSearchFor)));
  232. if (offset != -1)
  233. *pRetVal = new UIATextRangeProvider (*owner, { offset, offset + textToSearchFor.length() });
  234. return S_OK;
  235. });
  236. }
  237. JUCE_COMRESULT GetAttributeValue (TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override
  238. {
  239. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  240. {
  241. VariantHelpers::clear (pRetVal);
  242. switch (attributeId)
  243. {
  244. case UIA_IsReadOnlyAttributeId:
  245. {
  246. VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
  247. break;
  248. }
  249. case UIA_CaretPositionAttributeId:
  250. {
  251. auto cursorPos = textInterface.getTextInsertionOffset();
  252. auto caretPos = [&]
  253. {
  254. if (cursorPos == 0)
  255. return CaretPosition_BeginningOfLine;
  256. if (cursorPos == textInterface.getTotalNumCharacters())
  257. return CaretPosition_EndOfLine;
  258. return CaretPosition_Unknown;
  259. }();
  260. VariantHelpers::setInt (caretPos, pRetVal);
  261. break;
  262. }
  263. }
  264. return S_OK;
  265. });
  266. }
  267. JUCE_COMRESULT GetBoundingRectangles (SAFEARRAY** pRetVal) override
  268. {
  269. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  270. {
  271. auto rectangleList = textInterface.getTextBounds (selectionRange);
  272. auto numRectangles = rectangleList.getNumRectangles();
  273. *pRetVal = SafeArrayCreateVector (VT_R8, 0, 4 * (ULONG) numRectangles);
  274. if (*pRetVal == nullptr)
  275. return E_FAIL;
  276. if (numRectangles > 0)
  277. {
  278. double* doubleArr = nullptr;
  279. if (FAILED (SafeArrayAccessData (*pRetVal, reinterpret_cast<void**> (&doubleArr))))
  280. {
  281. SafeArrayDestroy (*pRetVal);
  282. return E_FAIL;
  283. }
  284. for (int i = 0; i < numRectangles; ++i)
  285. {
  286. auto r = Desktop::getInstance().getDisplays().logicalToPhysical (rectangleList.getRectangle (i));
  287. doubleArr[i * 4] = r.getX();
  288. doubleArr[i * 4 + 1] = r.getY();
  289. doubleArr[i * 4 + 2] = r.getWidth();
  290. doubleArr[i * 4 + 3] = r.getHeight();
  291. }
  292. if (FAILED (SafeArrayUnaccessData (*pRetVal)))
  293. {
  294. SafeArrayDestroy (*pRetVal);
  295. return E_FAIL;
  296. }
  297. }
  298. return S_OK;
  299. });
  300. }
  301. JUCE_COMRESULT GetChildren (SAFEARRAY** pRetVal) override
  302. {
  303. return withCheckedComArgs (pRetVal, *this, [&]
  304. {
  305. *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 0);
  306. return S_OK;
  307. });
  308. }
  309. JUCE_COMRESULT GetEnclosingElement (IRawElementProviderSimple** pRetVal) override
  310. {
  311. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  312. return withCheckedComArgs (pRetVal, *this, [&]
  313. {
  314. getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  315. return S_OK;
  316. });
  317. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  318. }
  319. JUCE_COMRESULT GetText (int maxLength, BSTR* pRetVal) override
  320. {
  321. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  322. {
  323. auto text = textInterface.getText (selectionRange);
  324. if (maxLength >= 0 && text.length() > maxLength)
  325. text = text.substring (0, maxLength);
  326. *pRetVal = SysAllocString ((const OLECHAR*) text.toWideCharPointer());
  327. return S_OK;
  328. });
  329. }
  330. JUCE_COMRESULT Move (TextUnit unit, int count, int* pRetVal) override
  331. {
  332. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&)
  333. {
  334. if (count > 0)
  335. {
  336. MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
  337. MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
  338. }
  339. else if (count < 0)
  340. {
  341. MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
  342. MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
  343. }
  344. return S_OK;
  345. });
  346. }
  347. JUCE_COMRESULT MoveEndpointByRange (TextPatternRangeEndpoint endpoint,
  348. ITextRangeProvider* targetRange,
  349. TextPatternRangeEndpoint targetEndpoint) override
  350. {
  351. if (targetRange == nullptr)
  352. return E_INVALIDARG;
  353. if (! isElementValid())
  354. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  355. if (auto* textInterface = owner->getHandler().getTextInterface())
  356. {
  357. auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
  358. auto targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
  359. : otherRange.getEnd());
  360. setEndpointChecked (endpoint, targetPoint);
  361. return S_OK;
  362. }
  363. return (HRESULT) UIA_E_NOTSUPPORTED;
  364. }
  365. JUCE_COMRESULT MoveEndpointByUnit (TextPatternRangeEndpoint endpoint,
  366. TextUnit unit,
  367. int count,
  368. int* pRetVal) override
  369. {
  370. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  371. {
  372. if (count == 0 || textInterface.getTotalNumCharacters() == 0)
  373. return S_OK;
  374. auto endpointToMove = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
  375. : selectionRange.getEnd());
  376. const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards
  377. : AccessibilityTextHelpers::Direction::backwards);
  378. const auto boundaryType = getBoundaryType (unit);
  379. // handle case where endpoint is on a boundary
  380. if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove)
  381. endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1);
  382. int numMoved;
  383. for (numMoved = 0; numMoved < std::abs (count); ++numMoved)
  384. {
  385. auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface,
  386. endpointToMove,
  387. boundaryType,
  388. direction);
  389. if (nextEndpoint == endpointToMove)
  390. break;
  391. endpointToMove = nextEndpoint;
  392. }
  393. *pRetVal = numMoved;
  394. setEndpointChecked (endpoint, endpointToMove);
  395. return S_OK;
  396. });
  397. }
  398. JUCE_COMRESULT RemoveFromSelection() override
  399. {
  400. if (! isElementValid())
  401. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  402. if (auto* textInterface = owner->getHandler().getTextInterface())
  403. {
  404. textInterface->setSelection ({});
  405. return S_OK;
  406. }
  407. return (HRESULT) UIA_E_NOTSUPPORTED;
  408. }
  409. JUCE_COMRESULT ScrollIntoView (BOOL) override
  410. {
  411. if (! isElementValid())
  412. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  413. return (HRESULT) UIA_E_NOTSUPPORTED;
  414. }
  415. JUCE_COMRESULT Select() override
  416. {
  417. if (! isElementValid())
  418. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  419. if (auto* textInterface = owner->getHandler().getTextInterface())
  420. {
  421. textInterface->setSelection ({});
  422. textInterface->setSelection (selectionRange);
  423. return S_OK;
  424. }
  425. return (HRESULT) UIA_E_NOTSUPPORTED;
  426. }
  427. private:
  428. static AccessibilityTextHelpers::BoundaryType getBoundaryType (TextUnit unit)
  429. {
  430. switch (unit)
  431. {
  432. case TextUnit_Character:
  433. return AccessibilityTextHelpers::BoundaryType::character;
  434. case TextUnit_Format:
  435. case TextUnit_Word:
  436. return AccessibilityTextHelpers::BoundaryType::word;
  437. case TextUnit_Line:
  438. return AccessibilityTextHelpers::BoundaryType::line;
  439. case TextUnit_Paragraph:
  440. case TextUnit_Page:
  441. case TextUnit_Document:
  442. return AccessibilityTextHelpers::BoundaryType::document;
  443. };
  444. jassertfalse;
  445. return AccessibilityTextHelpers::BoundaryType::character;
  446. }
  447. void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint)
  448. {
  449. if (endpoint == TextPatternRangeEndpoint_Start)
  450. {
  451. if (selectionRange.getEnd() < newEndpoint)
  452. selectionRange.setEnd (newEndpoint);
  453. selectionRange.setStart (newEndpoint);
  454. }
  455. else
  456. {
  457. if (selectionRange.getStart() > newEndpoint)
  458. selectionRange.setStart (newEndpoint);
  459. selectionRange.setEnd (newEndpoint);
  460. }
  461. }
  462. ComSmartPtr<UIATextProvider> owner;
  463. Range<int> selectionRange;
  464. //==============================================================================
  465. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
  466. };
  467. //==============================================================================
  468. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
  469. };
  470. } // namespace juce