blob: f440853d77f73d5f41f032194c9538d43eaee6dd [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.graphics.Canvas;
Doug Feltf47d7402010-04-21 16:01:52 -070020import android.graphics.Paint;
Gilles Debunne34ec2b52012-04-10 13:25:33 -070021import android.util.Log;
Gilles Debunne6435a562011-08-04 21:22:30 -070022
23import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050024import com.android.internal.util.GrowingArrayUtils;
25
26import libcore.util.EmptyArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
28import java.lang.reflect.Array;
29
30/**
31 * This is the class for text whose content and markup can both be changed.
32 */
Gilles Debunne6435a562011-08-04 21:22:30 -070033public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
34 Appendable, GraphicsOperations {
Jean Chalard84a33202014-02-13 18:24:36 +090035 private final static String TAG = "SpannableStringBuilder";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036 /**
37 * Create a new SpannableStringBuilder with empty contents
38 */
39 public SpannableStringBuilder() {
40 this("");
41 }
42
43 /**
44 * Create a new SpannableStringBuilder containing a copy of the
45 * specified text, including its spans if any.
46 */
47 public SpannableStringBuilder(CharSequence text) {
48 this(text, 0, text.length());
49 }
50
51 /**
52 * Create a new SpannableStringBuilder containing a copy of the
53 * specified slice of the specified text, including its spans if any.
54 */
55 public SpannableStringBuilder(CharSequence text, int start, int end) {
56 int srclen = end - start;
57
Gilles Debunne0249b432012-04-09 16:02:31 -070058 if (srclen < 0) throw new StringIndexOutOfBoundsException();
59
Adam Lesinski776abc22014-03-07 11:30:59 -050060 mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 mGapStart = srclen;
Adam Lesinski776abc22014-03-07 11:30:59 -050062 mGapLength = mText.length - srclen;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063
64 TextUtils.getChars(text, start, end, mText, 0);
65
66 mSpanCount = 0;
Adam Lesinski776abc22014-03-07 11:30:59 -050067 mSpans = EmptyArray.OBJECT;
68 mSpanStarts = EmptyArray.INT;
69 mSpanEnds = EmptyArray.INT;
70 mSpanFlags = EmptyArray.INT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
72 if (text instanceof Spanned) {
73 Spanned sp = (Spanned) text;
74 Object[] spans = sp.getSpans(start, end, Object.class);
75
76 for (int i = 0; i < spans.length; i++) {
77 if (spans[i] instanceof NoCopySpan) {
78 continue;
79 }
Gilles Debunne174c44c2012-04-10 16:01:09 -070080
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 int st = sp.getSpanStart(spans[i]) - start;
82 int en = sp.getSpanEnd(spans[i]) - start;
83 int fl = sp.getSpanFlags(spans[i]);
84
85 if (st < 0)
86 st = 0;
87 if (st > end - start)
88 st = end - start;
89
90 if (en < 0)
91 en = 0;
92 if (en > end - start)
93 en = end - start;
94
Gilles Debunne0249b432012-04-09 16:02:31 -070095 setSpan(false, spans[i], st, en, fl);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 }
97 }
98 }
99
100 public static SpannableStringBuilder valueOf(CharSequence source) {
101 if (source instanceof SpannableStringBuilder) {
102 return (SpannableStringBuilder) source;
103 } else {
104 return new SpannableStringBuilder(source);
105 }
106 }
107
108 /**
109 * Return the char at the specified offset within the buffer.
110 */
111 public char charAt(int where) {
112 int len = length();
113 if (where < 0) {
114 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
115 } else if (where >= len) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700116 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 }
118
119 if (where >= mGapStart)
120 return mText[where + mGapLength];
121 else
122 return mText[where];
123 }
124
125 /**
126 * Return the number of chars in the buffer.
127 */
128 public int length() {
129 return mText.length - mGapLength;
130 }
131
132 private void resizeFor(int size) {
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700133 final int oldLength = mText.length;
Adam Lesinski776abc22014-03-07 11:30:59 -0500134 if (size + 1 <= oldLength) {
135 return;
136 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137
Adam Lesinski776abc22014-03-07 11:30:59 -0500138 char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700139 System.arraycopy(mText, 0, newText, 0, mGapStart);
Adam Lesinski776abc22014-03-07 11:30:59 -0500140 final int newLength = newText.length;
141 final int delta = newLength - oldLength;
Gilles Debunne90985282012-04-17 17:08:04 -0700142 final int after = oldLength - (mGapStart + mGapLength);
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700143 System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
144 mText = newText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700146 mGapLength += delta;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 if (mGapLength < 1)
148 new Exception("mGapLength < 1").printStackTrace();
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700149
150 for (int i = 0; i < mSpanCount; i++) {
151 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
152 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
153 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 }
155
156 private void moveGapTo(int where) {
157 if (where == mGapStart)
158 return;
159
Gilles Debunne0249b432012-04-09 16:02:31 -0700160 boolean atEnd = (where == length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161
162 if (where < mGapStart) {
163 int overlap = mGapStart - where;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700164 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 } else /* where > mGapStart */ {
166 int overlap = where - mGapStart;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700167 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 }
169
170 // XXX be more clever
171 for (int i = 0; i < mSpanCount; i++) {
172 int start = mSpanStarts[i];
173 int end = mSpanEnds[i];
174
175 if (start > mGapStart)
176 start -= mGapLength;
177 if (start > where)
178 start += mGapLength;
179 else if (start == where) {
180 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
181
Gilles Debunne0249b432012-04-09 16:02:31 -0700182 if (flag == POINT || (atEnd && flag == PARAGRAPH))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 start += mGapLength;
184 }
185
186 if (end > mGapStart)
187 end -= mGapLength;
188 if (end > where)
189 end += mGapLength;
190 else if (end == where) {
191 int flag = (mSpanFlags[i] & END_MASK);
192
Gilles Debunne0249b432012-04-09 16:02:31 -0700193 if (flag == POINT || (atEnd && flag == PARAGRAPH))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 end += mGapLength;
195 }
196
197 mSpanStarts[i] = start;
198 mSpanEnds[i] = end;
199 }
200
201 mGapStart = where;
202 }
203
204 // Documentation from interface
205 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
206 return replace(where, where, tb, start, end);
207 }
208
209 // Documentation from interface
210 public SpannableStringBuilder insert(int where, CharSequence tb) {
211 return replace(where, where, tb, 0, tb.length());
212 }
213
214 // Documentation from interface
215 public SpannableStringBuilder delete(int start, int end) {
216 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
217
218 if (mGapLength > 2 * length())
219 resizeFor(length());
Gilles Debunne174c44c2012-04-10 16:01:09 -0700220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 return ret; // == this
222 }
223
224 // Documentation from interface
225 public void clear() {
226 replace(0, length(), "", 0, 0);
227 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700228
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 // Documentation from interface
230 public void clearSpans() {
231 for (int i = mSpanCount - 1; i >= 0; i--) {
232 Object what = mSpans[i];
233 int ostart = mSpanStarts[i];
234 int oend = mSpanEnds[i];
235
236 if (ostart > mGapStart)
237 ostart -= mGapLength;
238 if (oend > mGapStart)
239 oend -= mGapLength;
240
241 mSpanCount = i;
242 mSpans[i] = null;
243
244 sendSpanRemoved(what, ostart, oend);
245 }
246 }
247
248 // Documentation from interface
249 public SpannableStringBuilder append(CharSequence text) {
250 int length = length();
251 return replace(length, length, text, 0, text.length());
252 }
253
254 // Documentation from interface
255 public SpannableStringBuilder append(CharSequence text, int start, int end) {
256 int length = length();
257 return replace(length, length, text, start, end);
258 }
259
260 // Documentation from interface
261 public SpannableStringBuilder append(char text) {
262 return append(String.valueOf(text));
263 }
264
Gilles Debunne174c44c2012-04-10 16:01:09 -0700265 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
266 // Can be negative
Gilles Debunne26b62d42012-04-26 18:46:19 -0700267 final int replacedLength = end - start;
268 final int replacementLength = csEnd - csStart;
269 final int nbNewChars = replacementLength - replacedLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270
271 for (int i = mSpanCount - 1; i >= 0; i--) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700272 int spanStart = mSpanStarts[i];
273 if (spanStart > mGapStart)
274 spanStart -= mGapLength;
275
276 int spanEnd = mSpanEnds[i];
277 if (spanEnd > mGapStart)
278 spanEnd -= mGapLength;
279
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700281 int ost = spanStart;
282 int oen = spanEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 int clen = length();
284
Gilles Debunne174c44c2012-04-10 16:01:09 -0700285 if (spanStart > start && spanStart <= end) {
286 for (spanStart = end; spanStart < clen; spanStart++)
287 if (spanStart > end && charAt(spanStart - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 break;
289 }
290
Gilles Debunne174c44c2012-04-10 16:01:09 -0700291 if (spanEnd > start && spanEnd <= end) {
292 for (spanEnd = end; spanEnd < clen; spanEnd++)
293 if (spanEnd > end && charAt(spanEnd - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 break;
295 }
296
Gilles Debunne174c44c2012-04-10 16:01:09 -0700297 if (spanStart != ost || spanEnd != oen)
298 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700300
301 int flags = 0;
302 if (spanStart == start) flags |= SPAN_START_AT_START;
303 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
304 if (spanEnd == start) flags |= SPAN_END_AT_START;
305 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
306 mSpanFlags[i] |= flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 }
308
309 moveGapTo(end);
310
Gilles Debunne312cd582010-07-02 17:02:34 -0700311 if (nbNewChars >= mGapLength) {
312 resizeFor(mText.length + nbNewChars - mGapLength);
313 }
314
Gilles Debunnee2448682012-05-04 15:29:09 -0700315 final boolean textIsRemoved = replacementLength == 0;
Gilles Debunne90985282012-04-17 17:08:04 -0700316 // The removal pass needs to be done before the gap is updated in order to broadcast the
317 // correct previous positions to the correct intersecting SpanWatchers
Gilles Debunne26b62d42012-04-26 18:46:19 -0700318 if (replacedLength > 0) { // no need for span fixup on pure insertion
Gilles Debunne90985282012-04-17 17:08:04 -0700319 // A for loop will not work because the array is being modified
320 // Do not iterate in reverse to keep the SpanWatchers notified in ordering
321 // Also, a removed SpanWatcher should not get notified of removed spans located
322 // further in the span array.
323 int i = 0;
324 while (i < mSpanCount) {
325 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
326 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
Gilles Debunnee2448682012-05-04 15:29:09 -0700327 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
328 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
329 // This condition indicates that the span would become empty
330 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
Gilles Debunne90985282012-04-17 17:08:04 -0700331 removeSpan(i);
Gilles Debunnee2448682012-05-04 15:29:09 -0700332 continue; // do not increment i, spans will be shifted left in the array
Gilles Debunne90985282012-04-17 17:08:04 -0700333 }
Gilles Debunnee2448682012-05-04 15:29:09 -0700334
335 i++;
Gilles Debunne90985282012-04-17 17:08:04 -0700336 }
337 }
338
Gilles Debunne312cd582010-07-02 17:02:34 -0700339 mGapStart += nbNewChars;
340 mGapLength -= nbNewChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341
342 if (mGapLength < 1)
343 new Exception("mGapLength < 1").printStackTrace();
344
Gilles Debunne174c44c2012-04-10 16:01:09 -0700345 TextUtils.getChars(cs, csStart, csEnd, mText, start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346
Gilles Debunne26b62d42012-04-26 18:46:19 -0700347 if (replacedLength > 0) { // no need for span fixup on pure insertion
Gilles Debunne90985282012-04-17 17:08:04 -0700348 final boolean atEnd = (mGapStart + mGapLength == mText.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349
Gilles Debunne90985282012-04-17 17:08:04 -0700350 for (int i = 0; i < mSpanCount; i++) {
Gilles Debunne26b62d42012-04-26 18:46:19 -0700351 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
352 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
353 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354
Gilles Debunne26b62d42012-04-26 18:46:19 -0700355 final int endFlag = (mSpanFlags[i] & END_MASK);
356 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
357 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 }
359 }
Gilles Debunne0249b432012-04-09 16:02:31 -0700360
Gilles Debunne174c44c2012-04-10 16:01:09 -0700361 mSpanCountBeforeAdd = mSpanCount;
362
363 if (cs instanceof Spanned) {
364 Spanned sp = (Spanned) cs;
365 Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
Gilles Debunne0249b432012-04-09 16:02:31 -0700366
367 for (int i = 0; i < spans.length; i++) {
368 int st = sp.getSpanStart(spans[i]);
369 int en = sp.getSpanEnd(spans[i]);
370
Gilles Debunne174c44c2012-04-10 16:01:09 -0700371 if (st < csStart) st = csStart;
372 if (en > csEnd) en = csEnd;
Gilles Debunne0249b432012-04-09 16:02:31 -0700373
374 // Add span only if this object is not yet used as a span in this string
Gilles Debunne90985282012-04-17 17:08:04 -0700375 if (getSpanStart(spans[i]) < 0) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700376 setSpan(false, spans[i], st - csStart + start, en - csStart + start,
377 sp.getSpanFlags(spans[i]));
Gilles Debunne0249b432012-04-09 16:02:31 -0700378 }
379 }
380 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 }
382
Gilles Debunne26b62d42012-04-26 18:46:19 -0700383 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
384 boolean textIsRemoved) {
385 if (offset >= start && offset < mGapStart + mGapLength) {
386 if (flag == POINT) {
387 // A POINT located inside the replaced range should be moved to the end of the
388 // replaced text.
389 // The exception is when the point is at the start of the range and we are doing a
390 // text replacement (as opposed to a deletion): the point stays there.
391 if (textIsRemoved || offset > start) {
392 return mGapStart + mGapLength;
393 }
394 } else {
395 if (flag == PARAGRAPH) {
396 if (atEnd) {
397 return mGapStart + mGapLength;
398 }
399 } else { // MARK
Gilles Debunnee2448682012-05-04 15:29:09 -0700400 // MARKs should be moved to the start, with the exception of a mark located at
401 // the end of the range (which will be < mGapStart + mGapLength since mGapLength
402 // is > 0, which should stay 'unchanged' at the end of the replaced text.
Gilles Debunne26b62d42012-04-26 18:46:19 -0700403 if (textIsRemoved || offset < mGapStart - nbNewChars) {
404 return start;
405 } else {
406 // Move to the end of replaced text (needed if nbNewChars != 0)
407 return mGapStart;
408 }
409 }
410 }
411 }
412 return offset;
413 }
414
Gilles Debunne75beb332011-04-29 11:40:22 -0700415 private void removeSpan(int i) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700416 Object object = mSpans[i];
417
418 int start = mSpanStarts[i];
419 int end = mSpanEnds[i];
420
421 if (start > mGapStart) start -= mGapLength;
422 if (end > mGapStart) end -= mGapLength;
423
424 int count = mSpanCount - (i + 1);
425 System.arraycopy(mSpans, i + 1, mSpans, i, count);
426 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
427 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
428 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
Gilles Debunne75beb332011-04-29 11:40:22 -0700429
430 mSpanCount--;
Gilles Debunne6435a562011-08-04 21:22:30 -0700431
432 mSpans[mSpanCount] = null;
433
434 sendSpanRemoved(object, start, end);
Gilles Debunne75beb332011-04-29 11:40:22 -0700435 }
436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 // Documentation from interface
438 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
439 return replace(start, end, tb, 0, tb.length());
440 }
441
442 // Documentation from interface
Jean Chalard84a33202014-02-13 18:24:36 +0900443 public SpannableStringBuilder replace(int start, int end,
Gilles Debunne0249b432012-04-09 16:02:31 -0700444 CharSequence tb, int tbstart, int tbend) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700445 checkRange("replace", start, end);
446
Jean Chalard84a33202014-02-13 18:24:36 +0900447 // Sanity check
448 if (start > end) {
449 Log.w(TAG, "Bad arguments to #replace : "
450 + "start = " + start + ", end = " + end);
451 final int tmp = start;
452 start = end;
453 end = tmp;
454 }
455 if (tbstart > tbend) {
456 Log.w(TAG, "Bad arguments to #replace : "
457 + "tbstart = " + tbstart + ", tbend = " + tbend);
458 final int tmp = tbstart;
459 tbstart = tbend;
460 tbend = tmp;
461 }
462
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 int filtercount = mFilters.length;
464 for (int i = 0; i < filtercount; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700465 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466
467 if (repl != null) {
468 tb = repl;
469 tbstart = 0;
470 tbend = repl.length();
471 }
472 }
473
Gilles Debunneb51036f2012-04-02 11:27:50 -0700474 final int origLen = end - start;
475 final int newLen = tbend - tbstart;
476
Gilles Debunned60da052012-04-18 15:12:20 -0700477 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
478 // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
479 // Early exit so that the text watchers do not get notified
480 return this;
481 }
482
Gilles Debunneb51036f2012-04-02 11:27:50 -0700483 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
484 sendBeforeTextChanged(textWatchers, start, origLen, newLen);
485
Gilles Debunne0249b432012-04-09 16:02:31 -0700486 // Try to keep the cursor / selection at the same relative position during
487 // a text replacement. If replaced or replacement text length is zero, this
488 // is already taken care of.
489 boolean adjustSelection = origLen != 0 && newLen != 0;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700490 int selectionStart = 0;
491 int selectionEnd = 0;
Gilles Debunne0249b432012-04-09 16:02:31 -0700492 if (adjustSelection) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700493 selectionStart = Selection.getSelectionStart(this);
494 selectionEnd = Selection.getSelectionEnd(this);
Gilles Debunne0249b432012-04-09 16:02:31 -0700495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496
Gilles Debunne0249b432012-04-09 16:02:31 -0700497 change(start, end, tb, tbstart, tbend);
Gilles Debunne6435a562011-08-04 21:22:30 -0700498
Gilles Debunne0249b432012-04-09 16:02:31 -0700499 if (adjustSelection) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700500 if (selectionStart > start && selectionStart < end) {
501 final int offset = (selectionStart - start) * newLen / origLen;
502 selectionStart = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503
Gilles Debunne174c44c2012-04-10 16:01:09 -0700504 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 Spanned.SPAN_POINT_POINT);
506 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700507 if (selectionEnd > start && selectionEnd < end) {
508 final int offset = (selectionEnd - start) * newLen / origLen;
509 selectionEnd = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510
Gilles Debunne174c44c2012-04-10 16:01:09 -0700511 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
512 Spanned.SPAN_POINT_POINT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700515
Gilles Debunneb51036f2012-04-02 11:27:50 -0700516 sendTextChanged(textWatchers, start, origLen, newLen);
517 sendAfterTextChanged(textWatchers);
518
Gilles Debunne174c44c2012-04-10 16:01:09 -0700519 // Span watchers need to be called after text watchers, which may update the layout
520 sendToSpanWatchers(start, end, newLen - origLen);
521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 return this;
523 }
524
Gilles Debunned60da052012-04-18 15:12:20 -0700525 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
526 if (text instanceof Spanned) {
527 Spanned spanned = (Spanned) text;
528 Object[] spans = spanned.getSpans(offset, offset, Object.class);
529 final int length = spans.length;
530 for (int i = 0; i < length; i++) {
531 Object span = spans[i];
532 int flags = spanned.getSpanFlags(span);
533 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
534 }
535 }
536 return false;
537 }
538
Gilles Debunne174c44c2012-04-10 16:01:09 -0700539 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
540 for (int i = 0; i < mSpanCountBeforeAdd; i++) {
541 int spanStart = mSpanStarts[i];
542 int spanEnd = mSpanEnds[i];
543 if (spanStart > mGapStart) spanStart -= mGapLength;
544 if (spanEnd > mGapStart) spanEnd -= mGapLength;
545 int spanFlags = mSpanFlags[i];
546
547 int newReplaceEnd = replaceEnd + nbNewChars;
548 boolean spanChanged = false;
Gilles Debunne90985282012-04-17 17:08:04 -0700549
Gilles Debunne174c44c2012-04-10 16:01:09 -0700550 int previousSpanStart = spanStart;
551 if (spanStart > newReplaceEnd) {
552 if (nbNewChars != 0) {
553 previousSpanStart -= nbNewChars;
554 spanChanged = true;
555 }
556 } else if (spanStart >= replaceStart) {
557 // No change if span start was already at replace interval boundaries before replace
558 if ((spanStart != replaceStart ||
559 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
560 (spanStart != newReplaceEnd ||
561 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
Gilles Debunne90985282012-04-17 17:08:04 -0700562 // TODO A correct previousSpanStart cannot be computed at this point.
563 // It would require to save all the previous spans' positions before the replace
564 // Using an invalid -1 value to convey this would break the broacast range
Gilles Debunne174c44c2012-04-10 16:01:09 -0700565 spanChanged = true;
566 }
567 }
Gilles Debunne90985282012-04-17 17:08:04 -0700568
Gilles Debunne174c44c2012-04-10 16:01:09 -0700569 int previousSpanEnd = spanEnd;
570 if (spanEnd > newReplaceEnd) {
571 if (nbNewChars != 0) {
572 previousSpanEnd -= nbNewChars;
573 spanChanged = true;
574 }
575 } else if (spanEnd >= replaceStart) {
576 // No change if span start was already at replace interval boundaries before replace
577 if ((spanEnd != replaceStart ||
578 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
579 (spanEnd != newReplaceEnd ||
580 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
581 // TODO same as above for previousSpanEnd
582 spanChanged = true;
583 }
584 }
585
586 if (spanChanged) {
587 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
588 }
589 mSpanFlags[i] &= ~SPAN_START_END_MASK;
590 }
591
592 // The spans starting at mIntermediateSpanCount were added from the replacement text
593 for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
594 int spanStart = mSpanStarts[i];
595 int spanEnd = mSpanEnds[i];
596 if (spanStart > mGapStart) spanStart -= mGapLength;
597 if (spanEnd > mGapStart) spanEnd -= mGapLength;
598 sendSpanAdded(mSpans[i], spanStart, spanEnd);
599 }
600 }
601
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 /**
603 * Mark the specified range of text with the specified object.
604 * The flags determine how the span will behave when text is
605 * inserted at the start or end of the span's range.
606 */
607 public void setSpan(Object what, int start, int end, int flags) {
608 setSpan(true, what, start, end, flags);
609 }
610
Gilles Debunne6435a562011-08-04 21:22:30 -0700611 private void setSpan(boolean send, Object what, int start, int end, int flags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 checkRange("setSpan", start, end);
613
Gilles Debunne0249b432012-04-09 16:02:31 -0700614 int flagsStart = (flags & START_MASK) >> START_SHIFT;
615 if (flagsStart == PARAGRAPH) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 if (start != 0 && start != length()) {
617 char c = charAt(start - 1);
618
619 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700620 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 }
622 }
623
Gilles Debunne0249b432012-04-09 16:02:31 -0700624 int flagsEnd = flags & END_MASK;
625 if (flagsEnd == PARAGRAPH) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 if (end != 0 && end != length()) {
627 char c = charAt(end - 1);
628
629 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700630 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 }
632 }
633
Gilles Debunne0249b432012-04-09 16:02:31 -0700634 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
635 if (flagsStart == POINT && flagsEnd == MARK && start == end) {
Jean Chalard84a33202014-02-13 18:24:36 +0900636 if (send) {
637 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
638 }
Gilles Debunne34ec2b52012-04-10 13:25:33 -0700639 // Silently ignore invalid spans when they are created from this class.
640 // This avoids the duplication of the above test code before all the
641 // calls to setSpan that are done in this class
642 return;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700643 }
644
Gilles Debunne0249b432012-04-09 16:02:31 -0700645 int nstart = start;
646 int nend = end;
647
Gilles Debunne6435a562011-08-04 21:22:30 -0700648 if (start > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 start += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700650 } else if (start == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700651 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652 start += mGapLength;
653 }
654
Gilles Debunne6435a562011-08-04 21:22:30 -0700655 if (end > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 end += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700657 } else if (end == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700658 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800659 end += mGapLength;
660 }
661
662 int count = mSpanCount;
663 Object[] spans = mSpans;
664
665 for (int i = 0; i < count; i++) {
666 if (spans[i] == what) {
667 int ostart = mSpanStarts[i];
668 int oend = mSpanEnds[i];
669
670 if (ostart > mGapStart)
671 ostart -= mGapLength;
672 if (oend > mGapStart)
673 oend -= mGapLength;
674
675 mSpanStarts[i] = start;
676 mSpanEnds[i] = end;
677 mSpanFlags[i] = flags;
678
Gilles Debunnefc1190b2012-03-30 13:41:31 -0700679 if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680
681 return;
682 }
683 }
684
Adam Lesinski776abc22014-03-07 11:30:59 -0500685 mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
686 mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
687 mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
688 mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 mSpanCount++;
690
Gilles Debunnefc1190b2012-03-30 13:41:31 -0700691 if (send) sendSpanAdded(what, nstart, nend);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 }
693
694 /**
695 * Remove the specified markup object from the buffer.
696 */
697 public void removeSpan(Object what) {
698 for (int i = mSpanCount - 1; i >= 0; i--) {
699 if (mSpans[i] == what) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700700 removeSpan(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 return;
702 }
703 }
704 }
705
706 /**
707 * Return the buffer offset of the beginning of the specified
708 * markup object, or -1 if it is not attached to this buffer.
709 */
710 public int getSpanStart(Object what) {
711 int count = mSpanCount;
712 Object[] spans = mSpans;
713
714 for (int i = count - 1; i >= 0; i--) {
715 if (spans[i] == what) {
716 int where = mSpanStarts[i];
717
718 if (where > mGapStart)
719 where -= mGapLength;
720
721 return where;
722 }
723 }
724
725 return -1;
726 }
727
728 /**
729 * Return the buffer offset of the end of the specified
730 * markup object, or -1 if it is not attached to this buffer.
731 */
732 public int getSpanEnd(Object what) {
733 int count = mSpanCount;
734 Object[] spans = mSpans;
735
736 for (int i = count - 1; i >= 0; i--) {
737 if (spans[i] == what) {
738 int where = mSpanEnds[i];
739
740 if (where > mGapStart)
741 where -= mGapLength;
742
743 return where;
744 }
745 }
746
747 return -1;
748 }
749
750 /**
751 * Return the flags of the end of the specified
752 * markup object, or 0 if it is not attached to this buffer.
753 */
754 public int getSpanFlags(Object what) {
755 int count = mSpanCount;
756 Object[] spans = mSpans;
757
758 for (int i = count - 1; i >= 0; i--) {
759 if (spans[i] == what) {
760 return mSpanFlags[i];
761 }
762 }
763
764 return 0;
765 }
766
767 /**
768 * Return an array of the spans of the specified type that overlap
769 * the specified range of the buffer. The kind may be Object.class to get
770 * a list of all the spans regardless of type.
771 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700772 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700774 if (kind == null) return ArrayUtils.emptyArray(kind);
775
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800776 int spanCount = mSpanCount;
777 Object[] spans = mSpans;
778 int[] starts = mSpanStarts;
779 int[] ends = mSpanEnds;
780 int[] flags = mSpanFlags;
781 int gapstart = mGapStart;
782 int gaplen = mGapLength;
783
784 int count = 0;
Gilles Debunne312cd582010-07-02 17:02:34 -0700785 T[] ret = null;
786 T ret1 = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787
788 for (int i = 0; i < spanCount; i++) {
789 int spanStart = starts[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 if (spanStart > gapstart) {
791 spanStart -= gaplen;
792 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 if (spanStart > queryEnd) {
794 continue;
795 }
Gilles Debunneb062e812011-09-27 14:58:37 -0700796
797 int spanEnd = ends[i];
798 if (spanEnd > gapstart) {
799 spanEnd -= gaplen;
800 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 if (spanEnd < queryStart) {
802 continue;
803 }
804
805 if (spanStart != spanEnd && queryStart != queryEnd) {
806 if (spanStart == queryEnd)
807 continue;
808 if (spanEnd == queryStart)
809 continue;
810 }
811
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700812 // Expensive test, should be performed after the previous tests
813 if (!kind.isInstance(spans[i])) continue;
814
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 if (count == 0) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700816 // Safe conversion thanks to the isInstance test above
817 ret1 = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 count++;
819 } else {
820 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700821 // Safe conversion, but requires a suppressWarning
822 ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 ret[0] = ret1;
824 }
825
826 int prio = flags[i] & SPAN_PRIORITY;
827 if (prio != 0) {
828 int j;
829
830 for (j = 0; j < count; j++) {
831 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
832
833 if (prio > p) {
834 break;
835 }
836 }
837
838 System.arraycopy(ret, j, ret, j + 1, count - j);
Gilles Debunne312cd582010-07-02 17:02:34 -0700839 // Safe conversion thanks to the isInstance test above
840 ret[j] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800841 count++;
842 } else {
Gilles Debunne312cd582010-07-02 17:02:34 -0700843 // Safe conversion thanks to the isInstance test above
844 ret[count++] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800845 }
846 }
847 }
848
849 if (count == 0) {
Doug Feltf47d7402010-04-21 16:01:52 -0700850 return ArrayUtils.emptyArray(kind);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 }
852 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700853 // Safe conversion, but requires a suppressWarning
854 ret = (T[]) Array.newInstance(kind, 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800855 ret[0] = ret1;
Gilles Debunne312cd582010-07-02 17:02:34 -0700856 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 }
858 if (count == ret.length) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700859 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860 }
861
Gilles Debunne312cd582010-07-02 17:02:34 -0700862 // Safe conversion, but requires a suppressWarning
863 T[] nret = (T[]) Array.newInstance(kind, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 System.arraycopy(ret, 0, nret, 0, count);
Gilles Debunne312cd582010-07-02 17:02:34 -0700865 return nret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 }
867
868 /**
869 * Return the next offset after <code>start</code> but less than or
870 * equal to <code>limit</code> where a span of the specified type
871 * begins or ends.
872 */
873 public int nextSpanTransition(int start, int limit, Class kind) {
874 int count = mSpanCount;
875 Object[] spans = mSpans;
876 int[] starts = mSpanStarts;
877 int[] ends = mSpanEnds;
878 int gapstart = mGapStart;
879 int gaplen = mGapLength;
880
881 if (kind == null) {
882 kind = Object.class;
883 }
884
885 for (int i = 0; i < count; i++) {
886 int st = starts[i];
887 int en = ends[i];
888
889 if (st > gapstart)
890 st -= gaplen;
891 if (en > gapstart)
892 en -= gaplen;
893
894 if (st > start && st < limit && kind.isInstance(spans[i]))
895 limit = st;
896 if (en > start && en < limit && kind.isInstance(spans[i]))
897 limit = en;
898 }
899
900 return limit;
901 }
902
903 /**
904 * Return a new CharSequence containing a copy of the specified
905 * range of this buffer, including the overlapping spans.
906 */
907 public CharSequence subSequence(int start, int end) {
908 return new SpannableStringBuilder(this, start, end);
909 }
910
911 /**
912 * Copy the specified range of chars from this buffer into the
913 * specified array, beginning at the specified offset.
914 */
915 public void getChars(int start, int end, char[] dest, int destoff) {
916 checkRange("getChars", start, end);
917
918 if (end <= mGapStart) {
919 System.arraycopy(mText, start, dest, destoff, end - start);
920 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700921 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922 } else {
923 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
924 System.arraycopy(mText, mGapStart + mGapLength,
Gilles Debunne174c44c2012-04-10 16:01:09 -0700925 dest, destoff + (mGapStart - start),
926 end - mGapStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 }
928 }
929
930 /**
931 * Return a String containing a copy of the chars in this buffer.
932 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700933 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 public String toString() {
935 int len = length();
936 char[] buf = new char[len];
937
938 getChars(0, len, buf, 0);
939 return new String(buf);
940 }
941
Gilles Debunne653d3a22011-12-07 10:35:59 -0800942 /**
943 * Return a String containing a copy of the chars in this buffer, limited to the
944 * [start, end[ range.
945 * @hide
946 */
947 public String substring(int start, int end) {
948 char[] buf = new char[end - start];
949 getChars(start, end, buf, 0);
950 return new String(buf);
951 }
952
Gilles Debunneb51036f2012-04-02 11:27:50 -0700953 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
954 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955
956 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700957 watchers[i].beforeTextChanged(this, start, before, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958 }
959 }
960
Gilles Debunneb51036f2012-04-02 11:27:50 -0700961 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
962 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963
964 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700965 watchers[i].onTextChanged(this, start, before, after);
966 }
967 }
968
969 private void sendAfterTextChanged(TextWatcher[] watchers) {
970 int n = watchers.length;
971
972 for (int i = 0; i < n; i++) {
973 watchers[i].afterTextChanged(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800974 }
975 }
976
977 private void sendSpanAdded(Object what, int start, int end) {
978 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
979 int n = recip.length;
980
981 for (int i = 0; i < n; i++) {
982 recip[i].onSpanAdded(this, what, start, end);
983 }
984 }
985
986 private void sendSpanRemoved(Object what, int start, int end) {
987 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
988 int n = recip.length;
989
990 for (int i = 0; i < n; i++) {
991 recip[i].onSpanRemoved(this, what, start, end);
992 }
993 }
994
Gilles Debunne174c44c2012-04-10 16:01:09 -0700995 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
996 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
997 // called, so that the order of the span does not affect this broadcast.
998 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
999 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
1000 int n = spanWatchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001 for (int i = 0; i < n; i++) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001002 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003 }
1004 }
1005
1006 private static String region(int start, int end) {
1007 return "(" + start + " ... " + end + ")";
1008 }
1009
1010 private void checkRange(final String operation, int start, int end) {
1011 if (end < start) {
1012 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001013 region(start, end) + " has end before start");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 }
1015
1016 int len = length();
1017
1018 if (start > len || end > len) {
1019 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001020 region(start, end) + " ends beyond length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 }
1022
1023 if (start < 0 || end < 0) {
1024 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001025 region(start, end) + " starts before 0");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 }
1027 }
1028
Gilles Debunne174c44c2012-04-10 16:01:09 -07001029 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 private boolean isprint(char c) { // XXX
1031 if (c >= ' ' && c <= '~')
1032 return true;
1033 else
1034 return false;
1035 }
1036
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037 private static final int startFlag(int flag) {
1038 return (flag >> 4) & 0x0F;
1039 }
1040
1041 private static final int endFlag(int flag) {
1042 return flag & 0x0F;
1043 }
1044
1045 public void dump() { // XXX
1046 for (int i = 0; i < mGapStart; i++) {
1047 System.out.print('|');
1048 System.out.print(' ');
1049 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1050 System.out.print(' ');
1051 }
1052
1053 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1054 System.out.print('|');
1055 System.out.print('(');
1056 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1057 System.out.print(')');
1058 }
1059
1060 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1061 System.out.print('|');
1062 System.out.print(' ');
1063 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1064 System.out.print(' ');
1065 }
1066
1067 System.out.print('\n');
1068
1069 for (int i = 0; i < mText.length + 1; i++) {
1070 int found = 0;
1071 int wfound = 0;
1072
1073 for (int j = 0; j < mSpanCount; j++) {
1074 if (mSpanStarts[j] == i) {
1075 found = 1;
1076 wfound = j;
1077 break;
1078 }
1079
1080 if (mSpanEnds[j] == i) {
1081 found = 2;
1082 wfound = j;
1083 break;
1084 }
1085 }
1086
1087 if (found == 1) {
1088 if (startFlag(mSpanFlags[wfound]) == MARK)
1089 System.out.print("( ");
1090 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1091 System.out.print("< ");
1092 else
1093 System.out.print("[ ");
1094 } else if (found == 2) {
1095 if (endFlag(mSpanFlags[wfound]) == POINT)
1096 System.out.print(") ");
1097 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1098 System.out.print("> ");
1099 else
1100 System.out.print("] ");
1101 } else {
1102 System.out.print(" ");
1103 }
1104 }
1105
1106 System.out.print("\n");
1107 }
Gilles Debunne26b62d42012-04-26 18:46:19 -07001108 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109
1110 /**
1111 * Don't call this yourself -- exists for Canvas to use internally.
1112 * {@hide}
1113 */
Gilles Debunneb51036f2012-04-02 11:27:50 -07001114 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 -08001115 checkRange("drawText", start, end);
1116
1117 if (end <= mGapStart) {
1118 c.drawText(mText, start, end - start, x, y, p);
1119 } else if (start >= mGapStart) {
1120 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1121 } else {
1122 char[] buf = TextUtils.obtain(end - start);
1123
1124 getChars(start, end, buf, 0);
1125 c.drawText(buf, 0, end - start, x, y, p);
1126 TextUtils.recycle(buf);
1127 }
1128 }
1129
Doug Felt0c702b82010-05-14 10:55:42 -07001130
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131 /**
Doug Feltf47d7402010-04-21 16:01:52 -07001132 * Don't call this yourself -- exists for Canvas to use internally.
1133 * {@hide}
1134 */
Gilles Debunneb51036f2012-04-02 11:27:50 -07001135 public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001136 float x, float y, int flags, Paint p) {
Doug Feltf47d7402010-04-21 16:01:52 -07001137 checkRange("drawTextRun", start, end);
1138
Doug Felt0c702b82010-05-14 10:55:42 -07001139 int contextLen = contextEnd - contextStart;
1140 int len = end - start;
1141 if (contextEnd <= mGapStart) {
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001142 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
Doug Felt0c702b82010-05-14 10:55:42 -07001143 } else if (contextStart >= mGapStart) {
1144 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001145 contextLen, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001146 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001147 char[] buf = TextUtils.obtain(contextLen);
1148 getChars(contextStart, contextEnd, buf, 0);
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001149 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001150 TextUtils.recycle(buf);
1151 }
1152 }
1153
Gilles Debunne174c44c2012-04-10 16:01:09 -07001154 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 * Don't call this yourself -- exists for Paint to use internally.
1156 * {@hide}
1157 */
1158 public float measureText(int start, int end, Paint p) {
1159 checkRange("measureText", start, end);
1160
1161 float ret;
1162
1163 if (end <= mGapStart) {
1164 ret = p.measureText(mText, start, end - start);
1165 } else if (start >= mGapStart) {
1166 ret = p.measureText(mText, start + mGapLength, end - start);
1167 } else {
1168 char[] buf = TextUtils.obtain(end - start);
1169
1170 getChars(start, end, buf, 0);
1171 ret = p.measureText(buf, 0, end - start);
1172 TextUtils.recycle(buf);
1173 }
1174
1175 return ret;
1176 }
1177
1178 /**
1179 * Don't call this yourself -- exists for Paint to use internally.
1180 * {@hide}
1181 */
1182 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1183 checkRange("getTextWidths", start, end);
1184
1185 int ret;
1186
1187 if (end <= mGapStart) {
1188 ret = p.getTextWidths(mText, start, end - start, widths);
1189 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001190 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191 } else {
1192 char[] buf = TextUtils.obtain(end - start);
1193
1194 getChars(start, end, buf, 0);
1195 ret = p.getTextWidths(buf, 0, end - start, widths);
1196 TextUtils.recycle(buf);
1197 }
1198
1199 return ret;
1200 }
1201
Doug Felt0c702b82010-05-14 10:55:42 -07001202 /**
1203 * Don't call this yourself -- exists for Paint to use internally.
1204 * {@hide}
1205 */
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001206 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
Doug Felt0c702b82010-05-14 10:55:42 -07001207 float[] advances, int advancesPos, Paint p) {
1208
1209 float ret;
1210
1211 int contextLen = contextEnd - contextStart;
1212 int len = end - start;
1213
1214 if (end <= mGapStart) {
1215 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001216 flags, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001217 } else if (start >= mGapStart) {
1218 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001219 contextStart + mGapLength, contextLen, flags, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001220 } else {
1221 char[] buf = TextUtils.obtain(contextLen);
1222 getChars(contextStart, contextEnd, buf, 0);
1223 ret = p.getTextRunAdvances(buf, start - contextStart, len,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001224 0, contextLen, flags, advances, advancesPos);
1225 TextUtils.recycle(buf);
1226 }
1227
1228 return ret;
1229 }
1230
1231 /**
Gilles Debunnef09d5102011-03-02 17:28:31 -08001232 * Returns the next cursor position in the run. This avoids placing the cursor between
1233 * surrogates, between characters that form conjuncts, between base characters and combining
1234 * marks, or within a reordering cluster.
1235 *
1236 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1237 * span enclosing the cursor in the direction of movement.
1238 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1239 * the start of the string.</p>
1240 *
Gilles Debunne616f3832011-03-02 19:50:16 -08001241 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
Gilles Debunnef09d5102011-03-02 17:28:31 -08001242 * this returns -1. Otherwise this will never return a value before contextStart or after
1243 * contextEnd.</p>
1244 *
1245 * @param contextStart the start index of the context
1246 * @param contextEnd the (non-inclusive) end index of the context
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001247 * @param flags either DIRECTION_RTL or DIRECTION_LTR
Gilles Debunnef09d5102011-03-02 17:28:31 -08001248 * @param offset the cursor position to move from
Gilles Debunne616f3832011-03-02 19:50:16 -08001249 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1250 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1251 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
Gilles Debunnef09d5102011-03-02 17:28:31 -08001252 * @param p the Paint object that is requesting this information
1253 * @return the offset of the next position, or -1
Gilles Debunneb0b22562011-03-03 14:51:39 -08001254 * @deprecated This is an internal method, refrain from using it in your code
Gilles Debunnef09d5102011-03-02 17:28:31 -08001255 */
Gilles Debunneb0b22562011-03-03 14:51:39 -08001256 @Deprecated
Doug Felt0c702b82010-05-14 10:55:42 -07001257 public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001258 int cursorOpt, Paint p) {
Doug Felt0c702b82010-05-14 10:55:42 -07001259
1260 int ret;
1261
1262 int contextLen = contextEnd - contextStart;
1263 if (contextEnd <= mGapStart) {
1264 ret = p.getTextRunCursor(mText, contextStart, contextLen,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001265 flags, offset, cursorOpt);
Doug Felt0c702b82010-05-14 10:55:42 -07001266 } else if (contextStart >= mGapStart) {
1267 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001268 flags, offset + mGapLength, cursorOpt) - mGapLength;
Doug Felt0c702b82010-05-14 10:55:42 -07001269 } else {
1270 char[] buf = TextUtils.obtain(contextLen);
1271 getChars(contextStart, contextEnd, buf, 0);
1272 ret = p.getTextRunCursor(buf, 0, contextLen,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001273 flags, offset - contextStart, cursorOpt) + contextStart;
Doug Felt0c702b82010-05-14 10:55:42 -07001274 TextUtils.recycle(buf);
1275 }
1276
1277 return ret;
1278 }
1279
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001280 // Documentation from interface
1281 public void setFilters(InputFilter[] filters) {
1282 if (filters == null) {
1283 throw new IllegalArgumentException();
1284 }
1285
1286 mFilters = filters;
1287 }
1288
1289 // Documentation from interface
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290 public InputFilter[] getFilters() {
1291 return mFilters;
1292 }
1293
Chet Haase9b985722013-09-18 15:12:35 -07001294 // Same as SpannableStringInternal
1295 @Override
1296 public boolean equals(Object o) {
1297 if (o instanceof Spanned &&
1298 toString().equals(o.toString())) {
Chet Haase1d3c4b32013-10-03 17:21:14 -07001299 Spanned other = (Spanned) o;
Chet Haase9b985722013-09-18 15:12:35 -07001300 // Check span data
Chet Haase1d3c4b32013-10-03 17:21:14 -07001301 Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
Chet Haase9b985722013-09-18 15:12:35 -07001302 if (mSpanCount == otherSpans.length) {
1303 for (int i = 0; i < mSpanCount; ++i) {
1304 Object thisSpan = mSpans[i];
1305 Object otherSpan = otherSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -07001306 if (thisSpan == this) {
1307 if (other != otherSpan ||
1308 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1309 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1310 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
1311 return false;
1312 }
1313 } else if (!thisSpan.equals(otherSpan) ||
1314 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1315 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1316 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
Chet Haase9b985722013-09-18 15:12:35 -07001317 return false;
1318 }
1319 }
1320 return true;
1321 }
Chet Haase9b985722013-09-18 15:12:35 -07001322 }
1323 return false;
1324 }
1325
1326 // Same as SpannableStringInternal
1327 @Override
1328 public int hashCode() {
1329 int hash = toString().hashCode();
1330 hash = hash * 31 + mSpanCount;
1331 for (int i = 0; i < mSpanCount; ++i) {
1332 Object span = mSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -07001333 if (span != this) {
1334 hash = hash * 31 + span.hashCode();
1335 }
Chet Haase9b985722013-09-18 15:12:35 -07001336 hash = hash * 31 + getSpanStart(span);
1337 hash = hash * 31 + getSpanEnd(span);
1338 hash = hash * 31 + getSpanFlags(span);
1339 }
1340 return hash;
1341 }
1342
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1344 private InputFilter[] mFilters = NO_FILTERS;
1345
1346 private char[] mText;
1347 private int mGapStart;
1348 private int mGapLength;
1349
1350 private Object[] mSpans;
1351 private int[] mSpanStarts;
1352 private int[] mSpanEnds;
1353 private int[] mSpanFlags;
1354 private int mSpanCount;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001355 private int mSpanCountBeforeAdd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356
Gilles Debunneb51036f2012-04-02 11:27:50 -07001357 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
Gilles Debunne0249b432012-04-09 16:02:31 -07001358 private static final int MARK = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001359 private static final int POINT = 2;
1360 private static final int PARAGRAPH = 3;
1361
1362 private static final int START_MASK = 0xF0;
1363 private static final int END_MASK = 0x0F;
1364 private static final int START_SHIFT = 4;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001365
1366 // These bits are not (currently) used by SPANNED flags
1367 private static final int SPAN_START_AT_START = 0x1000;
1368 private static final int SPAN_START_AT_END = 0x2000;
1369 private static final int SPAN_END_AT_START = 0x4000;
1370 private static final int SPAN_END_AT_END = 0x8000;
1371 private static final int SPAN_START_END_MASK = 0xF000;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372}