diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index 1e47b14a3d..7d61b26146 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -252,6 +252,10 @@ namespace juce #include "native/juce_MultiTouchMapper.h" #endif +#if JUCE_WINDOWS + #include "native/accessibility/juce_AccessibilityTextHelpers.h" +#endif + namespace juce { diff --git a/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h b/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h new file mode 100644 index 0000000000..3c74b331e7 --- /dev/null +++ b/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h @@ -0,0 +1,111 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace AccessibilityTextHelpers +{ + enum class BoundaryType + { + character, + word, + line, + document + }; + + enum class Direction + { + forwards, + backwards + }; + + static int findTextBoundary (const AccessibilityTextInterface& textInterface, + int currentPosition, + BoundaryType boundary, + Direction direction) + { + const auto numCharacters = textInterface.getTotalNumCharacters(); + const auto isForwards = (direction == Direction::forwards); + + auto offsetWithDirecton = [isForwards] (int input) { return isForwards ? input : -input; }; + + switch (boundary) + { + case BoundaryType::character: + return jlimit (0, numCharacters, currentPosition + offsetWithDirecton (1)); + + case BoundaryType::word: + case BoundaryType::line: + { + const auto text = [&]() -> String + { + if (isForwards) + return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() }); + + const auto str = textInterface.getText ({ 0, currentPosition }); + + auto start = str.getCharPointer(); + auto end = start.findTerminatingNull(); + const auto size = getAddressDifference (end.getAddress(), start.getAddress()); + + String reversed; + + if (size > 0) + { + reversed.preallocateBytes ((size_t) size); + + auto destPtr = reversed.getCharPointer(); + + for (;;) + { + destPtr.write (*--end); + + if (end == start) + break; + } + + destPtr.writeNull(); + } + + return reversed; + }(); + + auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text) + : StringArray::fromTokens (text, false)); + + return currentPosition + offsetWithDirecton (tokens[0].length()); + } + + case BoundaryType::document: + return isForwards ? numCharacters : 0; + } + + jassertfalse; + return -1; + } +} + +} // 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 1b480b4520..8b301401df 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h +++ b/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h @@ -249,43 +249,19 @@ private: if (auto* textInterface = owner->getHandler().getTextInterface()) { - auto numCharacters = textInterface->getTotalNumCharacters(); + const auto boundaryType = getBoundaryType (unit); - if (numCharacters == 0) - { - selectionRange = {}; - return S_OK; - } + const auto start = AccessibilityTextHelpers::findTextBoundary (*textInterface, + selectionRange.getStart(), + boundaryType, + AccessibilityTextHelpers::Direction::backwards); - if (unit == TextUnit_Character) - { - selectionRange.setStart (jlimit (0, numCharacters - 1, selectionRange.getStart())); - selectionRange.setEnd (selectionRange.getStart() + 1); - } - else if (unit == TextUnit_Paragraph - || unit == TextUnit_Page - || unit == TextUnit_Document) - { - selectionRange = { 0, numCharacters }; - } - else if (unit == TextUnit_Word - || unit == TextUnit_Format - || unit == TextUnit_Line) - { - const auto boundaryType = (unit == TextUnit_Line ? BoundaryType::line : BoundaryType::word); + const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface, + start, + boundaryType, + AccessibilityTextHelpers::Direction::forwards); - auto start = findBoundary (*textInterface, - selectionRange.getStart(), - boundaryType, - NextEndpointDirection::backwards); - - auto end = findBoundary (*textInterface, - start, - boundaryType, - NextEndpointDirection::forwards); - - selectionRange = Range (start, end); - } + selectionRange = Range (start, end); return S_OK; } @@ -485,60 +461,37 @@ private: { return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) { - auto numCharacters = textInterface.getTotalNumCharacters(); - - if (count == 0 || numCharacters == 0) + if (count == 0 || textInterface.getTotalNumCharacters() == 0) return S_OK; - const auto isStart = (endpoint == TextPatternRangeEndpoint_Start); - auto endpointToMove = (isStart ? selectionRange.getStart() : selectionRange.getEnd()); + auto endpointToMove = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart() + : selectionRange.getEnd()); - if (unit == TextUnit_Character) + const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards + : AccessibilityTextHelpers::Direction::backwards); + + const auto boundaryType = getBoundaryType (unit); + + // handle case where endpoint is on a boundary + if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove) + endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1); + + int numMoved; + for (numMoved = 0; numMoved < std::abs (count); ++numMoved) { - const auto targetPoint = jlimit (0, numCharacters, endpointToMove + count); + auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface, + endpointToMove, + boundaryType, + direction); - *pRetVal = targetPoint - endpointToMove; - setEndpointChecked (endpoint, targetPoint); + if (nextEndpoint == endpointToMove) + break; - return S_OK; + endpointToMove = nextEndpoint; } - auto direction = (count > 0 ? NextEndpointDirection::forwards - : NextEndpointDirection::backwards); - - if (unit == TextUnit_Paragraph - || unit == TextUnit_Page - || unit == TextUnit_Document) - { - *pRetVal = (direction == NextEndpointDirection::forwards ? 1 : -1); - setEndpointChecked (endpoint, numCharacters); - return S_OK; - } - - if (unit == TextUnit_Word - || unit == TextUnit_Format - || unit == TextUnit_Line) - { - const auto boundaryType = unit == TextUnit_Line ? BoundaryType::line : BoundaryType::word; - - // handle case where endpoint is on a boundary - if (findBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove) - endpointToMove += (direction == NextEndpointDirection::forwards ? 1 : -1); - - int numMoved; - for (numMoved = 0; numMoved < std::abs (count); ++numMoved) - { - auto nextEndpoint = findBoundary (textInterface, endpointToMove, boundaryType, direction); - - if (nextEndpoint == endpointToMove) - break; - - endpointToMove = nextEndpoint; - } - - *pRetVal = numMoved; - setEndpointChecked (endpoint, endpointToMove); - } + *pRetVal = numMoved; + setEndpointChecked (endpoint, endpointToMove); return S_OK; }); @@ -583,28 +536,28 @@ private: } private: - enum class NextEndpointDirection { forwards, backwards }; - enum class BoundaryType { word, line }; - - static int findBoundary (const AccessibilityTextInterface& textInterface, - int currentPosition, - BoundaryType boundary, - NextEndpointDirection direction) + static AccessibilityTextHelpers::BoundaryType getBoundaryType (TextUnit unit) { - const auto text = [&]() -> String + switch (unit) { - if (direction == NextEndpointDirection::forwards) - return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() }); + case TextUnit_Character: + return AccessibilityTextHelpers::BoundaryType::character; - auto stdString = textInterface.getText ({ 0, currentPosition }).toStdString(); - std::reverse (stdString.begin(), stdString.end()); - return stdString; - }(); + case TextUnit_Format: + case TextUnit_Word: + return AccessibilityTextHelpers::BoundaryType::word; - auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text) - : StringArray::fromTokens (text, false)); + case TextUnit_Line: + return AccessibilityTextHelpers::BoundaryType::line; - return currentPosition + (direction == NextEndpointDirection::forwards ? tokens[0].length() : -(tokens[0].length())); + case TextUnit_Paragraph: + case TextUnit_Page: + case TextUnit_Document: + return AccessibilityTextHelpers::BoundaryType::document; + }; + + jassertfalse; + return AccessibilityTextHelpers::BoundaryType::character; } void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint)