1 | /* |
2 | * Copyright (C) 2006-2017 Apple Inc. All rights reserved. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Library General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Library General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Library General Public License |
15 | * along with this library; see the file COPYING.LIB. If not, write to |
16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | * Boston, MA 02110-1301, USA. |
18 | * |
19 | */ |
20 | |
21 | #pragma once |
22 | |
23 | #include "Node.h" |
24 | |
25 | #include <wtf/Forward.h> |
26 | #include <wtf/OptionSet.h> |
27 | #include <wtf/Variant.h> |
28 | #include <wtf/text/WTFString.h> |
29 | |
30 | #if PLATFORM(IOS_FAMILY) |
31 | #import <wtf/RetainPtr.h> |
32 | typedef struct objc_object *id; |
33 | #endif |
34 | |
35 | namespace WebCore { |
36 | |
37 | // A range of a node within a document that is "marked", such as the range of a misspelled word. |
38 | // It optionally includes a description that could be displayed in the user interface. |
39 | // It also optionally includes a flag specifying whether the match is active, which is ignored |
40 | // for all types other than type TextMatch. |
41 | class DocumentMarker { |
42 | public: |
43 | enum MarkerType { |
44 | Spelling = 1 << 0, |
45 | Grammar = 1 << 1, |
46 | TextMatch = 1 << 2, |
47 | // Text has been modified by spell correction, reversion of spell correction or other type of substitution. |
48 | // On some platforms, this prevents the text from being autocorrected again. On post Snow Leopard Mac OS X, |
49 | // if a Replacement marker contains non-empty description, a reversion UI will be shown. |
50 | Replacement = 1 << 3, |
51 | // Renderer needs to add underline indicating that the text has been modified by spell |
52 | // correction. Text with Replacement marker doesn't necessarily has CorrectionIndicator |
53 | // marker. For instance, after some text has been corrected, it will have both Replacement |
54 | // and CorrectionIndicator. However, if user further modifies such text, we would remove |
55 | // CorrectionIndicator marker, but retain Replacement marker. |
56 | CorrectionIndicator = 1 << 4, |
57 | // Correction suggestion has been offered, but got rejected by user. |
58 | RejectedCorrection = 1 << 5, |
59 | // Text has been modified by autocorrection. The description of this marker is the original text before autocorrection. |
60 | Autocorrected = 1 << 6, |
61 | // On some platforms, this prevents the text from being spellchecked again. |
62 | SpellCheckingExemption = 1 << 7, |
63 | // This marker indicates user has deleted an autocorrection starting at the end of the |
64 | // range that bears this marker. In some platforms, if the user later inserts the same original |
65 | // word again at this position, it will not be autocorrected again. The description of this |
66 | // marker is the original word before autocorrection was applied. |
67 | DeletedAutocorrection = 1 << 8, |
68 | // This marker indicates that the range of text spanned by the marker is entered by voice dictation, |
69 | // and it has alternative text. |
70 | DictationAlternatives = 1 << 9, |
71 | #if ENABLE(TELEPHONE_NUMBER_DETECTION) |
72 | TelephoneNumber = 1 << 10, |
73 | #endif |
74 | #if PLATFORM(IOS_FAMILY) |
75 | // FIXME: iOS should share the same dictation mark system with the other platforms <rdar://problem/9431249>. |
76 | DictationPhraseWithAlternatives = 1 << 11, |
77 | DictationResult = 1 << 12, |
78 | #endif |
79 | // This marker indicates that the user has selected a text candidate. |
80 | AcceptedCandidate = 1 << 13, |
81 | // This marker indicates that the user has initiated a drag with this content. |
82 | DraggedContent = 1 << 14, |
83 | #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING) |
84 | // This marker maintains state for the platform text checker. |
85 | PlatformTextChecking = 1 << 15, |
86 | #endif |
87 | }; |
88 | |
89 | static constexpr OptionSet<MarkerType> allMarkers(); |
90 | |
91 | using IsActiveMatchData = bool; |
92 | using DescriptionData = String; |
93 | struct DictationData { |
94 | uint64_t context; |
95 | String originalText; |
96 | }; |
97 | struct DictationAlternativesData { |
98 | #if PLATFORM(IOS_FAMILY) |
99 | Vector<String> alternatives; |
100 | RetainPtr<id> metadata; |
101 | #endif |
102 | }; |
103 | struct DraggedContentData { |
104 | RefPtr<Node> targetNode; |
105 | }; |
106 | #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING) |
107 | struct PlatformTextCheckingData { |
108 | String key; |
109 | String value; |
110 | }; |
111 | #endif |
112 | using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData, DraggedContentData |
113 | #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING) |
114 | , PlatformTextCheckingData |
115 | #endif |
116 | >; |
117 | |
118 | DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch); |
119 | DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, const String& description = String()); |
120 | DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, Data&&); |
121 | #if PLATFORM(IOS_FAMILY) |
122 | DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, const String& description, const Vector<String>& alternatives, RetainPtr<id> metadata); |
123 | #endif |
124 | |
125 | MarkerType type() const { return m_type; } |
126 | unsigned startOffset() const { return m_startOffset; } |
127 | unsigned endOffset() const { return m_endOffset; } |
128 | |
129 | const String& description() const; |
130 | |
131 | bool isActiveMatch() const; |
132 | void setActiveMatch(bool); |
133 | |
134 | const Data& data() const { return m_data; } |
135 | void clearData() { m_data = false; } |
136 | |
137 | // Offset modifications are done by DocumentMarkerController. |
138 | // Other classes should not call following setters. |
139 | void setStartOffset(unsigned offset) { m_startOffset = offset; } |
140 | void setEndOffset(unsigned offset) { m_endOffset = offset; } |
141 | void shiftOffsets(int delta); |
142 | |
143 | #if PLATFORM(IOS_FAMILY) |
144 | bool isDictation() const; |
145 | const Vector<String>& alternatives() const; |
146 | void setAlternative(const String&, size_t index); |
147 | id metadata() const; |
148 | void setMetadata(id); |
149 | #endif |
150 | |
151 | private: |
152 | MarkerType m_type; |
153 | unsigned m_startOffset; |
154 | unsigned m_endOffset; |
155 | Data m_data; |
156 | }; |
157 | |
158 | constexpr auto DocumentMarker::allMarkers() -> OptionSet<MarkerType> |
159 | { |
160 | return { |
161 | AcceptedCandidate, |
162 | Autocorrected, |
163 | CorrectionIndicator, |
164 | DeletedAutocorrection, |
165 | DictationAlternatives, |
166 | DraggedContent, |
167 | Grammar, |
168 | RejectedCorrection, |
169 | Replacement, |
170 | SpellCheckingExemption, |
171 | Spelling, |
172 | TextMatch, |
173 | #if ENABLE(TELEPHONE_NUMBER_DETECTION) |
174 | TelephoneNumber, |
175 | #endif |
176 | #if PLATFORM(IOS_FAMILY) |
177 | DictationPhraseWithAlternatives, |
178 | DictationResult, |
179 | #endif |
180 | #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING) |
181 | PlatformTextChecking |
182 | #endif |
183 | }; |
184 | } |
185 | |
186 | inline DocumentMarker::DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch) |
187 | : m_type(TextMatch) |
188 | , m_startOffset(startOffset) |
189 | , m_endOffset(endOffset) |
190 | , m_data(isActiveMatch) |
191 | { |
192 | } |
193 | |
194 | inline DocumentMarker::DocumentMarker(MarkerType type, unsigned startOffset, unsigned endOffset, const String& description) |
195 | : m_type(type) |
196 | , m_startOffset(startOffset) |
197 | , m_endOffset(endOffset) |
198 | , m_data(description) |
199 | { |
200 | } |
201 | |
202 | inline DocumentMarker::DocumentMarker(MarkerType type, unsigned startOffset, unsigned endOffset, Data&& data) |
203 | : m_type(type) |
204 | , m_startOffset(startOffset) |
205 | , m_endOffset(endOffset) |
206 | , m_data(WTFMove(data)) |
207 | { |
208 | } |
209 | |
210 | inline void DocumentMarker::shiftOffsets(int delta) |
211 | { |
212 | m_startOffset += delta; |
213 | m_endOffset += delta; |
214 | } |
215 | |
216 | inline const String& DocumentMarker::description() const |
217 | { |
218 | return WTF::holds_alternative<String>(m_data) ? WTF::get<String>(m_data) : emptyString(); |
219 | } |
220 | |
221 | inline bool DocumentMarker::isActiveMatch() const |
222 | { |
223 | return WTF::holds_alternative<bool>(m_data) && WTF::get<bool>(m_data); |
224 | } |
225 | |
226 | inline void DocumentMarker::setActiveMatch(bool isActiveMatch) |
227 | { |
228 | ASSERT(m_type == TextMatch); |
229 | m_data = isActiveMatch; |
230 | } |
231 | |
232 | #if PLATFORM(IOS_FAMILY) |
233 | |
234 | // FIXME: iOS should share the same dictation mark system with the other platforms <rdar://problem/9431249>. |
235 | |
236 | inline DocumentMarker::DocumentMarker(MarkerType type, unsigned startOffset, unsigned endOffset, const String&, const Vector<String>& alternatives, RetainPtr<id> metadata) |
237 | : m_type(type) |
238 | , m_startOffset(startOffset) |
239 | , m_endOffset(endOffset) |
240 | , m_data(DictationAlternativesData { alternatives, metadata }) |
241 | { |
242 | ASSERT(isDictation()); |
243 | } |
244 | |
245 | inline bool DocumentMarker::isDictation() const |
246 | { |
247 | return m_type == DictationPhraseWithAlternatives || m_type == DictationResult; |
248 | } |
249 | |
250 | inline const Vector<String>& DocumentMarker::alternatives() const |
251 | { |
252 | return WTF::get<DictationAlternativesData>(m_data).alternatives; |
253 | } |
254 | |
255 | inline void DocumentMarker::setAlternative(const String& alternative, size_t index) |
256 | { |
257 | WTF::get<DictationAlternativesData>(m_data).alternatives[index] = alternative; |
258 | } |
259 | |
260 | inline id DocumentMarker::metadata() const |
261 | { |
262 | return WTF::get<DictationAlternativesData>(m_data).metadata.get(); |
263 | } |
264 | |
265 | inline void DocumentMarker::setMetadata(id metadata) |
266 | { |
267 | WTF::get<DictationAlternativesData>(m_data).metadata = metadata; |
268 | } |
269 | |
270 | #endif |
271 | |
272 | } // namespace WebCore |
273 | |