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.

642 lines
24KB

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