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.

686 lines
26KB

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