From 963fd79e6a5c884fc1ee901569f07f507cbec52d Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 28 May 2021 14:49:52 +0100 Subject: [PATCH] Windows Accessibility: Improved support for read-only text interfaces and fixed issue causing text past 1000 characters to not be read out by Narrator --- .../juce_win32_AccessibilityElement.cpp | 15 ++--- .../accessibility/juce_win32_UIAHelpers.h | 5 +- .../juce_win32_UIATextProvider.h | 55 +++++++++++++++++-- .../juce_win32_UIAValueProvider.h | 6 -- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp b/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp index f0fb0c9bd9..af26f879ca 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp +++ b/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp @@ -158,16 +158,18 @@ JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUn case UIA_TextPatternId: case UIA_TextPattern2Id: { - if (accessibilityHandler.getTextInterface() != nullptr) + if (accessibilityHandler.getTextInterface() != nullptr + || isReadOnlyText (accessibilityHandler)) + { return new UIATextProvider (this); + } break; } case UIA_ValuePatternId: { if (accessibilityHandler.getValueInterface() != nullptr - || isEditableText (accessibilityHandler) - || nameIsAccessibilityValue (role)) + || isEditableText (accessibilityHandler)) { return new UIAValueProvider (this); } @@ -539,12 +541,7 @@ JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (IRawElementProviderFragment* //============================================================================== String AccessibilityNativeHandle::getElementName() const { - const auto role = accessibilityHandler.getRole(); - - if (nameIsAccessibilityValue (role)) - return {}; - - if (role == AccessibilityRole::tooltip) + if (accessibilityHandler.getRole() == AccessibilityRole::tooltip) return accessibilityHandler.getDescription(); auto name = accessibilityHandler.getTitle(); diff --git a/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h b/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h index c0e2b5b8c7..98711c20ff 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h +++ b/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h @@ -106,9 +106,10 @@ inline bool isEditableText (const AccessibilityHandler& handler) && handler.getTextInterface() != nullptr; } -inline bool nameIsAccessibilityValue (AccessibilityRole role) +inline bool isReadOnlyText (const AccessibilityHandler& handler) { - return role == AccessibilityRole::staticText; + return handler.getRole() == AccessibilityRole::staticText + && handler.getValueInterface() != nullptr; } } // namespace juce diff --git a/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h b/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h index c4fcd2848b..211bc57bab 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h +++ b/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h @@ -34,6 +34,10 @@ public: explicit UIATextProvider (AccessibilityNativeHandle* nativeHandle) : UIAProviderBase (nativeHandle) { + const auto& handler = getHandler(); + + if (isReadOnlyText (handler)) + readOnlyTextInterface = std::make_unique (handler.getValueInterface()->getCurrentValueAsString()); } //============================================================================== @@ -63,7 +67,9 @@ public: { return withCheckedComArgs (pRetVal, *this, [&] { - *pRetVal = SupportedTextSelection_Single; + *pRetVal = (readOnlyTextInterface != nullptr ? SupportedTextSelection_None + : SupportedTextSelection_Single); + return S_OK; }); } @@ -163,6 +169,15 @@ public: }); } + //============================================================================== + AccessibilityTextInterface* getTextInterface() const + { + if (readOnlyTextInterface != nullptr) + return readOnlyTextInterface.get(); + + return getHandler().getTextInterface(); + } + private: //============================================================================== template @@ -170,7 +185,7 @@ private: { return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT { - if (auto* textInterface = getHandler().getTextInterface()) + if (auto* textInterface = getTextInterface()) return callback (*textInterface); return UIA_E_NOTSUPPORTED; @@ -243,7 +258,7 @@ private: if (! isElementValid()) return UIA_E_ELEMENTNOTAVAILABLE; - if (auto* textInterface = getHandler().getTextInterface()) + if (auto* textInterface = owner->getTextInterface()) { auto numCharacters = textInterface->getTotalNumCharacters(); @@ -468,7 +483,7 @@ private: if (! isElementValid()) return UIA_E_ELEMENTNOTAVAILABLE; - if (auto* textInterface = getHandler().getTextInterface()) + if (auto* textInterface = owner->getTextInterface()) { auto otherRange = static_cast (targetRange)->getSelectionRange(); auto targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart() @@ -552,7 +567,7 @@ private: if (! isElementValid()) return UIA_E_ELEMENTNOTAVAILABLE; - if (auto* textInterface = getHandler().getTextInterface()) + if (auto* textInterface = owner->getTextInterface()) { textInterface->setSelection ({}); return S_OK; @@ -574,7 +589,7 @@ private: if (! isElementValid()) return UIA_E_ELEMENTNOTAVAILABLE; - if (auto* textInterface = getHandler().getTextInterface()) + if (auto* textInterface = owner->getTextInterface()) { textInterface->setSelection ({}); textInterface->setSelection (selectionRange); @@ -635,6 +650,34 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider) }; + //============================================================================== + class ReadOnlyTextInterface : public AccessibilityTextInterface + { + public: + explicit ReadOnlyTextInterface (const String& t) + : text (t) + { + } + + bool isDisplayingProtectedText() const override { return false; } + int getTotalNumCharacters() const { return text.length(); } + Range getSelection() const override { return selection; } + void setSelection (Range s) override { selection = s; } + int getTextInsertionOffset() const override { return 0; } + String getText (Range range) const override { return text.substring (range.getStart(), range.getEnd()); } + void setText (const String&) override {} + RectangleList getTextBounds (Range) const override { return {}; } + int getOffsetAtPoint (Point) const override { return 0; } + + private: + const String text; + Range selection; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReadOnlyTextInterface) + }; + + std::unique_ptr readOnlyTextInterface; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider) }; diff --git a/modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h b/modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h index 4a8b9f28e2..0a53c9f5cf 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h +++ b/modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h @@ -44,9 +44,6 @@ public: const auto& handler = getHandler(); - if (nameIsAccessibilityValue (handler.getRole())) - return UIA_E_NOTSUPPORTED; - const auto sendValuePropertyChangeMessage = [&]() { VARIANT newValue; @@ -112,9 +109,6 @@ private: { const auto& handler = getHandler(); - if (nameIsAccessibilityValue (handler.getRole())) - return handler.getTitle(); - if (isEditableText (handler)) if (auto* textInterface = getHandler().getTextInterface()) return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });