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.

620 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. using namespace ComTypes::Constants;
  245. switch (attributeId)
  246. {
  247. case UIA_IsReadOnlyAttributeId:
  248. {
  249. VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
  250. break;
  251. }
  252. case UIA_CaretPositionAttributeId:
  253. {
  254. auto cursorPos = textInterface.getTextInsertionOffset();
  255. auto caretPos = [&]
  256. {
  257. if (cursorPos == 0)
  258. return ComTypes::CaretPosition_BeginningOfLine;
  259. if (cursorPos == textInterface.getTotalNumCharacters())
  260. return ComTypes::CaretPosition_EndOfLine;
  261. return ComTypes::CaretPosition_Unknown;
  262. }();
  263. VariantHelpers::setInt (caretPos, pRetVal);
  264. break;
  265. }
  266. }
  267. return S_OK;
  268. });
  269. }
  270. JUCE_COMRESULT GetBoundingRectangles (SAFEARRAY** pRetVal) override
  271. {
  272. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  273. {
  274. auto rectangleList = textInterface.getTextBounds (selectionRange);
  275. auto numRectangles = rectangleList.getNumRectangles();
  276. *pRetVal = SafeArrayCreateVector (VT_R8, 0, 4 * (ULONG) numRectangles);
  277. if (*pRetVal == nullptr)
  278. return E_FAIL;
  279. if (numRectangles > 0)
  280. {
  281. double* doubleArr = nullptr;
  282. if (FAILED (SafeArrayAccessData (*pRetVal, reinterpret_cast<void**> (&doubleArr))))
  283. {
  284. SafeArrayDestroy (*pRetVal);
  285. return E_FAIL;
  286. }
  287. for (int i = 0; i < numRectangles; ++i)
  288. {
  289. auto r = Desktop::getInstance().getDisplays().logicalToPhysical (rectangleList.getRectangle (i));
  290. doubleArr[i * 4] = r.getX();
  291. doubleArr[i * 4 + 1] = r.getY();
  292. doubleArr[i * 4 + 2] = r.getWidth();
  293. doubleArr[i * 4 + 3] = r.getHeight();
  294. }
  295. if (FAILED (SafeArrayUnaccessData (*pRetVal)))
  296. {
  297. SafeArrayDestroy (*pRetVal);
  298. return E_FAIL;
  299. }
  300. }
  301. return S_OK;
  302. });
  303. }
  304. JUCE_COMRESULT GetChildren (SAFEARRAY** pRetVal) override
  305. {
  306. return withCheckedComArgs (pRetVal, *this, [&]
  307. {
  308. *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 0);
  309. return S_OK;
  310. });
  311. }
  312. JUCE_COMRESULT GetEnclosingElement (IRawElementProviderSimple** pRetVal) override
  313. {
  314. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  315. return withCheckedComArgs (pRetVal, *this, [&]
  316. {
  317. getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  318. return S_OK;
  319. });
  320. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  321. }
  322. JUCE_COMRESULT GetText (int maxLength, BSTR* pRetVal) override
  323. {
  324. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  325. {
  326. auto text = textInterface.getText (selectionRange);
  327. if (maxLength >= 0 && text.length() > maxLength)
  328. text = text.substring (0, maxLength);
  329. *pRetVal = SysAllocString ((const OLECHAR*) text.toWideCharPointer());
  330. return S_OK;
  331. });
  332. }
  333. JUCE_COMRESULT Move (ComTypes::TextUnit unit, int count, int* pRetVal) override
  334. {
  335. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  336. {
  337. using ATH = AccessibilityTextHelpers;
  338. const auto boundaryType = getBoundaryType (unit);
  339. const auto previousUnitBoundary = ATH::findTextBoundary (textInterface,
  340. selectionRange.getStart(),
  341. boundaryType,
  342. ATH::Direction::backwards,
  343. ATH::IncludeThisBoundary::yes,
  344. ATH::IncludeWhitespaceAfterWords::no);
  345. auto numMoved = 0;
  346. auto movedEndpoint = previousUnitBoundary;
  347. for (; numMoved < std::abs (count); ++numMoved)
  348. {
  349. const auto nextEndpoint = ATH::findTextBoundary (textInterface,
  350. movedEndpoint,
  351. boundaryType,
  352. count > 0 ? ATH::Direction::forwards : ATH::Direction::backwards,
  353. ATH::IncludeThisBoundary::no,
  354. count > 0 ? ATH::IncludeWhitespaceAfterWords::yes : ATH::IncludeWhitespaceAfterWords::no);
  355. if (nextEndpoint == movedEndpoint)
  356. break;
  357. movedEndpoint = nextEndpoint;
  358. }
  359. *pRetVal = numMoved;
  360. ExpandToEnclosingUnit (unit);
  361. return S_OK;
  362. });
  363. }
  364. JUCE_COMRESULT MoveEndpointByRange (ComTypes::TextPatternRangeEndpoint endpoint,
  365. ComTypes::ITextRangeProvider* targetRange,
  366. ComTypes::TextPatternRangeEndpoint targetEndpoint) override
  367. {
  368. if (targetRange == nullptr)
  369. return E_INVALIDARG;
  370. if (! isElementValid())
  371. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  372. if (owner->getHandler().getTextInterface() != nullptr)
  373. {
  374. auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
  375. auto targetPoint = (targetEndpoint == ComTypes::TextPatternRangeEndpoint_Start ? otherRange.getStart()
  376. : otherRange.getEnd());
  377. setEndpointChecked (endpoint, targetPoint);
  378. return S_OK;
  379. }
  380. return (HRESULT) UIA_E_NOTSUPPORTED;
  381. }
  382. JUCE_COMRESULT MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint endpoint,
  383. ComTypes::TextUnit unit,
  384. int count,
  385. int* pRetVal) override
  386. {
  387. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  388. {
  389. if (count == 0 || textInterface.getTotalNumCharacters() == 0)
  390. return S_OK;
  391. const auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
  392. : selectionRange.getEnd());
  393. using ATH = AccessibilityTextHelpers;
  394. const auto direction = (count > 0 ? ATH::Direction::forwards
  395. : ATH::Direction::backwards);
  396. const auto boundaryType = getBoundaryType (unit);
  397. auto movedEndpoint = endpointToMove;
  398. int numMoved = 0;
  399. for (; numMoved < std::abs (count); ++numMoved)
  400. {
  401. auto nextEndpoint = ATH::findTextBoundary (textInterface,
  402. movedEndpoint,
  403. boundaryType,
  404. direction,
  405. ATH::IncludeThisBoundary::no,
  406. direction == ATH::Direction::forwards ? ATH::IncludeWhitespaceAfterWords::yes
  407. : ATH::IncludeWhitespaceAfterWords::no);
  408. if (nextEndpoint == movedEndpoint)
  409. break;
  410. movedEndpoint = nextEndpoint;
  411. }
  412. *pRetVal = numMoved;
  413. setEndpointChecked (endpoint, movedEndpoint);
  414. return S_OK;
  415. });
  416. }
  417. JUCE_COMRESULT RemoveFromSelection() override
  418. {
  419. if (! isElementValid())
  420. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  421. if (auto* textInterface = owner->getHandler().getTextInterface())
  422. {
  423. textInterface->setSelection ({});
  424. return S_OK;
  425. }
  426. return (HRESULT) UIA_E_NOTSUPPORTED;
  427. }
  428. JUCE_COMRESULT ScrollIntoView (BOOL) override
  429. {
  430. if (! isElementValid())
  431. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  432. return (HRESULT) UIA_E_NOTSUPPORTED;
  433. }
  434. JUCE_COMRESULT Select() override
  435. {
  436. if (! isElementValid())
  437. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  438. if (auto* textInterface = owner->getHandler().getTextInterface())
  439. {
  440. textInterface->setSelection ({});
  441. textInterface->setSelection (selectionRange);
  442. return S_OK;
  443. }
  444. return (HRESULT) UIA_E_NOTSUPPORTED;
  445. }
  446. private:
  447. static AccessibilityTextHelpers::BoundaryType getBoundaryType (ComTypes::TextUnit unit)
  448. {
  449. switch (unit)
  450. {
  451. case ComTypes::TextUnit_Character:
  452. return AccessibilityTextHelpers::BoundaryType::character;
  453. case ComTypes::TextUnit_Format:
  454. case ComTypes::TextUnit_Word:
  455. return AccessibilityTextHelpers::BoundaryType::word;
  456. case ComTypes::TextUnit_Line:
  457. return AccessibilityTextHelpers::BoundaryType::line;
  458. case ComTypes::TextUnit_Paragraph:
  459. case ComTypes::TextUnit_Page:
  460. case ComTypes::TextUnit_Document:
  461. return AccessibilityTextHelpers::BoundaryType::document;
  462. };
  463. jassertfalse;
  464. return AccessibilityTextHelpers::BoundaryType::character;
  465. }
  466. void setEndpointChecked (ComTypes::TextPatternRangeEndpoint endpoint, int newEndpoint)
  467. {
  468. if (endpoint == ComTypes::TextPatternRangeEndpoint_Start)
  469. {
  470. if (selectionRange.getEnd() < newEndpoint)
  471. selectionRange.setEnd (newEndpoint);
  472. selectionRange.setStart (newEndpoint);
  473. }
  474. else
  475. {
  476. if (selectionRange.getStart() > newEndpoint)
  477. selectionRange.setStart (newEndpoint);
  478. selectionRange.setEnd (newEndpoint);
  479. }
  480. }
  481. ComSmartPtr<UIATextProvider> owner;
  482. Range<int> selectionRange;
  483. //==============================================================================
  484. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
  485. };
  486. //==============================================================================
  487. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
  488. };
  489. } // namespace juce