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.

585 lines
23KB

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