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.

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