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.

590 lines
23KB

  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<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. const auto boundaryType = getBoundaryType (unit);
  200. const auto start = AccessibilityTextHelpers::findTextBoundary (*textInterface,
  201. selectionRange.getStart(),
  202. boundaryType,
  203. AccessibilityTextHelpers::Direction::backwards);
  204. const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface,
  205. start,
  206. boundaryType,
  207. AccessibilityTextHelpers::Direction::forwards);
  208. selectionRange = Range<int> (start, end);
  209. return S_OK;
  210. }
  211. return (HRESULT) UIA_E_NOTSUPPORTED;
  212. }
  213. JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ComTypes::ITextRangeProvider** pRetVal) override
  214. {
  215. return withCheckedComArgs (pRetVal, *this, []
  216. {
  217. return S_OK;
  218. });
  219. }
  220. JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
  221. ComTypes::ITextRangeProvider** pRetVal) override
  222. {
  223. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  224. {
  225. auto selectionText = textInterface.getText (selectionRange);
  226. String textToSearchFor (text);
  227. auto offset = (backward ? (ignoreCase ? selectionText.lastIndexOfIgnoreCase (textToSearchFor) : selectionText.lastIndexOf (textToSearchFor))
  228. : (ignoreCase ? selectionText.indexOfIgnoreCase (textToSearchFor) : selectionText.indexOf (textToSearchFor)));
  229. if (offset != -1)
  230. *pRetVal = new UIATextRangeProvider (*owner, { offset, offset + textToSearchFor.length() });
  231. return S_OK;
  232. });
  233. }
  234. JUCE_COMRESULT GetAttributeValue (TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override
  235. {
  236. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  237. {
  238. VariantHelpers::clear (pRetVal);
  239. switch (attributeId)
  240. {
  241. case ComTypes::UIA_IsReadOnlyAttributeId:
  242. {
  243. VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
  244. break;
  245. }
  246. case ComTypes::UIA_CaretPositionAttributeId:
  247. {
  248. auto cursorPos = textInterface.getTextInsertionOffset();
  249. auto caretPos = [&]
  250. {
  251. if (cursorPos == 0)
  252. return ComTypes::CaretPosition_BeginningOfLine;
  253. if (cursorPos == textInterface.getTotalNumCharacters())
  254. return ComTypes::CaretPosition_EndOfLine;
  255. return ComTypes::CaretPosition_Unknown;
  256. }();
  257. VariantHelpers::setInt (caretPos, pRetVal);
  258. break;
  259. }
  260. }
  261. return S_OK;
  262. });
  263. }
  264. JUCE_COMRESULT GetBoundingRectangles (SAFEARRAY** pRetVal) override
  265. {
  266. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  267. {
  268. auto rectangleList = textInterface.getTextBounds (selectionRange);
  269. auto numRectangles = rectangleList.getNumRectangles();
  270. *pRetVal = SafeArrayCreateVector (VT_R8, 0, 4 * (ULONG) numRectangles);
  271. if (*pRetVal == nullptr)
  272. return E_FAIL;
  273. if (numRectangles > 0)
  274. {
  275. double* doubleArr = nullptr;
  276. if (FAILED (SafeArrayAccessData (*pRetVal, reinterpret_cast<void**> (&doubleArr))))
  277. {
  278. SafeArrayDestroy (*pRetVal);
  279. return E_FAIL;
  280. }
  281. for (int i = 0; i < numRectangles; ++i)
  282. {
  283. auto r = Desktop::getInstance().getDisplays().logicalToPhysical (rectangleList.getRectangle (i));
  284. doubleArr[i * 4] = r.getX();
  285. doubleArr[i * 4 + 1] = r.getY();
  286. doubleArr[i * 4 + 2] = r.getWidth();
  287. doubleArr[i * 4 + 3] = r.getHeight();
  288. }
  289. if (FAILED (SafeArrayUnaccessData (*pRetVal)))
  290. {
  291. SafeArrayDestroy (*pRetVal);
  292. return E_FAIL;
  293. }
  294. }
  295. return S_OK;
  296. });
  297. }
  298. JUCE_COMRESULT GetChildren (SAFEARRAY** pRetVal) override
  299. {
  300. return withCheckedComArgs (pRetVal, *this, [&]
  301. {
  302. *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 0);
  303. return S_OK;
  304. });
  305. }
  306. JUCE_COMRESULT GetEnclosingElement (IRawElementProviderSimple** pRetVal) override
  307. {
  308. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  309. return withCheckedComArgs (pRetVal, *this, [&]
  310. {
  311. getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  312. return S_OK;
  313. });
  314. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  315. }
  316. JUCE_COMRESULT GetText (int maxLength, BSTR* pRetVal) override
  317. {
  318. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  319. {
  320. auto text = textInterface.getText (selectionRange);
  321. if (maxLength >= 0 && text.length() > maxLength)
  322. text = text.substring (0, maxLength);
  323. *pRetVal = SysAllocString ((const OLECHAR*) text.toWideCharPointer());
  324. return S_OK;
  325. });
  326. }
  327. JUCE_COMRESULT Move (ComTypes::TextUnit unit, int count, int* pRetVal) override
  328. {
  329. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&)
  330. {
  331. if (count > 0)
  332. {
  333. MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal);
  334. MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal);
  335. }
  336. else if (count < 0)
  337. {
  338. MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal);
  339. MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal);
  340. }
  341. return S_OK;
  342. });
  343. }
  344. JUCE_COMRESULT MoveEndpointByRange (ComTypes::TextPatternRangeEndpoint endpoint,
  345. ComTypes::ITextRangeProvider* targetRange,
  346. ComTypes::TextPatternRangeEndpoint targetEndpoint) override
  347. {
  348. if (targetRange == nullptr)
  349. return E_INVALIDARG;
  350. if (! isElementValid())
  351. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  352. if (owner->getHandler().getTextInterface() != nullptr)
  353. {
  354. auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
  355. auto targetPoint = (targetEndpoint == ComTypes::TextPatternRangeEndpoint_Start ? otherRange.getStart()
  356. : otherRange.getEnd());
  357. setEndpointChecked (endpoint, targetPoint);
  358. return S_OK;
  359. }
  360. return (HRESULT) UIA_E_NOTSUPPORTED;
  361. }
  362. JUCE_COMRESULT MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint endpoint,
  363. ComTypes::TextUnit unit,
  364. int count,
  365. int* pRetVal) override
  366. {
  367. return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
  368. {
  369. if (count == 0 || textInterface.getTotalNumCharacters() == 0)
  370. return S_OK;
  371. auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
  372. : selectionRange.getEnd());
  373. const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards
  374. : AccessibilityTextHelpers::Direction::backwards);
  375. const auto boundaryType = getBoundaryType (unit);
  376. // handle case where endpoint is on a boundary
  377. if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove)
  378. endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1);
  379. int numMoved;
  380. for (numMoved = 0; numMoved < std::abs (count); ++numMoved)
  381. {
  382. auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface,
  383. endpointToMove,
  384. boundaryType,
  385. direction);
  386. if (nextEndpoint == endpointToMove)
  387. break;
  388. endpointToMove = nextEndpoint;
  389. }
  390. *pRetVal = numMoved;
  391. setEndpointChecked (endpoint, endpointToMove);
  392. return S_OK;
  393. });
  394. }
  395. JUCE_COMRESULT RemoveFromSelection() override
  396. {
  397. if (! isElementValid())
  398. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  399. if (auto* textInterface = owner->getHandler().getTextInterface())
  400. {
  401. textInterface->setSelection ({});
  402. return S_OK;
  403. }
  404. return (HRESULT) UIA_E_NOTSUPPORTED;
  405. }
  406. JUCE_COMRESULT ScrollIntoView (BOOL) override
  407. {
  408. if (! isElementValid())
  409. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  410. return (HRESULT) UIA_E_NOTSUPPORTED;
  411. }
  412. JUCE_COMRESULT Select() override
  413. {
  414. if (! isElementValid())
  415. return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
  416. if (auto* textInterface = owner->getHandler().getTextInterface())
  417. {
  418. textInterface->setSelection ({});
  419. textInterface->setSelection (selectionRange);
  420. return S_OK;
  421. }
  422. return (HRESULT) UIA_E_NOTSUPPORTED;
  423. }
  424. private:
  425. static AccessibilityTextHelpers::BoundaryType getBoundaryType (ComTypes::TextUnit unit)
  426. {
  427. switch (unit)
  428. {
  429. case ComTypes::TextUnit_Character:
  430. return AccessibilityTextHelpers::BoundaryType::character;
  431. case ComTypes::TextUnit_Format:
  432. case ComTypes::TextUnit_Word:
  433. return AccessibilityTextHelpers::BoundaryType::word;
  434. case ComTypes::TextUnit_Line:
  435. return AccessibilityTextHelpers::BoundaryType::line;
  436. case ComTypes::TextUnit_Paragraph:
  437. case ComTypes::TextUnit_Page:
  438. case ComTypes::TextUnit_Document:
  439. return AccessibilityTextHelpers::BoundaryType::document;
  440. };
  441. jassertfalse;
  442. return AccessibilityTextHelpers::BoundaryType::character;
  443. }
  444. void setEndpointChecked (ComTypes::TextPatternRangeEndpoint endpoint, int newEndpoint)
  445. {
  446. if (endpoint == ComTypes::TextPatternRangeEndpoint_Start)
  447. {
  448. if (selectionRange.getEnd() < newEndpoint)
  449. selectionRange.setEnd (newEndpoint);
  450. selectionRange.setStart (newEndpoint);
  451. }
  452. else
  453. {
  454. if (selectionRange.getStart() > newEndpoint)
  455. selectionRange.setStart (newEndpoint);
  456. selectionRange.setEnd (newEndpoint);
  457. }
  458. }
  459. ComSmartPtr<UIATextProvider> owner;
  460. Range<int> selectionRange;
  461. //==============================================================================
  462. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
  463. };
  464. //==============================================================================
  465. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
  466. };
  467. } // namespace juce