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.

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