1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct AccessibilityTextHelpers
{
/* Wraps a CharPtr into a stdlib-compatible iterator.
MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible
when building in C++20 mode, but I don't really want to add public default constructors to
the CharPtr types. Instead, we add a very basic default constructor here which sets the
wrapped CharPtr to nullptr.
*/
template <typename CharPtr>
class CharPtrIteratorAdapter
{
public:
using difference_type = int;
using value_type = decltype (*std::declval<CharPtr>());
using pointer = value_type*;
using reference = value_type;
using iterator_category = std::bidirectional_iterator_tag;
CharPtrIteratorAdapter() = default;
constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {}
constexpr auto operator*() const { return *ptr; }
constexpr CharPtrIteratorAdapter& operator++()
{
++ptr;
return *this;
}
constexpr CharPtrIteratorAdapter& operator--()
{
--ptr;
return *this;
}
constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; }
constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; }
constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; }
constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; }
private:
CharPtr ptr { {} };
};
template <typename CharPtr>
static auto makeCharPtrIteratorAdapter (CharPtr ptr)
{
return CharPtrIteratorAdapter<CharPtr> { ptr };
}
enum class BoundaryType
{
character,
word,
line,
document
};
enum class Direction
{
forwards,
backwards
};
enum class ExtendSelection
{
no,
yes
};
/* Indicates whether a function may return the current text position, in the case that the
position already falls on a text unit boundary.
*/
enum class IncludeThisBoundary
{
no, //< Always search for the following boundary, even if the current position falls on a boundary
yes //< Return the current position if it falls on a boundary
};
/* Indicates whether a word boundary should include any whitespaces that follow the
non-whitespace characters.
*/
enum class IncludeWhitespaceAfterWords
{
no, //< The word ends on the first whitespace character
yes //< The word ends after the last whitespace character
};
/* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't
require the iterators to have any member type aliases.
*/
template <typename Iter>
static int countDifference (Iter from, Iter to)
{
int distance = 0;
while (from != to)
{
++from;
++distance;
}
return distance;
}
/* Returns the number of characters between ptr and the next word end in a specific
direction.
If ptr is inside a word, the result will be the distance to the end of the same
word.
*/
template <typename CharPtr>
static int findNextWordEndOffset (CharPtr beginIn,
CharPtr endIn,
CharPtr ptrIn,
Direction direction,
IncludeThisBoundary includeBoundary,
IncludeWhitespaceAfterWords includeWhitespace)
{
const auto begin = makeCharPtrIteratorAdapter (beginIn);
const auto end = makeCharPtrIteratorAdapter (endIn);
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
const auto move = [&] (auto b, auto e, auto iter)
{
const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); };
const auto start = [&]
{
if (iter == b && includeBoundary == IncludeThisBoundary::yes)
return b;
const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0);
return includeWhitespace == IncludeWhitespaceAfterWords::yes
? std::find_if (nudged, e, isSpace)
: std::find_if_not (nudged, e, isSpace);
}();
const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes
? std::find_if_not (start, e, isSpace)
: std::find_if (start, e, isSpace);
return countDifference (iter, found);
};
return direction == Direction::forwards ? move (begin, end, ptr)
: -move (std::make_reverse_iterator (end),
std::make_reverse_iterator (begin),
std::make_reverse_iterator (ptr));
}
/* Returns the number of characters between ptr and the beginning of the next line in a
specific direction.
*/
template <typename CharPtr>
static int findNextLineOffset (CharPtr beginIn,
CharPtr endIn,
CharPtr ptrIn,
Direction direction,
IncludeThisBoundary includeBoundary)
{
const auto begin = makeCharPtrIteratorAdapter (beginIn);
const auto end = makeCharPtrIteratorAdapter (endIn);
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); };
if (direction == Direction::forwards)
{
if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n')
return 0;
const auto newline = findNewline (ptr, end);
return countDifference (ptr, newline) + (newline == end ? 0 : 1);
}
const auto rbegin = std::make_reverse_iterator (ptr);
const auto rend = std::make_reverse_iterator (begin);
return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend));
}
/* Unfortunately, the method of computing end-points of text units depends on context, and on
the current platform.
Some examples of different behaviour:
- On Android, updating the cursor/selection always searches for the next text unit boundary;
but on Windows, ExpandToEnclosingUnit() should not move the starting point of the
selection if it already at a unit boundary. This means that we need both inclusive and
exclusive methods for finding the next text boundary.
- On Android, moving the cursor by 'words' should move to the first space following a
non-space character in the requested direction. On Windows, a 'word' includes trailing
whitespace, but not preceding whitespace. This means that we need a way of specifying
whether whitespace should be included when navigating by words.
*/
static int findTextBoundary (const AccessibilityTextInterface& textInterface,
int currentPosition,
BoundaryType boundary,
Direction direction,
IncludeThisBoundary includeBoundary,
IncludeWhitespaceAfterWords includeWhitespace)
{
const auto numCharacters = textInterface.getTotalNumCharacters();
const auto isForwards = (direction == Direction::forwards);
const auto currentClamped = jlimit (0, numCharacters, currentPosition);
switch (boundary)
{
case BoundaryType::character:
{
const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0
: (isForwards ? 1 : -1);
return jlimit (0, numCharacters, currentPosition + offset);
}
case BoundaryType::word:
{
const auto str = textInterface.getText ({ 0, numCharacters });
return currentClamped + findNextWordEndOffset (str.begin(),
str.end(),
str.begin() + currentClamped,
direction,
includeBoundary,
includeWhitespace);
}
case BoundaryType::line:
{
const auto str = textInterface.getText ({ 0, numCharacters });
return currentClamped + findNextLineOffset (str.begin(),
str.end(),
str.begin() + currentClamped,
direction,
includeBoundary);
}
case BoundaryType::document:
return isForwards ? numCharacters : 0;
}
jassertfalse;
return -1;
}
/* Adjusts the current text selection range, using an algorithm appropriate for cursor movement
on Android.
*/
static Range<int> findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface,
BoundaryType boundaryType,
ExtendSelection extend,
Direction direction)
{
const auto oldPos = textInterface.getTextInsertionOffset();
const auto cursorPos = findTextBoundary (textInterface,
oldPos,
boundaryType,
direction,
IncludeThisBoundary::no,
IncludeWhitespaceAfterWords::no);
if (extend == ExtendSelection::no)
return { cursorPos, cursorPos };
const auto currentSelection = textInterface.getSelection();
const auto start = currentSelection.getStart();
const auto end = currentSelection.getEnd();
return Range<int>::between (cursorPos, oldPos == start ? end : start);
}
};
} // namespace juce
|