blob: 40315add603db5b08f29aa1017939ea56e371e8c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text;
18
Roozbeh Pournadercd217b82015-08-11 16:51:21 -070019import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.graphics.Canvas;
Doug Feltf47d7402010-04-21 16:01:52 -070021import android.graphics.Paint;
Gilles Debunne34ec2b52012-04-10 13:25:33 -070022import android.util.Log;
Gilles Debunne6435a562011-08-04 21:22:30 -070023
24import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050025import com.android.internal.util.GrowingArrayUtils;
26
27import libcore.util.EmptyArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
29import java.lang.reflect.Array;
Raph Leviend7c020e2015-02-04 20:09:03 -080030import java.util.IdentityHashMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
32/**
33 * This is the class for text whose content and markup can both be changed.
34 */
Gilles Debunne6435a562011-08-04 21:22:30 -070035public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
36 Appendable, GraphicsOperations {
Jean Chalard84a33202014-02-13 18:24:36 +090037 private final static String TAG = "SpannableStringBuilder";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038 /**
39 * Create a new SpannableStringBuilder with empty contents
40 */
41 public SpannableStringBuilder() {
42 this("");
43 }
44
45 /**
46 * Create a new SpannableStringBuilder containing a copy of the
47 * specified text, including its spans if any.
48 */
49 public SpannableStringBuilder(CharSequence text) {
50 this(text, 0, text.length());
51 }
52
53 /**
54 * Create a new SpannableStringBuilder containing a copy of the
55 * specified slice of the specified text, including its spans if any.
56 */
57 public SpannableStringBuilder(CharSequence text, int start, int end) {
58 int srclen = end - start;
59
Gilles Debunne0249b432012-04-09 16:02:31 -070060 if (srclen < 0) throw new StringIndexOutOfBoundsException();
61
Adam Lesinski776abc22014-03-07 11:30:59 -050062 mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 mGapStart = srclen;
Adam Lesinski776abc22014-03-07 11:30:59 -050064 mGapLength = mText.length - srclen;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065
66 TextUtils.getChars(text, start, end, mText, 0);
67
68 mSpanCount = 0;
Adam Lesinski776abc22014-03-07 11:30:59 -050069 mSpans = EmptyArray.OBJECT;
70 mSpanStarts = EmptyArray.INT;
71 mSpanEnds = EmptyArray.INT;
72 mSpanFlags = EmptyArray.INT;
Raph Leviend7c020e2015-02-04 20:09:03 -080073 mSpanMax = EmptyArray.INT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074
75 if (text instanceof Spanned) {
76 Spanned sp = (Spanned) text;
77 Object[] spans = sp.getSpans(start, end, Object.class);
78
79 for (int i = 0; i < spans.length; i++) {
80 if (spans[i] instanceof NoCopySpan) {
81 continue;
82 }
Gilles Debunne174c44c2012-04-10 16:01:09 -070083
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 int st = sp.getSpanStart(spans[i]) - start;
85 int en = sp.getSpanEnd(spans[i]) - start;
86 int fl = sp.getSpanFlags(spans[i]);
87
88 if (st < 0)
89 st = 0;
90 if (st > end - start)
91 st = end - start;
92
93 if (en < 0)
94 en = 0;
95 if (en > end - start)
96 en = end - start;
97
Gilles Debunne0249b432012-04-09 16:02:31 -070098 setSpan(false, spans[i], st, en, fl);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800100 restoreInvariants();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 }
102 }
103
104 public static SpannableStringBuilder valueOf(CharSequence source) {
105 if (source instanceof SpannableStringBuilder) {
106 return (SpannableStringBuilder) source;
107 } else {
108 return new SpannableStringBuilder(source);
109 }
110 }
111
112 /**
113 * Return the char at the specified offset within the buffer.
114 */
115 public char charAt(int where) {
116 int len = length();
117 if (where < 0) {
118 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
119 } else if (where >= len) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700120 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 }
122
123 if (where >= mGapStart)
124 return mText[where + mGapLength];
125 else
126 return mText[where];
127 }
128
129 /**
130 * Return the number of chars in the buffer.
131 */
132 public int length() {
133 return mText.length - mGapLength;
134 }
135
136 private void resizeFor(int size) {
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700137 final int oldLength = mText.length;
Adam Lesinski776abc22014-03-07 11:30:59 -0500138 if (size + 1 <= oldLength) {
139 return;
140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141
Adam Lesinski776abc22014-03-07 11:30:59 -0500142 char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700143 System.arraycopy(mText, 0, newText, 0, mGapStart);
Adam Lesinski776abc22014-03-07 11:30:59 -0500144 final int newLength = newText.length;
145 final int delta = newLength - oldLength;
Gilles Debunne90985282012-04-17 17:08:04 -0700146 final int after = oldLength - (mGapStart + mGapLength);
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700147 System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
148 mText = newText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700150 mGapLength += delta;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 if (mGapLength < 1)
152 new Exception("mGapLength < 1").printStackTrace();
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700153
Raph Leviend7c020e2015-02-04 20:09:03 -0800154 if (mSpanCount != 0) {
155 for (int i = 0; i < mSpanCount; i++) {
156 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
157 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
158 }
159 calcMax(treeRoot());
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700160 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 }
162
163 private void moveGapTo(int where) {
164 if (where == mGapStart)
165 return;
166
Gilles Debunne0249b432012-04-09 16:02:31 -0700167 boolean atEnd = (where == length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168
169 if (where < mGapStart) {
170 int overlap = mGapStart - where;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700171 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 } else /* where > mGapStart */ {
173 int overlap = where - mGapStart;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700174 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 }
176
Raph Leviend7c020e2015-02-04 20:09:03 -0800177 // TODO: be more clever (although the win really isn't that big)
178 if (mSpanCount != 0) {
179 for (int i = 0; i < mSpanCount; i++) {
180 int start = mSpanStarts[i];
181 int end = mSpanEnds[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182
Raph Leviend7c020e2015-02-04 20:09:03 -0800183 if (start > mGapStart)
184 start -= mGapLength;
185 if (start > where)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 start += mGapLength;
Raph Leviend7c020e2015-02-04 20:09:03 -0800187 else if (start == where) {
188 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189
Raph Leviend7c020e2015-02-04 20:09:03 -0800190 if (flag == POINT || (atEnd && flag == PARAGRAPH))
191 start += mGapLength;
192 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193
Raph Leviend7c020e2015-02-04 20:09:03 -0800194 if (end > mGapStart)
195 end -= mGapLength;
196 if (end > where)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 end += mGapLength;
Raph Leviend7c020e2015-02-04 20:09:03 -0800198 else if (end == where) {
199 int flag = (mSpanFlags[i] & END_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200
Raph Leviend7c020e2015-02-04 20:09:03 -0800201 if (flag == POINT || (atEnd && flag == PARAGRAPH))
202 end += mGapLength;
203 }
204
205 mSpanStarts[i] = start;
206 mSpanEnds[i] = end;
207 }
208 calcMax(treeRoot());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 }
210
211 mGapStart = where;
212 }
213
214 // Documentation from interface
215 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
216 return replace(where, where, tb, start, end);
217 }
218
219 // Documentation from interface
220 public SpannableStringBuilder insert(int where, CharSequence tb) {
221 return replace(where, where, tb, 0, tb.length());
222 }
223
224 // Documentation from interface
225 public SpannableStringBuilder delete(int start, int end) {
226 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
227
228 if (mGapLength > 2 * length())
229 resizeFor(length());
Gilles Debunne174c44c2012-04-10 16:01:09 -0700230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 return ret; // == this
232 }
233
234 // Documentation from interface
235 public void clear() {
236 replace(0, length(), "", 0, 0);
237 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700238
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 // Documentation from interface
240 public void clearSpans() {
241 for (int i = mSpanCount - 1; i >= 0; i--) {
242 Object what = mSpans[i];
243 int ostart = mSpanStarts[i];
244 int oend = mSpanEnds[i];
245
246 if (ostart > mGapStart)
247 ostart -= mGapLength;
248 if (oend > mGapStart)
249 oend -= mGapLength;
250
251 mSpanCount = i;
252 mSpans[i] = null;
253
254 sendSpanRemoved(what, ostart, oend);
255 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800256 if (mIndexOfSpan != null) {
257 mIndexOfSpan.clear();
258 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 }
260
261 // Documentation from interface
262 public SpannableStringBuilder append(CharSequence text) {
263 int length = length();
264 return replace(length, length, text, 0, text.length());
265 }
266
Niels Egbertsca7b0272014-07-10 14:16:04 +0100267 /**
268 * Appends the character sequence {@code text} and spans {@code what} over the appended part.
269 * See {@link Spanned} for an explanation of what the flags mean.
270 * @param text the character sequence to append.
271 * @param what the object to be spanned over the appended text.
272 * @param flags see {@link Spanned}.
273 * @return this {@code SpannableStringBuilder}.
274 */
275 public SpannableStringBuilder append(CharSequence text, Object what, int flags) {
276 int start = length();
277 append(text);
278 setSpan(what, start, length(), flags);
279 return this;
280 }
281
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 // Documentation from interface
283 public SpannableStringBuilder append(CharSequence text, int start, int end) {
284 int length = length();
285 return replace(length, length, text, start, end);
286 }
287
288 // Documentation from interface
289 public SpannableStringBuilder append(char text) {
290 return append(String.valueOf(text));
291 }
292
Raph Leviend7c020e2015-02-04 20:09:03 -0800293 // Returns true if a node was removed (so we can restart search from root)
294 private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) {
295 if ((i & 1) != 0) {
296 // internal tree node
297 if (resolveGap(mSpanMax[i]) >= start &&
298 removeSpansForChange(start, end, textIsRemoved, leftChild(i))) {
299 return true;
300 }
301 }
302 if (i < mSpanCount) {
303 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
304 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
305 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
306 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
307 // The following condition indicates that the span would become empty
308 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
309 mIndexOfSpan.remove(mSpans[i]);
310 removeSpan(i);
311 return true;
312 }
313 return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
314 removeSpansForChange(start, end, textIsRemoved, rightChild(i));
315 }
316 return false;
317 }
318
Gilles Debunne174c44c2012-04-10 16:01:09 -0700319 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
320 // Can be negative
Gilles Debunne26b62d42012-04-26 18:46:19 -0700321 final int replacedLength = end - start;
322 final int replacementLength = csEnd - csStart;
323 final int nbNewChars = replacementLength - replacedLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324
Raph Leviend7c020e2015-02-04 20:09:03 -0800325 boolean changed = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 for (int i = mSpanCount - 1; i >= 0; i--) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700327 int spanStart = mSpanStarts[i];
328 if (spanStart > mGapStart)
329 spanStart -= mGapLength;
330
331 int spanEnd = mSpanEnds[i];
332 if (spanEnd > mGapStart)
333 spanEnd -= mGapLength;
334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700336 int ost = spanStart;
337 int oen = spanEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800338 int clen = length();
339
Gilles Debunne174c44c2012-04-10 16:01:09 -0700340 if (spanStart > start && spanStart <= end) {
341 for (spanStart = end; spanStart < clen; spanStart++)
342 if (spanStart > end && charAt(spanStart - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 break;
344 }
345
Gilles Debunne174c44c2012-04-10 16:01:09 -0700346 if (spanEnd > start && spanEnd <= end) {
347 for (spanEnd = end; spanEnd < clen; spanEnd++)
348 if (spanEnd > end && charAt(spanEnd - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 break;
350 }
351
Raph Leviend7c020e2015-02-04 20:09:03 -0800352 if (spanStart != ost || spanEnd != oen) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700353 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
Raph Leviend7c020e2015-02-04 20:09:03 -0800354 changed = true;
355 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700357
358 int flags = 0;
359 if (spanStart == start) flags |= SPAN_START_AT_START;
360 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
361 if (spanEnd == start) flags |= SPAN_END_AT_START;
362 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
363 mSpanFlags[i] |= flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800365 if (changed) {
366 restoreInvariants();
367 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368
369 moveGapTo(end);
370
Gilles Debunne312cd582010-07-02 17:02:34 -0700371 if (nbNewChars >= mGapLength) {
372 resizeFor(mText.length + nbNewChars - mGapLength);
373 }
374
Gilles Debunnee2448682012-05-04 15:29:09 -0700375 final boolean textIsRemoved = replacementLength == 0;
Gilles Debunne90985282012-04-17 17:08:04 -0700376 // The removal pass needs to be done before the gap is updated in order to broadcast the
377 // correct previous positions to the correct intersecting SpanWatchers
Gilles Debunne26b62d42012-04-26 18:46:19 -0700378 if (replacedLength > 0) { // no need for span fixup on pure insertion
Raph Leviend7c020e2015-02-04 20:09:03 -0800379 while (mSpanCount > 0 &&
380 removeSpansForChange(start, end, textIsRemoved, treeRoot())) {
381 // keep deleting spans as needed, and restart from root after every deletion
382 // because deletion can invalidate an index.
Gilles Debunne90985282012-04-17 17:08:04 -0700383 }
384 }
385
Gilles Debunne312cd582010-07-02 17:02:34 -0700386 mGapStart += nbNewChars;
387 mGapLength -= nbNewChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388
389 if (mGapLength < 1)
390 new Exception("mGapLength < 1").printStackTrace();
391
Gilles Debunne174c44c2012-04-10 16:01:09 -0700392 TextUtils.getChars(cs, csStart, csEnd, mText, start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393
Gilles Debunne26b62d42012-04-26 18:46:19 -0700394 if (replacedLength > 0) { // no need for span fixup on pure insertion
Raph Leviend7c020e2015-02-04 20:09:03 -0800395 // TODO potential optimization: only update bounds on intersecting spans
Gilles Debunne90985282012-04-17 17:08:04 -0700396 final boolean atEnd = (mGapStart + mGapLength == mText.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397
Gilles Debunne90985282012-04-17 17:08:04 -0700398 for (int i = 0; i < mSpanCount; i++) {
Gilles Debunne26b62d42012-04-26 18:46:19 -0700399 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
400 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
401 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402
Gilles Debunne26b62d42012-04-26 18:46:19 -0700403 final int endFlag = (mSpanFlags[i] & END_MASK);
404 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
405 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800407 // TODO potential optimization: only fix up invariants when bounds actually changed
408 restoreInvariants();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 }
Gilles Debunne0249b432012-04-09 16:02:31 -0700410
Gilles Debunne174c44c2012-04-10 16:01:09 -0700411 if (cs instanceof Spanned) {
412 Spanned sp = (Spanned) cs;
413 Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
Gilles Debunne0249b432012-04-09 16:02:31 -0700414
415 for (int i = 0; i < spans.length; i++) {
416 int st = sp.getSpanStart(spans[i]);
417 int en = sp.getSpanEnd(spans[i]);
418
Gilles Debunne174c44c2012-04-10 16:01:09 -0700419 if (st < csStart) st = csStart;
420 if (en > csEnd) en = csEnd;
Gilles Debunne0249b432012-04-09 16:02:31 -0700421
422 // Add span only if this object is not yet used as a span in this string
Gilles Debunne90985282012-04-17 17:08:04 -0700423 if (getSpanStart(spans[i]) < 0) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700424 setSpan(false, spans[i], st - csStart + start, en - csStart + start,
Raph Leviend7c020e2015-02-04 20:09:03 -0800425 sp.getSpanFlags(spans[i]) | SPAN_ADDED);
Gilles Debunne0249b432012-04-09 16:02:31 -0700426 }
427 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800428 restoreInvariants();
Gilles Debunne0249b432012-04-09 16:02:31 -0700429 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 }
431
Gilles Debunne26b62d42012-04-26 18:46:19 -0700432 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
433 boolean textIsRemoved) {
434 if (offset >= start && offset < mGapStart + mGapLength) {
435 if (flag == POINT) {
436 // A POINT located inside the replaced range should be moved to the end of the
437 // replaced text.
438 // The exception is when the point is at the start of the range and we are doing a
439 // text replacement (as opposed to a deletion): the point stays there.
440 if (textIsRemoved || offset > start) {
441 return mGapStart + mGapLength;
442 }
443 } else {
444 if (flag == PARAGRAPH) {
445 if (atEnd) {
446 return mGapStart + mGapLength;
447 }
448 } else { // MARK
Gilles Debunnee2448682012-05-04 15:29:09 -0700449 // MARKs should be moved to the start, with the exception of a mark located at
450 // the end of the range (which will be < mGapStart + mGapLength since mGapLength
451 // is > 0, which should stay 'unchanged' at the end of the replaced text.
Gilles Debunne26b62d42012-04-26 18:46:19 -0700452 if (textIsRemoved || offset < mGapStart - nbNewChars) {
453 return start;
454 } else {
455 // Move to the end of replaced text (needed if nbNewChars != 0)
456 return mGapStart;
457 }
458 }
459 }
460 }
461 return offset;
462 }
463
Raph Leviend7c020e2015-02-04 20:09:03 -0800464 // Note: caller is responsible for removing the mIndexOfSpan entry.
Gilles Debunne75beb332011-04-29 11:40:22 -0700465 private void removeSpan(int i) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700466 Object object = mSpans[i];
467
468 int start = mSpanStarts[i];
469 int end = mSpanEnds[i];
470
471 if (start > mGapStart) start -= mGapLength;
472 if (end > mGapStart) end -= mGapLength;
473
474 int count = mSpanCount - (i + 1);
475 System.arraycopy(mSpans, i + 1, mSpans, i, count);
476 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
477 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
478 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
Gilles Debunne75beb332011-04-29 11:40:22 -0700479
480 mSpanCount--;
Gilles Debunne6435a562011-08-04 21:22:30 -0700481
Raph Leviend7c020e2015-02-04 20:09:03 -0800482 invalidateIndex(i);
Gilles Debunne6435a562011-08-04 21:22:30 -0700483 mSpans[mSpanCount] = null;
484
Raph Leviend7c020e2015-02-04 20:09:03 -0800485 // Invariants must be restored before sending span removed notifications.
486 restoreInvariants();
487
Gilles Debunne6435a562011-08-04 21:22:30 -0700488 sendSpanRemoved(object, start, end);
Gilles Debunne75beb332011-04-29 11:40:22 -0700489 }
490
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 // Documentation from interface
492 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
493 return replace(start, end, tb, 0, tb.length());
494 }
495
496 // Documentation from interface
Jean Chalard0a993102014-06-11 18:03:05 +0900497 public SpannableStringBuilder replace(final int start, final int end,
Gilles Debunne0249b432012-04-09 16:02:31 -0700498 CharSequence tb, int tbstart, int tbend) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700499 checkRange("replace", start, end);
500
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 int filtercount = mFilters.length;
502 for (int i = 0; i < filtercount; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700503 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504
505 if (repl != null) {
506 tb = repl;
507 tbstart = 0;
508 tbend = repl.length();
509 }
510 }
511
Gilles Debunneb51036f2012-04-02 11:27:50 -0700512 final int origLen = end - start;
513 final int newLen = tbend - tbstart;
514
Gilles Debunned60da052012-04-18 15:12:20 -0700515 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
516 // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
517 // Early exit so that the text watchers do not get notified
518 return this;
519 }
520
Gilles Debunneb51036f2012-04-02 11:27:50 -0700521 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
522 sendBeforeTextChanged(textWatchers, start, origLen, newLen);
523
Gilles Debunne0249b432012-04-09 16:02:31 -0700524 // Try to keep the cursor / selection at the same relative position during
525 // a text replacement. If replaced or replacement text length is zero, this
526 // is already taken care of.
527 boolean adjustSelection = origLen != 0 && newLen != 0;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700528 int selectionStart = 0;
529 int selectionEnd = 0;
Gilles Debunne0249b432012-04-09 16:02:31 -0700530 if (adjustSelection) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700531 selectionStart = Selection.getSelectionStart(this);
532 selectionEnd = Selection.getSelectionEnd(this);
Gilles Debunne0249b432012-04-09 16:02:31 -0700533 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534
Gilles Debunne0249b432012-04-09 16:02:31 -0700535 change(start, end, tb, tbstart, tbend);
Gilles Debunne6435a562011-08-04 21:22:30 -0700536
Gilles Debunne0249b432012-04-09 16:02:31 -0700537 if (adjustSelection) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800538 boolean changed = false;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700539 if (selectionStart > start && selectionStart < end) {
540 final int offset = (selectionStart - start) * newLen / origLen;
541 selectionStart = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542
Raph Leviend7c020e2015-02-04 20:09:03 -0800543 changed = true;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700544 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 Spanned.SPAN_POINT_POINT);
546 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700547 if (selectionEnd > start && selectionEnd < end) {
548 final int offset = (selectionEnd - start) * newLen / origLen;
549 selectionEnd = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550
Raph Leviend7c020e2015-02-04 20:09:03 -0800551 changed = true;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700552 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
553 Spanned.SPAN_POINT_POINT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800555 if (changed) {
556 restoreInvariants();
557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700559
Gilles Debunneb51036f2012-04-02 11:27:50 -0700560 sendTextChanged(textWatchers, start, origLen, newLen);
561 sendAfterTextChanged(textWatchers);
562
Gilles Debunne174c44c2012-04-10 16:01:09 -0700563 // Span watchers need to be called after text watchers, which may update the layout
564 sendToSpanWatchers(start, end, newLen - origLen);
565
Raph Levien051910b2014-06-15 18:25:29 -0700566 return this;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 }
568
Gilles Debunned60da052012-04-18 15:12:20 -0700569 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
570 if (text instanceof Spanned) {
571 Spanned spanned = (Spanned) text;
572 Object[] spans = spanned.getSpans(offset, offset, Object.class);
573 final int length = spans.length;
574 for (int i = 0; i < length; i++) {
575 Object span = spans[i];
576 int flags = spanned.getSpanFlags(span);
577 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
578 }
579 }
580 return false;
581 }
582
Gilles Debunne174c44c2012-04-10 16:01:09 -0700583 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800584 for (int i = 0; i < mSpanCount; i++) {
585 int spanFlags = mSpanFlags[i];
586
587 // This loop handles only modified (not added) spans.
588 if ((spanFlags & SPAN_ADDED) != 0) continue;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700589 int spanStart = mSpanStarts[i];
590 int spanEnd = mSpanEnds[i];
591 if (spanStart > mGapStart) spanStart -= mGapLength;
592 if (spanEnd > mGapStart) spanEnd -= mGapLength;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700593
594 int newReplaceEnd = replaceEnd + nbNewChars;
595 boolean spanChanged = false;
Gilles Debunne90985282012-04-17 17:08:04 -0700596
Gilles Debunne174c44c2012-04-10 16:01:09 -0700597 int previousSpanStart = spanStart;
598 if (spanStart > newReplaceEnd) {
599 if (nbNewChars != 0) {
600 previousSpanStart -= nbNewChars;
601 spanChanged = true;
602 }
603 } else if (spanStart >= replaceStart) {
604 // No change if span start was already at replace interval boundaries before replace
605 if ((spanStart != replaceStart ||
606 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
607 (spanStart != newReplaceEnd ||
608 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
Gilles Debunne90985282012-04-17 17:08:04 -0700609 // TODO A correct previousSpanStart cannot be computed at this point.
610 // It would require to save all the previous spans' positions before the replace
611 // Using an invalid -1 value to convey this would break the broacast range
Gilles Debunne174c44c2012-04-10 16:01:09 -0700612 spanChanged = true;
613 }
614 }
Gilles Debunne90985282012-04-17 17:08:04 -0700615
Gilles Debunne174c44c2012-04-10 16:01:09 -0700616 int previousSpanEnd = spanEnd;
617 if (spanEnd > newReplaceEnd) {
618 if (nbNewChars != 0) {
619 previousSpanEnd -= nbNewChars;
620 spanChanged = true;
621 }
622 } else if (spanEnd >= replaceStart) {
623 // No change if span start was already at replace interval boundaries before replace
624 if ((spanEnd != replaceStart ||
625 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
626 (spanEnd != newReplaceEnd ||
627 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
628 // TODO same as above for previousSpanEnd
629 spanChanged = true;
630 }
631 }
632
633 if (spanChanged) {
634 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
635 }
636 mSpanFlags[i] &= ~SPAN_START_END_MASK;
637 }
638
Raph Leviend7c020e2015-02-04 20:09:03 -0800639 // Handle added spans
640 for (int i = 0; i < mSpanCount; i++) {
641 int spanFlags = mSpanFlags[i];
642 if ((spanFlags & SPAN_ADDED) != 0) {
643 mSpanFlags[i] &= ~SPAN_ADDED;
644 int spanStart = mSpanStarts[i];
645 int spanEnd = mSpanEnds[i];
646 if (spanStart > mGapStart) spanStart -= mGapLength;
647 if (spanEnd > mGapStart) spanEnd -= mGapLength;
648 sendSpanAdded(mSpans[i], spanStart, spanEnd);
649 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700650 }
651 }
652
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653 /**
654 * Mark the specified range of text with the specified object.
655 * The flags determine how the span will behave when text is
656 * inserted at the start or end of the span's range.
657 */
658 public void setSpan(Object what, int start, int end, int flags) {
659 setSpan(true, what, start, end, flags);
660 }
661
Raph Leviend7c020e2015-02-04 20:09:03 -0800662 // Note: if send is false, then it is the caller's responsibility to restore
663 // invariants. If send is false and the span already exists, then this method
664 // will not change the index of any spans.
Gilles Debunne6435a562011-08-04 21:22:30 -0700665 private void setSpan(boolean send, Object what, int start, int end, int flags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 checkRange("setSpan", start, end);
667
Gilles Debunne0249b432012-04-09 16:02:31 -0700668 int flagsStart = (flags & START_MASK) >> START_SHIFT;
669 if (flagsStart == PARAGRAPH) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 if (start != 0 && start != length()) {
671 char c = charAt(start - 1);
672
673 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700674 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 }
676 }
677
Gilles Debunne0249b432012-04-09 16:02:31 -0700678 int flagsEnd = flags & END_MASK;
679 if (flagsEnd == PARAGRAPH) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680 if (end != 0 && end != length()) {
681 char c = charAt(end - 1);
682
683 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700684 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685 }
686 }
687
Gilles Debunne0249b432012-04-09 16:02:31 -0700688 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
689 if (flagsStart == POINT && flagsEnd == MARK && start == end) {
Jean Chalard84a33202014-02-13 18:24:36 +0900690 if (send) {
691 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
692 }
Gilles Debunne34ec2b52012-04-10 13:25:33 -0700693 // Silently ignore invalid spans when they are created from this class.
694 // This avoids the duplication of the above test code before all the
695 // calls to setSpan that are done in this class
696 return;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700697 }
698
Gilles Debunne0249b432012-04-09 16:02:31 -0700699 int nstart = start;
700 int nend = end;
701
Gilles Debunne6435a562011-08-04 21:22:30 -0700702 if (start > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 start += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700704 } else if (start == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700705 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800706 start += mGapLength;
707 }
708
Gilles Debunne6435a562011-08-04 21:22:30 -0700709 if (end > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 end += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700711 } else if (end == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700712 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 end += mGapLength;
714 }
715
716 int count = mSpanCount;
717 Object[] spans = mSpans;
718
Raph Leviend7c020e2015-02-04 20:09:03 -0800719 if (mIndexOfSpan != null) {
720 Integer index = mIndexOfSpan.get(what);
721 if (index != null) {
722 int i = index;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 int ostart = mSpanStarts[i];
724 int oend = mSpanEnds[i];
725
726 if (ostart > mGapStart)
727 ostart -= mGapLength;
728 if (oend > mGapStart)
729 oend -= mGapLength;
730
731 mSpanStarts[i] = start;
732 mSpanEnds[i] = end;
733 mSpanFlags[i] = flags;
734
Raph Leviend7c020e2015-02-04 20:09:03 -0800735 if (send) {
736 restoreInvariants();
737 sendSpanChanged(what, ostart, oend, nstart, nend);
738 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739
740 return;
741 }
742 }
743
Adam Lesinski776abc22014-03-07 11:30:59 -0500744 mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
745 mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
746 mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
747 mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
Raph Leviend7c020e2015-02-04 20:09:03 -0800748 invalidateIndex(mSpanCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749 mSpanCount++;
Raph Leviend7c020e2015-02-04 20:09:03 -0800750 // Make sure there is enough room for empty interior nodes.
751 // This magic formula computes the size of the smallest perfect binary
752 // tree no smaller than mSpanCount.
753 int sizeOfMax = 2 * treeRoot() + 1;
754 if (mSpanMax.length < sizeOfMax) {
755 mSpanMax = new int[sizeOfMax];
756 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757
Raph Leviend7c020e2015-02-04 20:09:03 -0800758 if (send) {
759 restoreInvariants();
760 sendSpanAdded(what, nstart, nend);
761 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762 }
763
764 /**
765 * Remove the specified markup object from the buffer.
766 */
767 public void removeSpan(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800768 if (mIndexOfSpan == null) return;
769 Integer i = mIndexOfSpan.remove(what);
770 if (i != null) {
771 removeSpan(i.intValue());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800772 }
773 }
774
775 /**
Raph Leviend7c020e2015-02-04 20:09:03 -0800776 * Return externally visible offset given offset into gapped buffer.
777 */
778 private int resolveGap(int i) {
779 return i > mGapStart ? i - mGapLength : i;
780 }
781
782 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 * Return the buffer offset of the beginning of the specified
784 * markup object, or -1 if it is not attached to this buffer.
785 */
786 public int getSpanStart(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800787 if (mIndexOfSpan == null) return -1;
788 Integer i = mIndexOfSpan.get(what);
789 return i == null ? -1 : resolveGap(mSpanStarts[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 }
791
792 /**
793 * Return the buffer offset of the end of the specified
794 * markup object, or -1 if it is not attached to this buffer.
795 */
796 public int getSpanEnd(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800797 if (mIndexOfSpan == null) return -1;
798 Integer i = mIndexOfSpan.get(what);
799 return i == null ? -1 : resolveGap(mSpanEnds[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 }
801
802 /**
803 * Return the flags of the end of the specified
804 * markup object, or 0 if it is not attached to this buffer.
805 */
806 public int getSpanFlags(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800807 if (mIndexOfSpan == null) return 0;
808 Integer i = mIndexOfSpan.get(what);
809 return i == null ? 0 : mSpanFlags[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 }
811
812 /**
813 * Return an array of the spans of the specified type that overlap
814 * the specified range of the buffer. The kind may be Object.class to get
815 * a list of all the spans regardless of type.
816 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700817 @SuppressWarnings("unchecked")
Roozbeh Pournadercd217b82015-08-11 16:51:21 -0700818 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
819 if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
820 if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
Raph Leviend7c020e2015-02-04 20:09:03 -0800821 int count = countSpans(queryStart, queryEnd, kind, treeRoot());
822 if (count == 0) {
823 return ArrayUtils.emptyArray(kind);
824 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700825
Raph Leviend7c020e2015-02-04 20:09:03 -0800826 // Safe conversion, but requires a suppressWarning
827 T[] ret = (T[]) Array.newInstance(kind, count);
828 getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, 0);
829 return ret;
830 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800831
Raph Leviend7c020e2015-02-04 20:09:03 -0800832 private int countSpans(int queryStart, int queryEnd, Class kind, int i) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833 int count = 0;
Raph Leviend7c020e2015-02-04 20:09:03 -0800834 if ((i & 1) != 0) {
835 // internal tree node
836 int left = leftChild(i);
837 int spanMax = mSpanMax[left];
838 if (spanMax > mGapStart) {
839 spanMax -= mGapLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800841 if (spanMax >= queryStart) {
842 count = countSpans(queryStart, queryEnd, kind, left);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800844 }
845 if (i < mSpanCount) {
846 int spanStart = mSpanStarts[i];
847 if (spanStart > mGapStart) {
848 spanStart -= mGapLength;
Gilles Debunneb062e812011-09-27 14:58:37 -0700849 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800850 if (spanStart <= queryEnd) {
851 int spanEnd = mSpanEnds[i];
852 if (spanEnd > mGapStart) {
853 spanEnd -= mGapLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800855 if (spanEnd >= queryStart &&
856 (spanStart == spanEnd || queryStart == queryEnd ||
857 (spanStart != queryEnd && spanEnd != queryStart)) &&
858 kind.isInstance(mSpans[i])) {
859 count++;
860 }
861 if ((i & 1) != 0) {
862 count += countSpans(queryStart, queryEnd, kind, rightChild(i));
863 }
864 }
865 }
866 return count;
867 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868
Raph Leviend7c020e2015-02-04 20:09:03 -0800869 @SuppressWarnings("unchecked")
870 private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
871 int i, T[] ret, int count) {
872 if ((i & 1) != 0) {
873 // internal tree node
874 int left = leftChild(i);
875 int spanMax = mSpanMax[left];
876 if (spanMax > mGapStart) {
877 spanMax -= mGapLength;
878 }
879 if (spanMax >= queryStart) {
880 count = getSpansRec(queryStart, queryEnd, kind, left, ret, count);
881 }
882 }
883 if (i >= mSpanCount) return count;
884 int spanStart = mSpanStarts[i];
885 if (spanStart > mGapStart) {
886 spanStart -= mGapLength;
887 }
888 if (spanStart <= queryEnd) {
889 int spanEnd = mSpanEnds[i];
890 if (spanEnd > mGapStart) {
891 spanEnd -= mGapLength;
892 }
893 if (spanEnd >= queryStart &&
894 (spanStart == spanEnd || queryStart == queryEnd ||
895 (spanStart != queryEnd && spanEnd != queryStart)) &&
896 kind.isInstance(mSpans[i])) {
897 int prio = mSpanFlags[i] & SPAN_PRIORITY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 if (prio != 0) {
899 int j;
900
901 for (j = 0; j < count; j++) {
902 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
903
904 if (prio > p) {
905 break;
906 }
907 }
908
909 System.arraycopy(ret, j, ret, j + 1, count - j);
Gilles Debunne312cd582010-07-02 17:02:34 -0700910 // Safe conversion thanks to the isInstance test above
Raph Leviend7c020e2015-02-04 20:09:03 -0800911 ret[j] = (T) mSpans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 } else {
Gilles Debunne312cd582010-07-02 17:02:34 -0700913 // Safe conversion thanks to the isInstance test above
Raph Leviend7c020e2015-02-04 20:09:03 -0800914 ret[count] = (T) mSpans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800916 count++;
917 }
918 if (count < ret.length && (i & 1) != 0) {
919 count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800920 }
921 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800922 return count;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 }
924
925 /**
926 * Return the next offset after <code>start</code> but less than or
927 * equal to <code>limit</code> where a span of the specified type
928 * begins or ends.
929 */
930 public int nextSpanTransition(int start, int limit, Class kind) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800931 if (mSpanCount == 0) return limit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 if (kind == null) {
933 kind = Object.class;
934 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800935 return nextSpanTransitionRec(start, limit, kind, treeRoot());
936 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937
Raph Leviend7c020e2015-02-04 20:09:03 -0800938 private int nextSpanTransitionRec(int start, int limit, Class kind, int i) {
939 if ((i & 1) != 0) {
940 // internal tree node
941 int left = leftChild(i);
942 if (resolveGap(mSpanMax[left]) > start) {
943 limit = nextSpanTransitionRec(start, limit, kind, left);
944 }
945 }
946 if (i < mSpanCount) {
947 int st = resolveGap(mSpanStarts[i]);
948 int en = resolveGap(mSpanEnds[i]);
949 if (st > start && st < limit && kind.isInstance(mSpans[i]))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800950 limit = st;
Raph Leviend7c020e2015-02-04 20:09:03 -0800951 if (en > start && en < limit && kind.isInstance(mSpans[i]))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 limit = en;
Raph Leviend7c020e2015-02-04 20:09:03 -0800953 if (st < limit && (i & 1) != 0) {
954 limit = nextSpanTransitionRec(start, limit, kind, rightChild(i));
955 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956 }
957
958 return limit;
959 }
960
961 /**
962 * Return a new CharSequence containing a copy of the specified
963 * range of this buffer, including the overlapping spans.
964 */
965 public CharSequence subSequence(int start, int end) {
966 return new SpannableStringBuilder(this, start, end);
967 }
968
969 /**
970 * Copy the specified range of chars from this buffer into the
971 * specified array, beginning at the specified offset.
972 */
973 public void getChars(int start, int end, char[] dest, int destoff) {
974 checkRange("getChars", start, end);
975
976 if (end <= mGapStart) {
977 System.arraycopy(mText, start, dest, destoff, end - start);
978 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700979 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980 } else {
981 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
982 System.arraycopy(mText, mGapStart + mGapLength,
Gilles Debunne174c44c2012-04-10 16:01:09 -0700983 dest, destoff + (mGapStart - start),
984 end - mGapStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985 }
986 }
987
988 /**
989 * Return a String containing a copy of the chars in this buffer.
990 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700991 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992 public String toString() {
993 int len = length();
994 char[] buf = new char[len];
995
996 getChars(0, len, buf, 0);
997 return new String(buf);
998 }
999
Gilles Debunne653d3a22011-12-07 10:35:59 -08001000 /**
1001 * Return a String containing a copy of the chars in this buffer, limited to the
1002 * [start, end[ range.
1003 * @hide
1004 */
1005 public String substring(int start, int end) {
1006 char[] buf = new char[end - start];
1007 getChars(start, end, buf, 0);
1008 return new String(buf);
1009 }
1010
James Cookd2026682015-03-03 14:40:14 -08001011 /**
1012 * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling
1013 * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that
1014 * recursively triggered a TextWatcher.
1015 */
1016 public int getTextWatcherDepth() {
1017 return mTextWatcherDepth;
1018 }
1019
Gilles Debunneb51036f2012-04-02 11:27:50 -07001020 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
1021 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001022
James Cookd2026682015-03-03 14:40:14 -08001023 mTextWatcherDepth++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -07001025 watchers[i].beforeTextChanged(this, start, before, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 }
James Cookd2026682015-03-03 14:40:14 -08001027 mTextWatcherDepth--;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 }
1029
Gilles Debunneb51036f2012-04-02 11:27:50 -07001030 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
1031 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032
James Cookd2026682015-03-03 14:40:14 -08001033 mTextWatcherDepth++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -07001035 watchers[i].onTextChanged(this, start, before, after);
1036 }
James Cookd2026682015-03-03 14:40:14 -08001037 mTextWatcherDepth--;
Gilles Debunneb51036f2012-04-02 11:27:50 -07001038 }
1039
1040 private void sendAfterTextChanged(TextWatcher[] watchers) {
1041 int n = watchers.length;
1042
James Cookd2026682015-03-03 14:40:14 -08001043 mTextWatcherDepth++;
Gilles Debunneb51036f2012-04-02 11:27:50 -07001044 for (int i = 0; i < n; i++) {
1045 watchers[i].afterTextChanged(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046 }
James Cookd2026682015-03-03 14:40:14 -08001047 mTextWatcherDepth--;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001048 }
1049
1050 private void sendSpanAdded(Object what, int start, int end) {
1051 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
1052 int n = recip.length;
1053
1054 for (int i = 0; i < n; i++) {
1055 recip[i].onSpanAdded(this, what, start, end);
1056 }
1057 }
1058
1059 private void sendSpanRemoved(Object what, int start, int end) {
1060 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
1061 int n = recip.length;
1062
1063 for (int i = 0; i < n; i++) {
1064 recip[i].onSpanRemoved(this, what, start, end);
1065 }
1066 }
1067
Gilles Debunne174c44c2012-04-10 16:01:09 -07001068 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
1069 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
1070 // called, so that the order of the span does not affect this broadcast.
1071 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
1072 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
1073 int n = spanWatchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001074 for (int i = 0; i < n; i++) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001075 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076 }
1077 }
1078
1079 private static String region(int start, int end) {
1080 return "(" + start + " ... " + end + ")";
1081 }
1082
1083 private void checkRange(final String operation, int start, int end) {
1084 if (end < start) {
1085 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001086 region(start, end) + " has end before start");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 }
1088
1089 int len = length();
1090
1091 if (start > len || end > len) {
1092 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001093 region(start, end) + " ends beyond length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 }
1095
1096 if (start < 0 || end < 0) {
1097 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001098 region(start, end) + " starts before 0");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 }
1100 }
1101
Gilles Debunne174c44c2012-04-10 16:01:09 -07001102 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 private boolean isprint(char c) { // XXX
1104 if (c >= ' ' && c <= '~')
1105 return true;
1106 else
1107 return false;
1108 }
1109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 private static final int startFlag(int flag) {
1111 return (flag >> 4) & 0x0F;
1112 }
1113
1114 private static final int endFlag(int flag) {
1115 return flag & 0x0F;
1116 }
1117
1118 public void dump() { // XXX
1119 for (int i = 0; i < mGapStart; i++) {
1120 System.out.print('|');
1121 System.out.print(' ');
1122 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1123 System.out.print(' ');
1124 }
1125
1126 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1127 System.out.print('|');
1128 System.out.print('(');
1129 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1130 System.out.print(')');
1131 }
1132
1133 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1134 System.out.print('|');
1135 System.out.print(' ');
1136 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1137 System.out.print(' ');
1138 }
1139
1140 System.out.print('\n');
1141
1142 for (int i = 0; i < mText.length + 1; i++) {
1143 int found = 0;
1144 int wfound = 0;
1145
1146 for (int j = 0; j < mSpanCount; j++) {
1147 if (mSpanStarts[j] == i) {
1148 found = 1;
1149 wfound = j;
1150 break;
1151 }
1152
1153 if (mSpanEnds[j] == i) {
1154 found = 2;
1155 wfound = j;
1156 break;
1157 }
1158 }
1159
1160 if (found == 1) {
1161 if (startFlag(mSpanFlags[wfound]) == MARK)
1162 System.out.print("( ");
1163 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1164 System.out.print("< ");
1165 else
1166 System.out.print("[ ");
1167 } else if (found == 2) {
1168 if (endFlag(mSpanFlags[wfound]) == POINT)
1169 System.out.print(") ");
1170 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1171 System.out.print("> ");
1172 else
1173 System.out.print("] ");
1174 } else {
1175 System.out.print(" ");
1176 }
1177 }
1178
1179 System.out.print("\n");
1180 }
Gilles Debunne26b62d42012-04-26 18:46:19 -07001181 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182
1183 /**
1184 * Don't call this yourself -- exists for Canvas to use internally.
1185 * {@hide}
1186 */
Gilles Debunneb51036f2012-04-02 11:27:50 -07001187 public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188 checkRange("drawText", start, end);
1189
1190 if (end <= mGapStart) {
1191 c.drawText(mText, start, end - start, x, y, p);
1192 } else if (start >= mGapStart) {
1193 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1194 } else {
1195 char[] buf = TextUtils.obtain(end - start);
1196
1197 getChars(start, end, buf, 0);
1198 c.drawText(buf, 0, end - start, x, y, p);
1199 TextUtils.recycle(buf);
1200 }
1201 }
1202
Doug Felt0c702b82010-05-14 10:55:42 -07001203
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 /**
Doug Feltf47d7402010-04-21 16:01:52 -07001205 * Don't call this yourself -- exists for Canvas to use internally.
1206 * {@hide}
1207 */
Gilles Debunneb51036f2012-04-02 11:27:50 -07001208 public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
Raph Levien051910b2014-06-15 18:25:29 -07001209 float x, float y, boolean isRtl, Paint p) {
Doug Feltf47d7402010-04-21 16:01:52 -07001210 checkRange("drawTextRun", start, end);
1211
Doug Felt0c702b82010-05-14 10:55:42 -07001212 int contextLen = contextEnd - contextStart;
1213 int len = end - start;
1214 if (contextEnd <= mGapStart) {
Raph Levien051910b2014-06-15 18:25:29 -07001215 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
Doug Felt0c702b82010-05-14 10:55:42 -07001216 } else if (contextStart >= mGapStart) {
1217 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
Raph Levien051910b2014-06-15 18:25:29 -07001218 contextLen, x, y, isRtl, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001219 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001220 char[] buf = TextUtils.obtain(contextLen);
1221 getChars(contextStart, contextEnd, buf, 0);
Raph Levien051910b2014-06-15 18:25:29 -07001222 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001223 TextUtils.recycle(buf);
1224 }
1225 }
1226
Gilles Debunne174c44c2012-04-10 16:01:09 -07001227 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 * Don't call this yourself -- exists for Paint to use internally.
1229 * {@hide}
1230 */
1231 public float measureText(int start, int end, Paint p) {
1232 checkRange("measureText", start, end);
1233
1234 float ret;
1235
1236 if (end <= mGapStart) {
1237 ret = p.measureText(mText, start, end - start);
1238 } else if (start >= mGapStart) {
1239 ret = p.measureText(mText, start + mGapLength, end - start);
1240 } else {
1241 char[] buf = TextUtils.obtain(end - start);
1242
1243 getChars(start, end, buf, 0);
1244 ret = p.measureText(buf, 0, end - start);
1245 TextUtils.recycle(buf);
1246 }
1247
1248 return ret;
1249 }
1250
1251 /**
1252 * Don't call this yourself -- exists for Paint to use internally.
1253 * {@hide}
1254 */
1255 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1256 checkRange("getTextWidths", start, end);
1257
1258 int ret;
1259
1260 if (end <= mGapStart) {
1261 ret = p.getTextWidths(mText, start, end - start, widths);
1262 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001263 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 } else {
1265 char[] buf = TextUtils.obtain(end - start);
1266
1267 getChars(start, end, buf, 0);
1268 ret = p.getTextWidths(buf, 0, end - start, widths);
1269 TextUtils.recycle(buf);
1270 }
1271
1272 return ret;
1273 }
1274
Doug Felt0c702b82010-05-14 10:55:42 -07001275 /**
1276 * Don't call this yourself -- exists for Paint to use internally.
1277 * {@hide}
1278 */
Raph Levien051910b2014-06-15 18:25:29 -07001279 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl,
Doug Felt0c702b82010-05-14 10:55:42 -07001280 float[] advances, int advancesPos, Paint p) {
1281
1282 float ret;
1283
1284 int contextLen = contextEnd - contextStart;
1285 int len = end - start;
1286
1287 if (end <= mGapStart) {
1288 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
Raph Levien051910b2014-06-15 18:25:29 -07001289 isRtl, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001290 } else if (start >= mGapStart) {
1291 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
Raph Levien051910b2014-06-15 18:25:29 -07001292 contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001293 } else {
1294 char[] buf = TextUtils.obtain(contextLen);
1295 getChars(contextStart, contextEnd, buf, 0);
1296 ret = p.getTextRunAdvances(buf, start - contextStart, len,
Raph Levien051910b2014-06-15 18:25:29 -07001297 0, contextLen, isRtl, advances, advancesPos);
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001298 TextUtils.recycle(buf);
1299 }
1300
1301 return ret;
1302 }
1303
1304 /**
Gilles Debunnef09d5102011-03-02 17:28:31 -08001305 * Returns the next cursor position in the run. This avoids placing the cursor between
1306 * surrogates, between characters that form conjuncts, between base characters and combining
1307 * marks, or within a reordering cluster.
1308 *
1309 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1310 * span enclosing the cursor in the direction of movement.
1311 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1312 * the start of the string.</p>
1313 *
Gilles Debunne616f3832011-03-02 19:50:16 -08001314 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
Gilles Debunnef09d5102011-03-02 17:28:31 -08001315 * this returns -1. Otherwise this will never return a value before contextStart or after
1316 * contextEnd.</p>
1317 *
1318 * @param contextStart the start index of the context
1319 * @param contextEnd the (non-inclusive) end index of the context
Raph Levien051910b2014-06-15 18:25:29 -07001320 * @param dir either DIRECTION_RTL or DIRECTION_LTR
Gilles Debunnef09d5102011-03-02 17:28:31 -08001321 * @param offset the cursor position to move from
Gilles Debunne616f3832011-03-02 19:50:16 -08001322 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1323 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1324 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
Gilles Debunnef09d5102011-03-02 17:28:31 -08001325 * @param p the Paint object that is requesting this information
1326 * @return the offset of the next position, or -1
Gilles Debunneb0b22562011-03-03 14:51:39 -08001327 * @deprecated This is an internal method, refrain from using it in your code
Gilles Debunnef09d5102011-03-02 17:28:31 -08001328 */
Gilles Debunneb0b22562011-03-03 14:51:39 -08001329 @Deprecated
Raph Levien051910b2014-06-15 18:25:29 -07001330 public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001331 int cursorOpt, Paint p) {
Doug Felt0c702b82010-05-14 10:55:42 -07001332
1333 int ret;
1334
1335 int contextLen = contextEnd - contextStart;
1336 if (contextEnd <= mGapStart) {
1337 ret = p.getTextRunCursor(mText, contextStart, contextLen,
Raph Levien051910b2014-06-15 18:25:29 -07001338 dir, offset, cursorOpt);
Doug Felt0c702b82010-05-14 10:55:42 -07001339 } else if (contextStart >= mGapStart) {
1340 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
Raph Levien051910b2014-06-15 18:25:29 -07001341 dir, offset + mGapLength, cursorOpt) - mGapLength;
Doug Felt0c702b82010-05-14 10:55:42 -07001342 } else {
1343 char[] buf = TextUtils.obtain(contextLen);
1344 getChars(contextStart, contextEnd, buf, 0);
1345 ret = p.getTextRunCursor(buf, 0, contextLen,
Raph Levien051910b2014-06-15 18:25:29 -07001346 dir, offset - contextStart, cursorOpt) + contextStart;
Doug Felt0c702b82010-05-14 10:55:42 -07001347 TextUtils.recycle(buf);
1348 }
1349
1350 return ret;
1351 }
1352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001353 // Documentation from interface
1354 public void setFilters(InputFilter[] filters) {
1355 if (filters == null) {
1356 throw new IllegalArgumentException();
1357 }
1358
1359 mFilters = filters;
1360 }
1361
1362 // Documentation from interface
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001363 public InputFilter[] getFilters() {
1364 return mFilters;
1365 }
1366
Chet Haase9b985722013-09-18 15:12:35 -07001367 // Same as SpannableStringInternal
1368 @Override
1369 public boolean equals(Object o) {
1370 if (o instanceof Spanned &&
1371 toString().equals(o.toString())) {
Chet Haase1d3c4b32013-10-03 17:21:14 -07001372 Spanned other = (Spanned) o;
Chet Haase9b985722013-09-18 15:12:35 -07001373 // Check span data
Chet Haase1d3c4b32013-10-03 17:21:14 -07001374 Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
Chet Haase9b985722013-09-18 15:12:35 -07001375 if (mSpanCount == otherSpans.length) {
1376 for (int i = 0; i < mSpanCount; ++i) {
1377 Object thisSpan = mSpans[i];
1378 Object otherSpan = otherSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -07001379 if (thisSpan == this) {
1380 if (other != otherSpan ||
1381 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1382 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1383 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
1384 return false;
1385 }
1386 } else if (!thisSpan.equals(otherSpan) ||
1387 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1388 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1389 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
Chet Haase9b985722013-09-18 15:12:35 -07001390 return false;
1391 }
1392 }
1393 return true;
1394 }
Chet Haase9b985722013-09-18 15:12:35 -07001395 }
1396 return false;
1397 }
1398
1399 // Same as SpannableStringInternal
1400 @Override
1401 public int hashCode() {
1402 int hash = toString().hashCode();
1403 hash = hash * 31 + mSpanCount;
1404 for (int i = 0; i < mSpanCount; ++i) {
1405 Object span = mSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -07001406 if (span != this) {
1407 hash = hash * 31 + span.hashCode();
1408 }
Chet Haase9b985722013-09-18 15:12:35 -07001409 hash = hash * 31 + getSpanStart(span);
1410 hash = hash * 31 + getSpanEnd(span);
1411 hash = hash * 31 + getSpanFlags(span);
1412 }
1413 return hash;
1414 }
1415
Raph Leviend7c020e2015-02-04 20:09:03 -08001416 // Primitives for treating span list as binary tree
1417
1418 // The spans (along with start and end offsets and flags) are stored in linear arrays sorted
1419 // by start offset. For fast searching, there is a binary search structure imposed over these
1420 // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual
1421 // but advantageous approach.
1422
1423 // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving
1424 // logic that accesses the values as a contiguous array. Other balanced binary tree approaches
1425 // (such as a complete binary tree) would require some shuffling of node indices.
1426
1427 // Basic properties of this structure: For a perfect binary tree of height m:
1428 // The tree has 2^(m+1) - 1 total nodes.
1429 // The root of the tree has index 2^m - 1.
1430 // All leaf nodes have even index, all interior nodes odd.
1431 // The height of a node of index i is the number of trailing ones in i's binary representation.
1432 // The left child of a node i of height h is i - 2^(h - 1).
1433 // The right child of a node i of height h is i + 2^(h - 1).
1434
1435 // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general
1436 // structure of a recursive traversal of node i is:
1437 // * traverse left child if i is an interior node
1438 // * process i if i < n
1439 // * traverse right child if i is an interior node and i < n
1440
1441 private int treeRoot() {
1442 return Integer.highestOneBit(mSpanCount) - 1;
1443 }
1444
1445 // (i+1) & ~i is equal to 2^(the number of trailing ones in i)
1446 private static int leftChild(int i) {
1447 return i - (((i + 1) & ~i) >> 1);
1448 }
1449
1450 private static int rightChild(int i) {
1451 return i + (((i + 1) & ~i) >> 1);
1452 }
1453
1454 // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree
1455 // over the binary tree structure described above. For each node, the mSpanMax[] array contains
1456 // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can
1457 // easily reject subtrees that contain no spans overlapping the area of interest.
1458
1459 // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have
1460 // descendants of index < n. In these cases, it simply represents the maximum span end of its
1461 // descendants. This is a consequence of the perfect binary tree structure.
1462 private int calcMax(int i) {
1463 int max = 0;
1464 if ((i & 1) != 0) {
1465 // internal tree node
1466 max = calcMax(leftChild(i));
1467 }
1468 if (i < mSpanCount) {
1469 max = Math.max(max, mSpanEnds[i]);
1470 if ((i & 1) != 0) {
1471 max = Math.max(max, calcMax(rightChild(i)));
1472 }
1473 }
1474 mSpanMax[i] = max;
1475 return max;
1476 }
1477
1478 // restores binary interval tree invariants after any mutation of span structure
1479 private void restoreInvariants() {
1480 if (mSpanCount == 0) return;
1481
1482 // invariant 1: span starts are nondecreasing
1483
1484 // This is a simple insertion sort because we expect it to be mostly sorted.
1485 for (int i = 1; i < mSpanCount; i++) {
1486 if (mSpanStarts[i] < mSpanStarts[i - 1]) {
1487 Object span = mSpans[i];
1488 int start = mSpanStarts[i];
1489 int end = mSpanEnds[i];
1490 int flags = mSpanFlags[i];
1491 int j = i;
1492 do {
1493 mSpans[j] = mSpans[j - 1];
1494 mSpanStarts[j] = mSpanStarts[j - 1];
1495 mSpanEnds[j] = mSpanEnds[j - 1];
1496 mSpanFlags[j] = mSpanFlags[j - 1];
1497 j--;
1498 } while (j > 0 && start < mSpanStarts[j - 1]);
1499 mSpans[j] = span;
1500 mSpanStarts[j] = start;
1501 mSpanEnds[j] = end;
1502 mSpanFlags[j] = flags;
1503 invalidateIndex(j);
1504 }
1505 }
1506
1507 // invariant 2: max is max span end for each node and its descendants
1508 calcMax(treeRoot());
1509
1510 // invariant 3: mIndexOfSpan maps spans back to indices
1511 if (mIndexOfSpan == null) {
1512 mIndexOfSpan = new IdentityHashMap<Object, Integer>();
1513 }
1514 for (int i = mLowWaterMark; i < mSpanCount; i++) {
1515 Integer existing = mIndexOfSpan.get(mSpans[i]);
1516 if (existing == null || existing != i) {
1517 mIndexOfSpan.put(mSpans[i], i);
1518 }
1519 }
1520 mLowWaterMark = Integer.MAX_VALUE;
1521 }
1522
1523 // Call this on any update to mSpans[], so that mIndexOfSpan can be updated
1524 private void invalidateIndex(int i) {
1525 mLowWaterMark = Math.min(i, mLowWaterMark);
1526 }
1527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001528 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1529 private InputFilter[] mFilters = NO_FILTERS;
1530
1531 private char[] mText;
1532 private int mGapStart;
1533 private int mGapLength;
1534
1535 private Object[] mSpans;
1536 private int[] mSpanStarts;
1537 private int[] mSpanEnds;
Raph Leviend7c020e2015-02-04 20:09:03 -08001538 private int[] mSpanMax; // see calcMax() for an explanation of what this array stores
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001539 private int[] mSpanFlags;
1540 private int mSpanCount;
Raph Leviend7c020e2015-02-04 20:09:03 -08001541 private IdentityHashMap<Object, Integer> mIndexOfSpan;
1542 private int mLowWaterMark; // indices below this have not been touched
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001543
James Cookd2026682015-03-03 14:40:14 -08001544 // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of
1545 // how deep the callbacks go.
1546 private int mTextWatcherDepth;
1547
Gilles Debunneb51036f2012-04-02 11:27:50 -07001548 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
Gilles Debunne0249b432012-04-09 16:02:31 -07001549 private static final int MARK = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 private static final int POINT = 2;
1551 private static final int PARAGRAPH = 3;
1552
1553 private static final int START_MASK = 0xF0;
1554 private static final int END_MASK = 0x0F;
1555 private static final int START_SHIFT = 4;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001556
1557 // These bits are not (currently) used by SPANNED flags
Raph Leviend7c020e2015-02-04 20:09:03 -08001558 private static final int SPAN_ADDED = 0x800;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001559 private static final int SPAN_START_AT_START = 0x1000;
1560 private static final int SPAN_START_AT_END = 0x2000;
1561 private static final int SPAN_END_AT_START = 0x4000;
1562 private static final int SPAN_END_AT_END = 0x8000;
1563 private static final int SPAN_START_END_MASK = 0xF000;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001564}