VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_basics / native / accessibility / juce_AccessibilityTextHelpers.h
blob35ca63fed953a6d6b5f3eb7400d839da247b10b8
1 /*
2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
23 ==============================================================================
26 namespace juce
29 struct AccessibilityTextHelpers
31 /* Wraps a CharPtr into a stdlib-compatible iterator.
33 MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible
34 when building in C++20 mode, but I don't really want to add public default constructors to
35 the CharPtr types. Instead, we add a very basic default constructor here which sets the
36 wrapped CharPtr to nullptr.
38 template <typename CharPtr>
39 class CharPtrIteratorAdapter
41 public:
42 using difference_type = int;
43 using value_type = decltype (*std::declval<CharPtr>());
44 using pointer = value_type*;
45 using reference = value_type;
46 using iterator_category = std::bidirectional_iterator_tag;
48 CharPtrIteratorAdapter() = default;
49 constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {}
51 constexpr auto operator*() const { return *ptr; }
53 constexpr CharPtrIteratorAdapter& operator++()
55 ++ptr;
56 return *this;
59 constexpr CharPtrIteratorAdapter& operator--()
61 --ptr;
62 return *this;
65 constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; }
66 constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; }
68 constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; }
69 constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; }
71 private:
72 CharPtr ptr { {} };
75 template <typename CharPtr>
76 static auto makeCharPtrIteratorAdapter (CharPtr ptr)
78 return CharPtrIteratorAdapter<CharPtr> { ptr };
81 enum class BoundaryType
83 character,
84 word,
85 line,
86 document
89 enum class Direction
91 forwards,
92 backwards
95 enum class ExtendSelection
97 no,
98 yes
101 /* Indicates whether a function may return the current text position, in the case that the
102 position already falls on a text unit boundary.
104 enum class IncludeThisBoundary
106 no, //< Always search for the following boundary, even if the current position falls on a boundary
107 yes //< Return the current position if it falls on a boundary
110 /* Indicates whether a word boundary should include any whitespaces that follow the
111 non-whitespace characters.
113 enum class IncludeWhitespaceAfterWords
115 no, //< The word ends on the first whitespace character
116 yes //< The word ends after the last whitespace character
119 /* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't
120 require the iterators to have any member type aliases.
122 template <typename Iter>
123 static int countDifference (Iter from, Iter to)
125 int distance = 0;
127 while (from != to)
129 ++from;
130 ++distance;
133 return distance;
136 /* Returns the number of characters between ptr and the next word end in a specific
137 direction.
139 If ptr is inside a word, the result will be the distance to the end of the same
140 word.
142 template <typename CharPtr>
143 static int findNextWordEndOffset (CharPtr beginIn,
144 CharPtr endIn,
145 CharPtr ptrIn,
146 Direction direction,
147 IncludeThisBoundary includeBoundary,
148 IncludeWhitespaceAfterWords includeWhitespace)
150 const auto begin = makeCharPtrIteratorAdapter (beginIn);
151 const auto end = makeCharPtrIteratorAdapter (endIn);
152 const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
154 const auto move = [&] (auto b, auto e, auto iter)
156 const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); };
158 const auto start = [&]
160 if (iter == b && includeBoundary == IncludeThisBoundary::yes)
161 return b;
163 const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0);
165 return includeWhitespace == IncludeWhitespaceAfterWords::yes
166 ? std::find_if (nudged, e, isSpace)
167 : std::find_if_not (nudged, e, isSpace);
168 }();
170 const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes
171 ? std::find_if_not (start, e, isSpace)
172 : std::find_if (start, e, isSpace);
174 return countDifference (iter, found);
177 return direction == Direction::forwards ? move (begin, end, ptr)
178 : -move (std::make_reverse_iterator (end),
179 std::make_reverse_iterator (begin),
180 std::make_reverse_iterator (ptr));
183 /* Returns the number of characters between ptr and the beginning of the next line in a
184 specific direction.
186 template <typename CharPtr>
187 static int findNextLineOffset (CharPtr beginIn,
188 CharPtr endIn,
189 CharPtr ptrIn,
190 Direction direction,
191 IncludeThisBoundary includeBoundary)
193 const auto begin = makeCharPtrIteratorAdapter (beginIn);
194 const auto end = makeCharPtrIteratorAdapter (endIn);
195 const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
197 const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); };
199 if (direction == Direction::forwards)
201 if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n')
202 return 0;
204 const auto newline = findNewline (ptr, end);
205 return countDifference (ptr, newline) + (newline == end ? 0 : 1);
208 const auto rbegin = std::make_reverse_iterator (ptr);
209 const auto rend = std::make_reverse_iterator (begin);
211 return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend));
214 /* Unfortunately, the method of computing end-points of text units depends on context, and on
215 the current platform.
217 Some examples of different behaviour:
218 - On Android, updating the cursor/selection always searches for the next text unit boundary;
219 but on Windows, ExpandToEnclosingUnit() should not move the starting point of the
220 selection if it already at a unit boundary. This means that we need both inclusive and
221 exclusive methods for finding the next text boundary.
222 - On Android, moving the cursor by 'words' should move to the first space following a
223 non-space character in the requested direction. On Windows, a 'word' includes trailing
224 whitespace, but not preceding whitespace. This means that we need a way of specifying
225 whether whitespace should be included when navigating by words.
227 static int findTextBoundary (const AccessibilityTextInterface& textInterface,
228 int currentPosition,
229 BoundaryType boundary,
230 Direction direction,
231 IncludeThisBoundary includeBoundary,
232 IncludeWhitespaceAfterWords includeWhitespace)
234 const auto numCharacters = textInterface.getTotalNumCharacters();
235 const auto isForwards = (direction == Direction::forwards);
236 const auto currentClamped = jlimit (0, numCharacters, currentPosition);
238 switch (boundary)
240 case BoundaryType::character:
242 const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0
243 : (isForwards ? 1 : -1);
244 return jlimit (0, numCharacters, currentPosition + offset);
247 case BoundaryType::word:
249 const auto str = textInterface.getText ({ 0, numCharacters });
250 return currentClamped + findNextWordEndOffset (str.begin(),
251 str.end(),
252 str.begin() + currentClamped,
253 direction,
254 includeBoundary,
255 includeWhitespace);
258 case BoundaryType::line:
260 const auto str = textInterface.getText ({ 0, numCharacters });
261 return currentClamped + findNextLineOffset (str.begin(),
262 str.end(),
263 str.begin() + currentClamped,
264 direction,
265 includeBoundary);
268 case BoundaryType::document:
269 return isForwards ? numCharacters : 0;
272 jassertfalse;
273 return -1;
276 /* Adjusts the current text selection range, using an algorithm appropriate for cursor movement
277 on Android.
279 static Range<int> findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface,
280 BoundaryType boundaryType,
281 ExtendSelection extend,
282 Direction direction)
284 const auto oldPos = textInterface.getTextInsertionOffset();
285 const auto cursorPos = findTextBoundary (textInterface,
286 oldPos,
287 boundaryType,
288 direction,
289 IncludeThisBoundary::no,
290 IncludeWhitespaceAfterWords::no);
292 if (extend == ExtendSelection::no)
293 return { cursorPos, cursorPos };
295 const auto currentSelection = textInterface.getSelection();
296 const auto start = currentSelection.getStart();
297 const auto end = currentSelection.getEnd();
298 return Range<int>::between (cursorPos, oldPos == start ? end : start);
302 } // namespace juce