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.

665 lines
25KB

  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. return S_OK;
  211. }
  212. if (unit == TextUnit_Paragraph
  213. || unit == TextUnit_Page
  214. || unit == TextUnit_Document)
  215. {
  216. selectionRange = { 0, textInterface->getTotalNumCharacters() };
  217. return S_OK;
  218. }
  219. auto start = getNextEndpointPosition (*textInterface,
  220. selectionRange.getStart(),
  221. unit,
  222. NextEndpointDirection::backwards);
  223. if (start >= 0)
  224. {
  225. auto end = getNextEndpointPosition (*textInterface,
  226. start,
  227. unit,
  228. NextEndpointDirection::forwards);
  229. if (end >= 0)
  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. auto isStart = (endpoint == TextPatternRangeEndpoint_Start);
  403. auto endpointToMove = (isStart ? selectionRange.getStart() : selectionRange.getEnd());
  404. if (unit == TextUnit_Character)
  405. {
  406. 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. for (int i = 0; i < std::abs (count); ++i)
  422. {
  423. auto nextEndpoint = getNextEndpointPosition (textInterface,
  424. endpointToMove,
  425. unit,
  426. direction);
  427. if (nextEndpoint < 0)
  428. {
  429. *pRetVal = (direction == NextEndpointDirection::forwards ? i : -i);
  430. setEndpointChecked (endpoint, endpointToMove);
  431. return S_OK;
  432. }
  433. endpointToMove = nextEndpoint;
  434. }
  435. *pRetVal = count;
  436. setEndpointChecked (endpoint, endpointToMove);
  437. return S_OK;
  438. });
  439. }
  440. JUCE_COMRESULT RemoveFromSelection() override
  441. {
  442. if (! isElementValid())
  443. return UIA_E_ELEMENTNOTAVAILABLE;
  444. if (auto* textInterface = getHandler().getTextInterface())
  445. {
  446. textInterface->setSelection ({});
  447. return S_OK;
  448. }
  449. return UIA_E_NOTSUPPORTED;
  450. }
  451. JUCE_COMRESULT ScrollIntoView (BOOL) override
  452. {
  453. if (! isElementValid())
  454. return UIA_E_ELEMENTNOTAVAILABLE;
  455. return UIA_E_NOTSUPPORTED;
  456. }
  457. JUCE_COMRESULT Select() override
  458. {
  459. if (! isElementValid())
  460. return UIA_E_ELEMENTNOTAVAILABLE;
  461. if (auto* textInterface = getHandler().getTextInterface())
  462. {
  463. textInterface->setSelection ({});
  464. textInterface->setSelection (selectionRange);
  465. return S_OK;
  466. }
  467. return UIA_E_NOTSUPPORTED;
  468. }
  469. private:
  470. enum class NextEndpointDirection { forwards, backwards };
  471. static int getNextEndpointPosition (const AccessibilityTextInterface& textInterface,
  472. int currentPosition,
  473. TextUnit unit,
  474. NextEndpointDirection direction)
  475. {
  476. auto isTextUnitSeparator = [unit] (const juce_wchar c)
  477. {
  478. return ((unit == TextUnit_Word || unit == TextUnit_Format) && CharacterFunctions::isWhitespace (c))
  479. || (unit == TextUnit_Line && (c == '\r' || c == '\n'));
  480. };
  481. constexpr int textBufferSize = 1024;
  482. int numChars = 0;
  483. if (direction == NextEndpointDirection::forwards)
  484. {
  485. auto textBuffer = textInterface.getText ({ currentPosition,
  486. jmin (textInterface.getTotalNumCharacters(), currentPosition + textBufferSize) });
  487. for (auto charPtr = textBuffer.getCharPointer(); ! charPtr.isEmpty();)
  488. {
  489. auto character = charPtr.getAndAdvance();
  490. ++numChars;
  491. if (isTextUnitSeparator (character))
  492. return currentPosition + numChars;
  493. }
  494. }
  495. else
  496. {
  497. auto textBuffer = textInterface.getText ({ jmax (0, currentPosition - textBufferSize),
  498. currentPosition });
  499. for (auto charPtr = textBuffer.end() - 1; charPtr != textBuffer.begin(); --charPtr)
  500. {
  501. auto character = *charPtr;
  502. if (isTextUnitSeparator (character))
  503. return currentPosition - numChars;
  504. ++numChars;
  505. }
  506. }
  507. return -1;
  508. }
  509. void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint)
  510. {
  511. if (endpoint == TextPatternRangeEndpoint_Start)
  512. {
  513. if (selectionRange.getEnd() < newEndpoint)
  514. selectionRange.setEnd (newEndpoint);
  515. selectionRange.setStart (newEndpoint);
  516. }
  517. else
  518. {
  519. if (selectionRange.getStart() > newEndpoint)
  520. selectionRange.setStart (newEndpoint);
  521. selectionRange.setEnd (newEndpoint);
  522. }
  523. }
  524. ComSmartPtr<UIATextProvider> owner;
  525. Range<int> selectionRange;
  526. //==============================================================================
  527. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
  528. };
  529. //==============================================================================
  530. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
  531. };
  532. } // namespace juce