blob: 9e4367117a150d9a43c7e5675b91e69d71fb3a9c [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024
25import java.lang.reflect.Array;
26
27/**
28 * This is the class for text whose content and markup can both be changed.
29 */
Gilles Debunne6435a562011-08-04 21:22:30 -070030public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
31 Appendable, GraphicsOperations {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032 /**
33 * Create a new SpannableStringBuilder with empty contents
34 */
35 public SpannableStringBuilder() {
36 this("");
37 }
38
39 /**
40 * Create a new SpannableStringBuilder containing a copy of the
41 * specified text, including its spans if any.
42 */
43 public SpannableStringBuilder(CharSequence text) {
44 this(text, 0, text.length());
45 }
46
47 /**
48 * Create a new SpannableStringBuilder containing a copy of the
49 * specified slice of the specified text, including its spans if any.
50 */
51 public SpannableStringBuilder(CharSequence text, int start, int end) {
52 int srclen = end - start;
53
Gilles Debunne0249b432012-04-09 16:02:31 -070054 if (srclen < 0) throw new StringIndexOutOfBoundsException();
55
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 int len = ArrayUtils.idealCharArraySize(srclen + 1);
57 mText = new char[len];
58 mGapStart = srclen;
59 mGapLength = len - srclen;
60
61 TextUtils.getChars(text, start, end, mText, 0);
62
63 mSpanCount = 0;
64 int alloc = ArrayUtils.idealIntArraySize(0);
65 mSpans = new Object[alloc];
66 mSpanStarts = new int[alloc];
67 mSpanEnds = new int[alloc];
68 mSpanFlags = new int[alloc];
69
70 if (text instanceof Spanned) {
71 Spanned sp = (Spanned) text;
72 Object[] spans = sp.getSpans(start, end, Object.class);
73
74 for (int i = 0; i < spans.length; i++) {
75 if (spans[i] instanceof NoCopySpan) {
76 continue;
77 }
Gilles Debunne174c44c2012-04-10 16:01:09 -070078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 int st = sp.getSpanStart(spans[i]) - start;
80 int en = sp.getSpanEnd(spans[i]) - start;
81 int fl = sp.getSpanFlags(spans[i]);
82
83 if (st < 0)
84 st = 0;
85 if (st > end - start)
86 st = end - start;
87
88 if (en < 0)
89 en = 0;
90 if (en > end - start)
91 en = end - start;
92
Gilles Debunne0249b432012-04-09 16:02:31 -070093 setSpan(false, spans[i], st, en, fl);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 }
95 }
96 }
97
98 public static SpannableStringBuilder valueOf(CharSequence source) {
99 if (source instanceof SpannableStringBuilder) {
100 return (SpannableStringBuilder) source;
101 } else {
102 return new SpannableStringBuilder(source);
103 }
104 }
105
106 /**
107 * Return the char at the specified offset within the buffer.
108 */
109 public char charAt(int where) {
110 int len = length();
111 if (where < 0) {
112 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
113 } else if (where >= len) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700114 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 }
116
117 if (where >= mGapStart)
118 return mText[where + mGapLength];
119 else
120 return mText[where];
121 }
122
123 /**
124 * Return the number of chars in the buffer.
125 */
126 public int length() {
127 return mText.length - mGapLength;
128 }
129
130 private void resizeFor(int size) {
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700131 final int oldLength = mText.length;
132 final int newLength = ArrayUtils.idealCharArraySize(size + 1);
Gilles Debunne90985282012-04-17 17:08:04 -0700133 final int delta = newLength - oldLength;
134 if (delta == 0) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700136 char[] newText = new char[newLength];
137 System.arraycopy(mText, 0, newText, 0, mGapStart);
Gilles Debunne90985282012-04-17 17:08:04 -0700138 final int after = oldLength - (mGapStart + mGapLength);
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700139 System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
140 mText = newText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700142 mGapLength += delta;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 if (mGapLength < 1)
144 new Exception("mGapLength < 1").printStackTrace();
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700145
146 for (int i = 0; i < mSpanCount; i++) {
147 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
148 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
149 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 }
151
152 private void moveGapTo(int where) {
153 if (where == mGapStart)
154 return;
155
Gilles Debunne0249b432012-04-09 16:02:31 -0700156 boolean atEnd = (where == length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157
158 if (where < mGapStart) {
159 int overlap = mGapStart - where;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700160 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 } else /* where > mGapStart */ {
162 int overlap = where - mGapStart;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700163 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 }
165
166 // XXX be more clever
167 for (int i = 0; i < mSpanCount; i++) {
168 int start = mSpanStarts[i];
169 int end = mSpanEnds[i];
170
171 if (start > mGapStart)
172 start -= mGapLength;
173 if (start > where)
174 start += mGapLength;
175 else if (start == where) {
176 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
177
Gilles Debunne0249b432012-04-09 16:02:31 -0700178 if (flag == POINT || (atEnd && flag == PARAGRAPH))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 start += mGapLength;
180 }
181
182 if (end > mGapStart)
183 end -= mGapLength;
184 if (end > where)
185 end += mGapLength;
186 else if (end == where) {
187 int flag = (mSpanFlags[i] & END_MASK);
188
Gilles Debunne0249b432012-04-09 16:02:31 -0700189 if (flag == POINT || (atEnd && flag == PARAGRAPH))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 end += mGapLength;
191 }
192
193 mSpanStarts[i] = start;
194 mSpanEnds[i] = end;
195 }
196
197 mGapStart = where;
198 }
199
200 // Documentation from interface
201 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
202 return replace(where, where, tb, start, end);
203 }
204
205 // Documentation from interface
206 public SpannableStringBuilder insert(int where, CharSequence tb) {
207 return replace(where, where, tb, 0, tb.length());
208 }
209
210 // Documentation from interface
211 public SpannableStringBuilder delete(int start, int end) {
212 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
213
214 if (mGapLength > 2 * length())
215 resizeFor(length());
Gilles Debunne174c44c2012-04-10 16:01:09 -0700216
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 return ret; // == this
218 }
219
220 // Documentation from interface
221 public void clear() {
222 replace(0, length(), "", 0, 0);
223 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700224
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 // Documentation from interface
226 public void clearSpans() {
227 for (int i = mSpanCount - 1; i >= 0; i--) {
228 Object what = mSpans[i];
229 int ostart = mSpanStarts[i];
230 int oend = mSpanEnds[i];
231
232 if (ostart > mGapStart)
233 ostart -= mGapLength;
234 if (oend > mGapStart)
235 oend -= mGapLength;
236
237 mSpanCount = i;
238 mSpans[i] = null;
239
240 sendSpanRemoved(what, ostart, oend);
241 }
242 }
243
244 // Documentation from interface
245 public SpannableStringBuilder append(CharSequence text) {
246 int length = length();
247 return replace(length, length, text, 0, text.length());
248 }
249
250 // Documentation from interface
251 public SpannableStringBuilder append(CharSequence text, int start, int end) {
252 int length = length();
253 return replace(length, length, text, start, end);
254 }
255
256 // Documentation from interface
257 public SpannableStringBuilder append(char text) {
258 return append(String.valueOf(text));
259 }
260
Gilles Debunne174c44c2012-04-10 16:01:09 -0700261 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
262 // Can be negative
Gilles Debunne26b62d42012-04-26 18:46:19 -0700263 final int replacedLength = end - start;
264 final int replacementLength = csEnd - csStart;
265 final int nbNewChars = replacementLength - replacedLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266
267 for (int i = mSpanCount - 1; i >= 0; i--) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700268 int spanStart = mSpanStarts[i];
269 if (spanStart > mGapStart)
270 spanStart -= mGapLength;
271
272 int spanEnd = mSpanEnds[i];
273 if (spanEnd > mGapStart)
274 spanEnd -= mGapLength;
275
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700277 int ost = spanStart;
278 int oen = spanEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 int clen = length();
280
Gilles Debunne174c44c2012-04-10 16:01:09 -0700281 if (spanStart > start && spanStart <= end) {
282 for (spanStart = end; spanStart < clen; spanStart++)
283 if (spanStart > end && charAt(spanStart - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 break;
285 }
286
Gilles Debunne174c44c2012-04-10 16:01:09 -0700287 if (spanEnd > start && spanEnd <= end) {
288 for (spanEnd = end; spanEnd < clen; spanEnd++)
289 if (spanEnd > end && charAt(spanEnd - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 break;
291 }
292
Gilles Debunne174c44c2012-04-10 16:01:09 -0700293 if (spanStart != ost || spanEnd != oen)
294 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700296
297 int flags = 0;
298 if (spanStart == start) flags |= SPAN_START_AT_START;
299 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
300 if (spanEnd == start) flags |= SPAN_END_AT_START;
301 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
302 mSpanFlags[i] |= flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 }
304
305 moveGapTo(end);
306
Gilles Debunne312cd582010-07-02 17:02:34 -0700307 if (nbNewChars >= mGapLength) {
308 resizeFor(mText.length + nbNewChars - mGapLength);
309 }
310
Gilles Debunnee2448682012-05-04 15:29:09 -0700311 final boolean textIsRemoved = replacementLength == 0;
Gilles Debunne90985282012-04-17 17:08:04 -0700312 // The removal pass needs to be done before the gap is updated in order to broadcast the
313 // correct previous positions to the correct intersecting SpanWatchers
Gilles Debunne26b62d42012-04-26 18:46:19 -0700314 if (replacedLength > 0) { // no need for span fixup on pure insertion
Gilles Debunne90985282012-04-17 17:08:04 -0700315 // A for loop will not work because the array is being modified
316 // Do not iterate in reverse to keep the SpanWatchers notified in ordering
317 // Also, a removed SpanWatcher should not get notified of removed spans located
318 // further in the span array.
319 int i = 0;
320 while (i < mSpanCount) {
321 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
322 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
Gilles Debunnee2448682012-05-04 15:29:09 -0700323 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
324 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
325 // This condition indicates that the span would become empty
326 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
Gilles Debunne90985282012-04-17 17:08:04 -0700327 removeSpan(i);
Gilles Debunnee2448682012-05-04 15:29:09 -0700328 continue; // do not increment i, spans will be shifted left in the array
Gilles Debunne90985282012-04-17 17:08:04 -0700329 }
Gilles Debunnee2448682012-05-04 15:29:09 -0700330
331 i++;
Gilles Debunne90985282012-04-17 17:08:04 -0700332 }
333 }
334
Gilles Debunne312cd582010-07-02 17:02:34 -0700335 mGapStart += nbNewChars;
336 mGapLength -= nbNewChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337
338 if (mGapLength < 1)
339 new Exception("mGapLength < 1").printStackTrace();
340
Gilles Debunne174c44c2012-04-10 16:01:09 -0700341 TextUtils.getChars(cs, csStart, csEnd, mText, start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342
Gilles Debunne26b62d42012-04-26 18:46:19 -0700343 if (replacedLength > 0) { // no need for span fixup on pure insertion
Gilles Debunne90985282012-04-17 17:08:04 -0700344 final boolean atEnd = (mGapStart + mGapLength == mText.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345
Gilles Debunne90985282012-04-17 17:08:04 -0700346 for (int i = 0; i < mSpanCount; i++) {
Gilles Debunne26b62d42012-04-26 18:46:19 -0700347 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
348 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
349 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350
Gilles Debunne26b62d42012-04-26 18:46:19 -0700351 final int endFlag = (mSpanFlags[i] & END_MASK);
352 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
353 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 }
355 }
Gilles Debunne0249b432012-04-09 16:02:31 -0700356
Gilles Debunne174c44c2012-04-10 16:01:09 -0700357 mSpanCountBeforeAdd = mSpanCount;
358
359 if (cs instanceof Spanned) {
360 Spanned sp = (Spanned) cs;
361 Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
Gilles Debunne0249b432012-04-09 16:02:31 -0700362
363 for (int i = 0; i < spans.length; i++) {
364 int st = sp.getSpanStart(spans[i]);
365 int en = sp.getSpanEnd(spans[i]);
366
Gilles Debunne174c44c2012-04-10 16:01:09 -0700367 if (st < csStart) st = csStart;
368 if (en > csEnd) en = csEnd;
Gilles Debunne0249b432012-04-09 16:02:31 -0700369
370 // Add span only if this object is not yet used as a span in this string
Gilles Debunne90985282012-04-17 17:08:04 -0700371 if (getSpanStart(spans[i]) < 0) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700372 setSpan(false, spans[i], st - csStart + start, en - csStart + start,
373 sp.getSpanFlags(spans[i]));
Gilles Debunne0249b432012-04-09 16:02:31 -0700374 }
375 }
376 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 }
378
Gilles Debunne26b62d42012-04-26 18:46:19 -0700379 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
380 boolean textIsRemoved) {
381 if (offset >= start && offset < mGapStart + mGapLength) {
382 if (flag == POINT) {
383 // A POINT located inside the replaced range should be moved to the end of the
384 // replaced text.
385 // The exception is when the point is at the start of the range and we are doing a
386 // text replacement (as opposed to a deletion): the point stays there.
387 if (textIsRemoved || offset > start) {
388 return mGapStart + mGapLength;
389 }
390 } else {
391 if (flag == PARAGRAPH) {
392 if (atEnd) {
393 return mGapStart + mGapLength;
394 }
395 } else { // MARK
Gilles Debunnee2448682012-05-04 15:29:09 -0700396 // MARKs should be moved to the start, with the exception of a mark located at
397 // the end of the range (which will be < mGapStart + mGapLength since mGapLength
398 // is > 0, which should stay 'unchanged' at the end of the replaced text.
Gilles Debunne26b62d42012-04-26 18:46:19 -0700399 if (textIsRemoved || offset < mGapStart - nbNewChars) {
400 return start;
401 } else {
402 // Move to the end of replaced text (needed if nbNewChars != 0)
403 return mGapStart;
404 }
405 }
406 }
407 }
408 return offset;
409 }
410
Gilles Debunne75beb332011-04-29 11:40:22 -0700411 private void removeSpan(int i) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700412 Object object = mSpans[i];
413
414 int start = mSpanStarts[i];
415 int end = mSpanEnds[i];
416
417 if (start > mGapStart) start -= mGapLength;
418 if (end > mGapStart) end -= mGapLength;
419
420 int count = mSpanCount - (i + 1);
421 System.arraycopy(mSpans, i + 1, mSpans, i, count);
422 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
423 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
424 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
Gilles Debunne75beb332011-04-29 11:40:22 -0700425
426 mSpanCount--;
Gilles Debunne6435a562011-08-04 21:22:30 -0700427
428 mSpans[mSpanCount] = null;
429
430 sendSpanRemoved(object, start, end);
Gilles Debunne75beb332011-04-29 11:40:22 -0700431 }
432
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 // Documentation from interface
434 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
435 return replace(start, end, tb, 0, tb.length());
436 }
437
438 // Documentation from interface
439 public SpannableStringBuilder replace(final int start, final int end,
Gilles Debunne0249b432012-04-09 16:02:31 -0700440 CharSequence tb, int tbstart, int tbend) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700441 checkRange("replace", start, end);
442
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 int filtercount = mFilters.length;
444 for (int i = 0; i < filtercount; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700445 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446
447 if (repl != null) {
448 tb = repl;
449 tbstart = 0;
450 tbend = repl.length();
451 }
452 }
453
Gilles Debunneb51036f2012-04-02 11:27:50 -0700454 final int origLen = end - start;
455 final int newLen = tbend - tbstart;
456
Gilles Debunned60da052012-04-18 15:12:20 -0700457 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
458 // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
459 // Early exit so that the text watchers do not get notified
460 return this;
461 }
462
Gilles Debunneb51036f2012-04-02 11:27:50 -0700463 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
464 sendBeforeTextChanged(textWatchers, start, origLen, newLen);
465
Gilles Debunne0249b432012-04-09 16:02:31 -0700466 // Try to keep the cursor / selection at the same relative position during
467 // a text replacement. If replaced or replacement text length is zero, this
468 // is already taken care of.
469 boolean adjustSelection = origLen != 0 && newLen != 0;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700470 int selectionStart = 0;
471 int selectionEnd = 0;
Gilles Debunne0249b432012-04-09 16:02:31 -0700472 if (adjustSelection) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700473 selectionStart = Selection.getSelectionStart(this);
474 selectionEnd = Selection.getSelectionEnd(this);
Gilles Debunne0249b432012-04-09 16:02:31 -0700475 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476
Gilles Debunne0249b432012-04-09 16:02:31 -0700477 change(start, end, tb, tbstart, tbend);
Gilles Debunne6435a562011-08-04 21:22:30 -0700478
Gilles Debunne0249b432012-04-09 16:02:31 -0700479 if (adjustSelection) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700480 if (selectionStart > start && selectionStart < end) {
481 final int offset = (selectionStart - start) * newLen / origLen;
482 selectionStart = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483
Gilles Debunne174c44c2012-04-10 16:01:09 -0700484 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 Spanned.SPAN_POINT_POINT);
486 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700487 if (selectionEnd > start && selectionEnd < end) {
488 final int offset = (selectionEnd - start) * newLen / origLen;
489 selectionEnd = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490
Gilles Debunne174c44c2012-04-10 16:01:09 -0700491 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
492 Spanned.SPAN_POINT_POINT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700495
Gilles Debunneb51036f2012-04-02 11:27:50 -0700496 sendTextChanged(textWatchers, start, origLen, newLen);
497 sendAfterTextChanged(textWatchers);
498
Gilles Debunne174c44c2012-04-10 16:01:09 -0700499 // Span watchers need to be called after text watchers, which may update the layout
500 sendToSpanWatchers(start, end, newLen - origLen);
501
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 return this;
503 }
504
Gilles Debunned60da052012-04-18 15:12:20 -0700505 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
506 if (text instanceof Spanned) {
507 Spanned spanned = (Spanned) text;
508 Object[] spans = spanned.getSpans(offset, offset, Object.class);
509 final int length = spans.length;
510 for (int i = 0; i < length; i++) {
511 Object span = spans[i];
512 int flags = spanned.getSpanFlags(span);
513 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
514 }
515 }
516 return false;
517 }
518
Gilles Debunne174c44c2012-04-10 16:01:09 -0700519 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
520 for (int i = 0; i < mSpanCountBeforeAdd; i++) {
521 int spanStart = mSpanStarts[i];
522 int spanEnd = mSpanEnds[i];
523 if (spanStart > mGapStart) spanStart -= mGapLength;
524 if (spanEnd > mGapStart) spanEnd -= mGapLength;
525 int spanFlags = mSpanFlags[i];
526
527 int newReplaceEnd = replaceEnd + nbNewChars;
528 boolean spanChanged = false;
Gilles Debunne90985282012-04-17 17:08:04 -0700529
Gilles Debunne174c44c2012-04-10 16:01:09 -0700530 int previousSpanStart = spanStart;
531 if (spanStart > newReplaceEnd) {
532 if (nbNewChars != 0) {
533 previousSpanStart -= nbNewChars;
534 spanChanged = true;
535 }
536 } else if (spanStart >= replaceStart) {
537 // No change if span start was already at replace interval boundaries before replace
538 if ((spanStart != replaceStart ||
539 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
540 (spanStart != newReplaceEnd ||
541 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
Gilles Debunne90985282012-04-17 17:08:04 -0700542 // TODO A correct previousSpanStart cannot be computed at this point.
543 // It would require to save all the previous spans' positions before the replace
544 // Using an invalid -1 value to convey this would break the broacast range
Gilles Debunne174c44c2012-04-10 16:01:09 -0700545 spanChanged = true;
546 }
547 }
Gilles Debunne90985282012-04-17 17:08:04 -0700548
Gilles Debunne174c44c2012-04-10 16:01:09 -0700549 int previousSpanEnd = spanEnd;
550 if (spanEnd > newReplaceEnd) {
551 if (nbNewChars != 0) {
552 previousSpanEnd -= nbNewChars;
553 spanChanged = true;
554 }
555 } else if (spanEnd >= replaceStart) {
556 // No change if span start was already at replace interval boundaries before replace
557 if ((spanEnd != replaceStart ||
558 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
559 (spanEnd != newReplaceEnd ||
560 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
561 // TODO same as above for previousSpanEnd
562 spanChanged = true;
563 }
564 }
565
566 if (spanChanged) {
567 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
568 }
569 mSpanFlags[i] &= ~SPAN_START_END_MASK;
570 }
571
572 // The spans starting at mIntermediateSpanCount were added from the replacement text
573 for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
574 int spanStart = mSpanStarts[i];
575 int spanEnd = mSpanEnds[i];
576 if (spanStart > mGapStart) spanStart -= mGapLength;
577 if (spanEnd > mGapStart) spanEnd -= mGapLength;
578 sendSpanAdded(mSpans[i], spanStart, spanEnd);
579 }
580 }
581
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 /**
583 * Mark the specified range of text with the specified object.
584 * The flags determine how the span will behave when text is
585 * inserted at the start or end of the span's range.
586 */
587 public void setSpan(Object what, int start, int end, int flags) {
588 setSpan(true, what, start, end, flags);
589 }
590
Gilles Debunne6435a562011-08-04 21:22:30 -0700591 private void setSpan(boolean send, Object what, int start, int end, int flags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 checkRange("setSpan", start, end);
593
Gilles Debunne0249b432012-04-09 16:02:31 -0700594 int flagsStart = (flags & START_MASK) >> START_SHIFT;
595 if (flagsStart == PARAGRAPH) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 if (start != 0 && start != length()) {
597 char c = charAt(start - 1);
598
599 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700600 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 }
602 }
603
Gilles Debunne0249b432012-04-09 16:02:31 -0700604 int flagsEnd = flags & END_MASK;
605 if (flagsEnd == PARAGRAPH) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 if (end != 0 && end != length()) {
607 char c = charAt(end - 1);
608
609 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700610 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 }
612 }
613
Gilles Debunne0249b432012-04-09 16:02:31 -0700614 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
615 if (flagsStart == POINT && flagsEnd == MARK && start == end) {
Gilles Debunne34ec2b52012-04-10 13:25:33 -0700616 if (send) Log.e("SpannableStringBuilder",
617 "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
618 // Silently ignore invalid spans when they are created from this class.
619 // This avoids the duplication of the above test code before all the
620 // calls to setSpan that are done in this class
621 return;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700622 }
623
Gilles Debunne0249b432012-04-09 16:02:31 -0700624 int nstart = start;
625 int nend = end;
626
Gilles Debunne6435a562011-08-04 21:22:30 -0700627 if (start > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 start += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700629 } else if (start == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700630 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 start += mGapLength;
632 }
633
Gilles Debunne6435a562011-08-04 21:22:30 -0700634 if (end > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 end += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700636 } else if (end == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700637 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 end += mGapLength;
639 }
640
641 int count = mSpanCount;
642 Object[] spans = mSpans;
643
644 for (int i = 0; i < count; i++) {
645 if (spans[i] == what) {
646 int ostart = mSpanStarts[i];
647 int oend = mSpanEnds[i];
648
649 if (ostart > mGapStart)
650 ostart -= mGapLength;
651 if (oend > mGapStart)
652 oend -= mGapLength;
653
654 mSpanStarts[i] = start;
655 mSpanEnds[i] = end;
656 mSpanFlags[i] = flags;
657
Gilles Debunnefc1190b2012-03-30 13:41:31 -0700658 if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800659
660 return;
661 }
662 }
663
664 if (mSpanCount + 1 >= mSpans.length) {
665 int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
666 Object[] newspans = new Object[newsize];
667 int[] newspanstarts = new int[newsize];
668 int[] newspanends = new int[newsize];
669 int[] newspanflags = new int[newsize];
670
671 System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
672 System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
673 System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
674 System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
675
676 mSpans = newspans;
677 mSpanStarts = newspanstarts;
678 mSpanEnds = newspanends;
679 mSpanFlags = newspanflags;
680 }
681
682 mSpans[mSpanCount] = what;
683 mSpanStarts[mSpanCount] = start;
684 mSpanEnds[mSpanCount] = end;
685 mSpanFlags[mSpanCount] = flags;
686 mSpanCount++;
687
Gilles Debunnefc1190b2012-03-30 13:41:31 -0700688 if (send) sendSpanAdded(what, nstart, nend);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 }
690
691 /**
692 * Remove the specified markup object from the buffer.
693 */
694 public void removeSpan(Object what) {
695 for (int i = mSpanCount - 1; i >= 0; i--) {
696 if (mSpans[i] == what) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700697 removeSpan(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 return;
699 }
700 }
701 }
702
703 /**
704 * Return the buffer offset of the beginning of the specified
705 * markup object, or -1 if it is not attached to this buffer.
706 */
707 public int getSpanStart(Object what) {
708 int count = mSpanCount;
709 Object[] spans = mSpans;
710
711 for (int i = count - 1; i >= 0; i--) {
712 if (spans[i] == what) {
713 int where = mSpanStarts[i];
714
715 if (where > mGapStart)
716 where -= mGapLength;
717
718 return where;
719 }
720 }
721
722 return -1;
723 }
724
725 /**
726 * Return the buffer offset of the end of the specified
727 * markup object, or -1 if it is not attached to this buffer.
728 */
729 public int getSpanEnd(Object what) {
730 int count = mSpanCount;
731 Object[] spans = mSpans;
732
733 for (int i = count - 1; i >= 0; i--) {
734 if (spans[i] == what) {
735 int where = mSpanEnds[i];
736
737 if (where > mGapStart)
738 where -= mGapLength;
739
740 return where;
741 }
742 }
743
744 return -1;
745 }
746
747 /**
748 * Return the flags of the end of the specified
749 * markup object, or 0 if it is not attached to this buffer.
750 */
751 public int getSpanFlags(Object what) {
752 int count = mSpanCount;
753 Object[] spans = mSpans;
754
755 for (int i = count - 1; i >= 0; i--) {
756 if (spans[i] == what) {
757 return mSpanFlags[i];
758 }
759 }
760
761 return 0;
762 }
763
764 /**
765 * Return an array of the spans of the specified type that overlap
766 * the specified range of the buffer. The kind may be Object.class to get
767 * a list of all the spans regardless of type.
768 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700769 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700771 if (kind == null) return ArrayUtils.emptyArray(kind);
772
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 int spanCount = mSpanCount;
774 Object[] spans = mSpans;
775 int[] starts = mSpanStarts;
776 int[] ends = mSpanEnds;
777 int[] flags = mSpanFlags;
778 int gapstart = mGapStart;
779 int gaplen = mGapLength;
780
781 int count = 0;
Gilles Debunne312cd582010-07-02 17:02:34 -0700782 T[] ret = null;
783 T ret1 = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784
785 for (int i = 0; i < spanCount; i++) {
786 int spanStart = starts[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 if (spanStart > gapstart) {
788 spanStart -= gaplen;
789 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 if (spanStart > queryEnd) {
791 continue;
792 }
Gilles Debunneb062e812011-09-27 14:58:37 -0700793
794 int spanEnd = ends[i];
795 if (spanEnd > gapstart) {
796 spanEnd -= gaplen;
797 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 if (spanEnd < queryStart) {
799 continue;
800 }
801
802 if (spanStart != spanEnd && queryStart != queryEnd) {
803 if (spanStart == queryEnd)
804 continue;
805 if (spanEnd == queryStart)
806 continue;
807 }
808
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700809 // Expensive test, should be performed after the previous tests
810 if (!kind.isInstance(spans[i])) continue;
811
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 if (count == 0) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700813 // Safe conversion thanks to the isInstance test above
814 ret1 = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 count++;
816 } else {
817 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700818 // Safe conversion, but requires a suppressWarning
819 ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820 ret[0] = ret1;
821 }
822
823 int prio = flags[i] & SPAN_PRIORITY;
824 if (prio != 0) {
825 int j;
826
827 for (j = 0; j < count; j++) {
828 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
829
830 if (prio > p) {
831 break;
832 }
833 }
834
835 System.arraycopy(ret, j, ret, j + 1, count - j);
Gilles Debunne312cd582010-07-02 17:02:34 -0700836 // Safe conversion thanks to the isInstance test above
837 ret[j] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 count++;
839 } else {
Gilles Debunne312cd582010-07-02 17:02:34 -0700840 // Safe conversion thanks to the isInstance test above
841 ret[count++] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 }
843 }
844 }
845
846 if (count == 0) {
Doug Feltf47d7402010-04-21 16:01:52 -0700847 return ArrayUtils.emptyArray(kind);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848 }
849 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700850 // Safe conversion, but requires a suppressWarning
851 ret = (T[]) Array.newInstance(kind, 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800852 ret[0] = ret1;
Gilles Debunne312cd582010-07-02 17:02:34 -0700853 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 }
855 if (count == ret.length) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700856 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 }
858
Gilles Debunne312cd582010-07-02 17:02:34 -0700859 // Safe conversion, but requires a suppressWarning
860 T[] nret = (T[]) Array.newInstance(kind, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 System.arraycopy(ret, 0, nret, 0, count);
Gilles Debunne312cd582010-07-02 17:02:34 -0700862 return nret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 }
864
865 /**
866 * Return the next offset after <code>start</code> but less than or
867 * equal to <code>limit</code> where a span of the specified type
868 * begins or ends.
869 */
870 public int nextSpanTransition(int start, int limit, Class kind) {
871 int count = mSpanCount;
872 Object[] spans = mSpans;
873 int[] starts = mSpanStarts;
874 int[] ends = mSpanEnds;
875 int gapstart = mGapStart;
876 int gaplen = mGapLength;
877
878 if (kind == null) {
879 kind = Object.class;
880 }
881
882 for (int i = 0; i < count; i++) {
883 int st = starts[i];
884 int en = ends[i];
885
886 if (st > gapstart)
887 st -= gaplen;
888 if (en > gapstart)
889 en -= gaplen;
890
891 if (st > start && st < limit && kind.isInstance(spans[i]))
892 limit = st;
893 if (en > start && en < limit && kind.isInstance(spans[i]))
894 limit = en;
895 }
896
897 return limit;
898 }
899
900 /**
901 * Return a new CharSequence containing a copy of the specified
902 * range of this buffer, including the overlapping spans.
903 */
904 public CharSequence subSequence(int start, int end) {
905 return new SpannableStringBuilder(this, start, end);
906 }
907
908 /**
909 * Copy the specified range of chars from this buffer into the
910 * specified array, beginning at the specified offset.
911 */
912 public void getChars(int start, int end, char[] dest, int destoff) {
913 checkRange("getChars", start, end);
914
915 if (end <= mGapStart) {
916 System.arraycopy(mText, start, dest, destoff, end - start);
917 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700918 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 } else {
920 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
921 System.arraycopy(mText, mGapStart + mGapLength,
Gilles Debunne174c44c2012-04-10 16:01:09 -0700922 dest, destoff + (mGapStart - start),
923 end - mGapStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 }
925 }
926
927 /**
928 * Return a String containing a copy of the chars in this buffer.
929 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700930 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 public String toString() {
932 int len = length();
933 char[] buf = new char[len];
934
935 getChars(0, len, buf, 0);
936 return new String(buf);
937 }
938
Gilles Debunne653d3a22011-12-07 10:35:59 -0800939 /**
940 * Return a String containing a copy of the chars in this buffer, limited to the
941 * [start, end[ range.
942 * @hide
943 */
944 public String substring(int start, int end) {
945 char[] buf = new char[end - start];
946 getChars(start, end, buf, 0);
947 return new String(buf);
948 }
949
Gilles Debunneb51036f2012-04-02 11:27:50 -0700950 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
951 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952
953 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700954 watchers[i].beforeTextChanged(this, start, before, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 }
956 }
957
Gilles Debunneb51036f2012-04-02 11:27:50 -0700958 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
959 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800960
961 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700962 watchers[i].onTextChanged(this, start, before, after);
963 }
964 }
965
966 private void sendAfterTextChanged(TextWatcher[] watchers) {
967 int n = watchers.length;
968
969 for (int i = 0; i < n; i++) {
970 watchers[i].afterTextChanged(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 }
972 }
973
974 private void sendSpanAdded(Object what, int start, int end) {
975 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
976 int n = recip.length;
977
978 for (int i = 0; i < n; i++) {
979 recip[i].onSpanAdded(this, what, start, end);
980 }
981 }
982
983 private void sendSpanRemoved(Object what, int start, int end) {
984 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
985 int n = recip.length;
986
987 for (int i = 0; i < n; i++) {
988 recip[i].onSpanRemoved(this, what, start, end);
989 }
990 }
991
Gilles Debunne174c44c2012-04-10 16:01:09 -0700992 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
993 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
994 // called, so that the order of the span does not affect this broadcast.
995 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
996 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
997 int n = spanWatchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800998 for (int i = 0; i < n; i++) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700999 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000 }
1001 }
1002
1003 private static String region(int start, int end) {
1004 return "(" + start + " ... " + end + ")";
1005 }
1006
1007 private void checkRange(final String operation, int start, int end) {
1008 if (end < start) {
1009 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001010 region(start, end) + " has end before start");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 }
1012
1013 int len = length();
1014
1015 if (start > len || end > len) {
1016 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001017 region(start, end) + " ends beyond length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 }
1019
1020 if (start < 0 || end < 0) {
1021 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001022 region(start, end) + " starts before 0");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023 }
1024 }
1025
Gilles Debunne174c44c2012-04-10 16:01:09 -07001026 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 private boolean isprint(char c) { // XXX
1028 if (c >= ' ' && c <= '~')
1029 return true;
1030 else
1031 return false;
1032 }
1033
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 private static final int startFlag(int flag) {
1035 return (flag >> 4) & 0x0F;
1036 }
1037
1038 private static final int endFlag(int flag) {
1039 return flag & 0x0F;
1040 }
1041
1042 public void dump() { // XXX
1043 for (int i = 0; i < mGapStart; i++) {
1044 System.out.print('|');
1045 System.out.print(' ');
1046 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1047 System.out.print(' ');
1048 }
1049
1050 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1051 System.out.print('|');
1052 System.out.print('(');
1053 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1054 System.out.print(')');
1055 }
1056
1057 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1058 System.out.print('|');
1059 System.out.print(' ');
1060 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1061 System.out.print(' ');
1062 }
1063
1064 System.out.print('\n');
1065
1066 for (int i = 0; i < mText.length + 1; i++) {
1067 int found = 0;
1068 int wfound = 0;
1069
1070 for (int j = 0; j < mSpanCount; j++) {
1071 if (mSpanStarts[j] == i) {
1072 found = 1;
1073 wfound = j;
1074 break;
1075 }
1076
1077 if (mSpanEnds[j] == i) {
1078 found = 2;
1079 wfound = j;
1080 break;
1081 }
1082 }
1083
1084 if (found == 1) {
1085 if (startFlag(mSpanFlags[wfound]) == MARK)
1086 System.out.print("( ");
1087 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1088 System.out.print("< ");
1089 else
1090 System.out.print("[ ");
1091 } else if (found == 2) {
1092 if (endFlag(mSpanFlags[wfound]) == POINT)
1093 System.out.print(") ");
1094 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1095 System.out.print("> ");
1096 else
1097 System.out.print("] ");
1098 } else {
1099 System.out.print(" ");
1100 }
1101 }
1102
1103 System.out.print("\n");
1104 }
Gilles Debunne26b62d42012-04-26 18:46:19 -07001105 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106
1107 /**
1108 * Don't call this yourself -- exists for Canvas to use internally.
1109 * {@hide}
1110 */
Gilles Debunneb51036f2012-04-02 11:27:50 -07001111 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 -08001112 checkRange("drawText", start, end);
1113
1114 if (end <= mGapStart) {
1115 c.drawText(mText, start, end - start, x, y, p);
1116 } else if (start >= mGapStart) {
1117 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1118 } else {
1119 char[] buf = TextUtils.obtain(end - start);
1120
1121 getChars(start, end, buf, 0);
1122 c.drawText(buf, 0, end - start, x, y, p);
1123 TextUtils.recycle(buf);
1124 }
1125 }
1126
Doug Felt0c702b82010-05-14 10:55:42 -07001127
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001128 /**
Doug Feltf47d7402010-04-21 16:01:52 -07001129 * Don't call this yourself -- exists for Canvas to use internally.
1130 * {@hide}
1131 */
Gilles Debunneb51036f2012-04-02 11:27:50 -07001132 public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001133 float x, float y, Paint p) {
Doug Feltf47d7402010-04-21 16:01:52 -07001134 checkRange("drawTextRun", start, end);
1135
Doug Felt0c702b82010-05-14 10:55:42 -07001136 int contextLen = contextEnd - contextStart;
1137 int len = end - start;
1138 if (contextEnd <= mGapStart) {
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001139 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, p);
Doug Felt0c702b82010-05-14 10:55:42 -07001140 } else if (contextStart >= mGapStart) {
1141 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001142 contextLen, x, y, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001143 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001144 char[] buf = TextUtils.obtain(contextLen);
1145 getChars(contextStart, contextEnd, buf, 0);
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001146 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001147 TextUtils.recycle(buf);
1148 }
1149 }
1150
Gilles Debunne174c44c2012-04-10 16:01:09 -07001151 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 * Don't call this yourself -- exists for Paint to use internally.
1153 * {@hide}
1154 */
1155 public float measureText(int start, int end, Paint p) {
1156 checkRange("measureText", start, end);
1157
1158 float ret;
1159
1160 if (end <= mGapStart) {
1161 ret = p.measureText(mText, start, end - start);
1162 } else if (start >= mGapStart) {
1163 ret = p.measureText(mText, start + mGapLength, end - start);
1164 } else {
1165 char[] buf = TextUtils.obtain(end - start);
1166
1167 getChars(start, end, buf, 0);
1168 ret = p.measureText(buf, 0, end - start);
1169 TextUtils.recycle(buf);
1170 }
1171
1172 return ret;
1173 }
1174
1175 /**
1176 * Don't call this yourself -- exists for Paint to use internally.
1177 * {@hide}
1178 */
1179 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1180 checkRange("getTextWidths", start, end);
1181
1182 int ret;
1183
1184 if (end <= mGapStart) {
1185 ret = p.getTextWidths(mText, start, end - start, widths);
1186 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001187 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188 } else {
1189 char[] buf = TextUtils.obtain(end - start);
1190
1191 getChars(start, end, buf, 0);
1192 ret = p.getTextWidths(buf, 0, end - start, widths);
1193 TextUtils.recycle(buf);
1194 }
1195
1196 return ret;
1197 }
1198
Doug Felt0c702b82010-05-14 10:55:42 -07001199 /**
1200 * Don't call this yourself -- exists for Paint to use internally.
1201 * {@hide}
1202 */
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001203 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
Doug Felt0c702b82010-05-14 10:55:42 -07001204 float[] advances, int advancesPos, Paint p) {
1205
1206 float ret;
1207
1208 int contextLen = contextEnd - contextStart;
1209 int len = end - start;
1210
1211 if (end <= mGapStart) {
1212 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001213 advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001214 } else if (start >= mGapStart) {
1215 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001216 contextStart + mGapLength, contextLen, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001217 } else {
1218 char[] buf = TextUtils.obtain(contextLen);
1219 getChars(contextStart, contextEnd, buf, 0);
1220 ret = p.getTextRunAdvances(buf, start - contextStart, len,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001221 0, contextLen, advances, advancesPos);
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07001222 TextUtils.recycle(buf);
1223 }
1224
1225 return ret;
1226 }
1227
1228 /**
Gilles Debunnef09d5102011-03-02 17:28:31 -08001229 * Returns the next cursor position in the run. This avoids placing the cursor between
1230 * surrogates, between characters that form conjuncts, between base characters and combining
1231 * marks, or within a reordering cluster.
1232 *
1233 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1234 * span enclosing the cursor in the direction of movement.
1235 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1236 * the start of the string.</p>
1237 *
Gilles Debunne616f3832011-03-02 19:50:16 -08001238 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
Gilles Debunnef09d5102011-03-02 17:28:31 -08001239 * this returns -1. Otherwise this will never return a value before contextStart or after
1240 * contextEnd.</p>
1241 *
1242 * @param contextStart the start index of the context
1243 * @param contextEnd the (non-inclusive) end index of the context
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001244 * @param flags reserved
Gilles Debunnef09d5102011-03-02 17:28:31 -08001245 * @param offset the cursor position to move from
Gilles Debunne616f3832011-03-02 19:50:16 -08001246 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1247 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1248 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
Gilles Debunnef09d5102011-03-02 17:28:31 -08001249 * @param p the Paint object that is requesting this information
1250 * @return the offset of the next position, or -1
Gilles Debunneb0b22562011-03-03 14:51:39 -08001251 * @deprecated This is an internal method, refrain from using it in your code
Gilles Debunnef09d5102011-03-02 17:28:31 -08001252 */
Gilles Debunneb0b22562011-03-03 14:51:39 -08001253 @Deprecated
Doug Felt0c702b82010-05-14 10:55:42 -07001254 public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001255 int cursorOpt, Paint p) {
1256 return getTextRunCursor(contextStart, contextEnd, offset, cursorOpt, p);
1257 }
1258
1259 /**
1260 * @hide
1261 */
1262 public int getTextRunCursor(int contextStart, int contextEnd, int offset,
1263 int cursorOpt, Paint p) {
Doug Felt0c702b82010-05-14 10:55:42 -07001264
1265 int ret;
1266
1267 int contextLen = contextEnd - contextStart;
1268 if (contextEnd <= mGapStart) {
1269 ret = p.getTextRunCursor(mText, contextStart, contextLen,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001270 offset, cursorOpt);
Doug Felt0c702b82010-05-14 10:55:42 -07001271 } else if (contextStart >= mGapStart) {
1272 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001273 offset + mGapLength, cursorOpt) - mGapLength;
Doug Felt0c702b82010-05-14 10:55:42 -07001274 } else {
1275 char[] buf = TextUtils.obtain(contextLen);
1276 getChars(contextStart, contextEnd, buf, 0);
1277 ret = p.getTextRunCursor(buf, 0, contextLen,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08001278 offset - contextStart, cursorOpt) + contextStart;
Doug Felt0c702b82010-05-14 10:55:42 -07001279 TextUtils.recycle(buf);
1280 }
1281
1282 return ret;
1283 }
1284
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 // Documentation from interface
1286 public void setFilters(InputFilter[] filters) {
1287 if (filters == null) {
1288 throw new IllegalArgumentException();
1289 }
1290
1291 mFilters = filters;
1292 }
1293
1294 // Documentation from interface
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001295 public InputFilter[] getFilters() {
1296 return mFilters;
1297 }
1298
1299 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1300 private InputFilter[] mFilters = NO_FILTERS;
1301
1302 private char[] mText;
1303 private int mGapStart;
1304 private int mGapLength;
1305
1306 private Object[] mSpans;
1307 private int[] mSpanStarts;
1308 private int[] mSpanEnds;
1309 private int[] mSpanFlags;
1310 private int mSpanCount;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001311 private int mSpanCountBeforeAdd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312
Gilles Debunneb51036f2012-04-02 11:27:50 -07001313 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
Gilles Debunne0249b432012-04-09 16:02:31 -07001314 private static final int MARK = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 private static final int POINT = 2;
1316 private static final int PARAGRAPH = 3;
1317
1318 private static final int START_MASK = 0xF0;
1319 private static final int END_MASK = 0x0F;
1320 private static final int START_SHIFT = 4;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001321
1322 // These bits are not (currently) used by SPANNED flags
1323 private static final int SPAN_START_AT_START = 0x1000;
1324 private static final int SPAN_START_AT_END = 0x2000;
1325 private static final int SPAN_END_AT_START = 0x4000;
1326 private static final int SPAN_END_AT_END = 0x8000;
1327 private static final int SPAN_START_END_MASK = 0xF000;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001328}