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.

618 lines
24KB

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