blob: b70875060b304e1b3dc46644b255919241a7c82d [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 Debunne6435a562011-08-04 21:22:30 -070021
22import com.android.internal.util.ArrayUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
24import java.lang.reflect.Array;
25
26/**
27 * This is the class for text whose content and markup can both be changed.
28 */
Gilles Debunne6435a562011-08-04 21:22:30 -070029public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
30 Appendable, GraphicsOperations {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031 /**
32 * Create a new SpannableStringBuilder with empty contents
33 */
34 public SpannableStringBuilder() {
35 this("");
36 }
37
38 /**
39 * Create a new SpannableStringBuilder containing a copy of the
40 * specified text, including its spans if any.
41 */
42 public SpannableStringBuilder(CharSequence text) {
43 this(text, 0, text.length());
44 }
45
46 /**
47 * Create a new SpannableStringBuilder containing a copy of the
48 * specified slice of the specified text, including its spans if any.
49 */
50 public SpannableStringBuilder(CharSequence text, int start, int end) {
51 int srclen = end - start;
52
53 int len = ArrayUtils.idealCharArraySize(srclen + 1);
54 mText = new char[len];
55 mGapStart = srclen;
56 mGapLength = len - srclen;
57
58 TextUtils.getChars(text, start, end, mText, 0);
59
60 mSpanCount = 0;
61 int alloc = ArrayUtils.idealIntArraySize(0);
62 mSpans = new Object[alloc];
63 mSpanStarts = new int[alloc];
64 mSpanEnds = new int[alloc];
65 mSpanFlags = new int[alloc];
66
67 if (text instanceof Spanned) {
68 Spanned sp = (Spanned) text;
69 Object[] spans = sp.getSpans(start, end, Object.class);
70
71 for (int i = 0; i < spans.length; i++) {
72 if (spans[i] instanceof NoCopySpan) {
73 continue;
74 }
75
76 int st = sp.getSpanStart(spans[i]) - start;
77 int en = sp.getSpanEnd(spans[i]) - start;
78 int fl = sp.getSpanFlags(spans[i]);
79
80 if (st < 0)
81 st = 0;
82 if (st > end - start)
83 st = end - start;
84
85 if (en < 0)
86 en = 0;
87 if (en > end - start)
88 en = end - start;
89
90 setSpan(spans[i], st, en, fl);
91 }
92 }
93 }
94
95 public static SpannableStringBuilder valueOf(CharSequence source) {
96 if (source instanceof SpannableStringBuilder) {
97 return (SpannableStringBuilder) source;
98 } else {
99 return new SpannableStringBuilder(source);
100 }
101 }
102
103 /**
104 * Return the char at the specified offset within the buffer.
105 */
106 public char charAt(int where) {
107 int len = length();
108 if (where < 0) {
109 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
110 } else if (where >= len) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700111 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 }
113
114 if (where >= mGapStart)
115 return mText[where + mGapLength];
116 else
117 return mText[where];
118 }
119
120 /**
121 * Return the number of chars in the buffer.
122 */
123 public int length() {
124 return mText.length - mGapLength;
125 }
126
127 private void resizeFor(int size) {
128 int newlen = ArrayUtils.idealCharArraySize(size + 1);
129 char[] newtext = new char[newlen];
130
131 int after = mText.length - (mGapStart + mGapLength);
132
133 System.arraycopy(mText, 0, newtext, 0, mGapStart);
134 System.arraycopy(mText, mText.length - after,
135 newtext, newlen - after, after);
136
137 for (int i = 0; i < mSpanCount; i++) {
138 if (mSpanStarts[i] > mGapStart)
139 mSpanStarts[i] += newlen - mText.length;
140 if (mSpanEnds[i] > mGapStart)
141 mSpanEnds[i] += newlen - mText.length;
142 }
143
144 int oldlen = mText.length;
145 mText = newtext;
146 mGapLength += mText.length - oldlen;
147
148 if (mGapLength < 1)
149 new Exception("mGapLength < 1").printStackTrace();
150 }
151
152 private void moveGapTo(int where) {
153 if (where == mGapStart)
154 return;
155
156 boolean atend = (where == length());
157
158 if (where < mGapStart) {
159 int overlap = mGapStart - where;
160
161 System.arraycopy(mText, where,
162 mText, mGapStart + mGapLength - overlap, overlap);
163 } else /* where > mGapStart */ {
164 int overlap = where - mGapStart;
165
166 System.arraycopy(mText, where + mGapLength - overlap,
167 mText, mGapStart, overlap);
168 }
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
182 if (flag == POINT || (atend && flag == PARAGRAPH))
183 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
193 if (flag == POINT || (atend && flag == PARAGRAPH))
194 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());
220
221 return ret; // == this
222 }
223
224 // Documentation from interface
225 public void clear() {
226 replace(0, length(), "", 0, 0);
227 }
228
229 // 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 Debunne6435a562011-08-04 21:22:30 -0700265 private int change(int start, int end, CharSequence tb, int tbstart, int tbend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 return change(true, start, end, tb, tbstart, tbend);
267 }
268
269 private int change(boolean notify, int start, int end,
270 CharSequence tb, int tbstart, int tbend) {
271 checkRange("replace", start, end);
272 int ret = tbend - tbstart;
273 TextWatcher[] recipients = null;
274
Gilles Debunne6435a562011-08-04 21:22:30 -0700275 if (notify) {
Gilles Debunne75beb332011-04-29 11:40:22 -0700276 recipients = sendTextWillChange(start, end - start, tbend - tbstart);
Gilles Debunne6435a562011-08-04 21:22:30 -0700277 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278
279 for (int i = mSpanCount - 1; i >= 0; i--) {
280 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
281 int st = mSpanStarts[i];
282 if (st > mGapStart)
283 st -= mGapLength;
284
285 int en = mSpanEnds[i];
286 if (en > mGapStart)
287 en -= mGapLength;
288
289 int ost = st;
290 int oen = en;
291 int clen = length();
292
293 if (st > start && st <= end) {
294 for (st = end; st < clen; st++)
295 if (st > end && charAt(st - 1) == '\n')
296 break;
297 }
298
299 if (en > start && en <= end) {
300 for (en = end; en < clen; en++)
301 if (en > end && charAt(en - 1) == '\n')
302 break;
303 }
304
305 if (st != ost || en != oen)
306 setSpan(mSpans[i], st, en, mSpanFlags[i]);
307 }
308 }
309
310 moveGapTo(end);
311
Gilles Debunne312cd582010-07-02 17:02:34 -0700312 // Can be negative
313 final int nbNewChars = (tbend - tbstart) - (end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314
Gilles Debunne312cd582010-07-02 17:02:34 -0700315 if (nbNewChars >= mGapLength) {
316 resizeFor(mText.length + nbNewChars - mGapLength);
317 }
318
319 mGapStart += nbNewChars;
320 mGapLength -= nbNewChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321
322 if (mGapLength < 1)
323 new Exception("mGapLength < 1").printStackTrace();
324
325 TextUtils.getChars(tb, tbstart, tbend, mText, start);
326
327 if (tb instanceof Spanned) {
328 Spanned sp = (Spanned) tb;
329 Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
330
331 for (int i = 0; i < spans.length; i++) {
332 int st = sp.getSpanStart(spans[i]);
333 int en = sp.getSpanEnd(spans[i]);
334
335 if (st < tbstart)
336 st = tbstart;
337 if (en > tbend)
338 en = tbend;
339
340 if (getSpanStart(spans[i]) < 0) {
341 setSpan(false, spans[i],
342 st - tbstart + start,
343 en - tbstart + start,
344 sp.getSpanFlags(spans[i]));
345 }
346 }
347 }
348
349 // no need for span fixup on pure insertion
350 if (tbend > tbstart && end - start == 0) {
351 if (notify) {
352 sendTextChange(recipients, start, end - start, tbend - tbstart);
353 sendTextHasChanged(recipients);
354 }
355
356 return ret;
357 }
358
359 boolean atend = (mGapStart + mGapLength == mText.length);
360
361 for (int i = mSpanCount - 1; i >= 0; i--) {
362 if (mSpanStarts[i] >= start &&
363 mSpanStarts[i] < mGapStart + mGapLength) {
364 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
365
366 if (flag == POINT || (flag == PARAGRAPH && atend))
367 mSpanStarts[i] = mGapStart + mGapLength;
368 else
369 mSpanStarts[i] = start;
370 }
371
372 if (mSpanEnds[i] >= start &&
373 mSpanEnds[i] < mGapStart + mGapLength) {
374 int flag = (mSpanFlags[i] & END_MASK);
375
376 if (flag == POINT || (flag == PARAGRAPH && atend))
377 mSpanEnds[i] = mGapStart + mGapLength;
378 else
379 mSpanEnds[i] = start;
380 }
381
382 // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 if (mSpanEnds[i] < mSpanStarts[i]) {
Gilles Debunne75beb332011-04-29 11:40:22 -0700384 removeSpan(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 }
386 }
387
388 if (notify) {
389 sendTextChange(recipients, start, end - start, tbend - tbstart);
390 sendTextHasChanged(recipients);
391 }
392
393 return ret;
394 }
395
Gilles Debunne75beb332011-04-29 11:40:22 -0700396 private void removeSpan(int i) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700397 Object object = mSpans[i];
398
399 int start = mSpanStarts[i];
400 int end = mSpanEnds[i];
401
402 if (start > mGapStart) start -= mGapLength;
403 if (end > mGapStart) end -= mGapLength;
404
405 int count = mSpanCount - (i + 1);
406 System.arraycopy(mSpans, i + 1, mSpans, i, count);
407 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
408 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
409 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
Gilles Debunne75beb332011-04-29 11:40:22 -0700410
411 mSpanCount--;
Gilles Debunne6435a562011-08-04 21:22:30 -0700412
413 mSpans[mSpanCount] = null;
414
415 sendSpanRemoved(object, start, end);
Gilles Debunne75beb332011-04-29 11:40:22 -0700416 }
417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 // Documentation from interface
419 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
420 return replace(start, end, tb, 0, tb.length());
421 }
422
423 // Documentation from interface
424 public SpannableStringBuilder replace(final int start, final int end,
425 CharSequence tb, int tbstart, int tbend) {
426 int filtercount = mFilters.length;
427 for (int i = 0; i < filtercount; i++) {
428 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend,
429 this, start, end);
430
431 if (repl != null) {
432 tb = repl;
433 tbstart = 0;
434 tbend = repl.length();
435 }
436 }
437
438 if (end == start && tbstart == tbend) {
439 return this;
440 }
441
442 if (end == start || tbstart == tbend) {
443 change(start, end, tb, tbstart, tbend);
444 } else {
445 int selstart = Selection.getSelectionStart(this);
446 int selend = Selection.getSelectionEnd(this);
447
448 // XXX just make the span fixups in change() do the right thing
449 // instead of this madness!
450
451 checkRange("replace", start, end);
452 moveGapTo(end);
453 TextWatcher[] recipients;
454
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 int origlen = end - start;
456
Gilles Debunne6435a562011-08-04 21:22:30 -0700457 recipients = sendTextWillChange(start, origlen, tbend - tbstart);
458
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 if (mGapLength < 2)
460 resizeFor(length() + 1);
461
462 for (int i = mSpanCount - 1; i >= 0; i--) {
463 if (mSpanStarts[i] == mGapStart)
464 mSpanStarts[i]++;
465
466 if (mSpanEnds[i] == mGapStart)
467 mSpanEnds[i]++;
468 }
469
470 mText[mGapStart] = ' ';
471 mGapStart++;
472 mGapLength--;
473
Gilles Debunne75beb332011-04-29 11:40:22 -0700474 if (mGapLength < 1) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 new Exception("mGapLength < 1").printStackTrace();
Gilles Debunne75beb332011-04-29 11:40:22 -0700476 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477
Gilles Debunne75beb332011-04-29 11:40:22 -0700478 int inserted = change(false, start + 1, start + 1, tb, tbstart, tbend);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 change(false, start, start + 1, "", 0, 0);
Gilles Debunne6435a562011-08-04 21:22:30 -0700480 change(false, start + inserted, start + inserted + origlen, "", 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481
482 /*
483 * Special case to keep the cursor in the same position
484 * if it was somewhere in the middle of the replaced region.
485 * If it was at the start or the end or crossing the whole
486 * replacement, it should already be where it belongs.
487 * TODO: Is there some more general mechanism that could
488 * accomplish this?
489 */
490 if (selstart > start && selstart < end) {
491 long off = selstart - start;
492
493 off = off * inserted / (end - start);
494 selstart = (int) off + start;
495
496 setSpan(false, Selection.SELECTION_START, selstart, selstart,
497 Spanned.SPAN_POINT_POINT);
498 }
499 if (selend > start && selend < end) {
500 long off = selend - start;
501
502 off = off * inserted / (end - start);
503 selend = (int) off + start;
504
Gilles Debunne6435a562011-08-04 21:22:30 -0700505 setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 sendTextChange(recipients, start, origlen, inserted);
508 sendTextHasChanged(recipients);
509 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700510
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 return this;
512 }
513
514 /**
515 * Mark the specified range of text with the specified object.
516 * The flags determine how the span will behave when text is
517 * inserted at the start or end of the span's range.
518 */
519 public void setSpan(Object what, int start, int end, int flags) {
520 setSpan(true, what, start, end, flags);
521 }
522
Gilles Debunne6435a562011-08-04 21:22:30 -0700523 private void setSpan(boolean send, Object what, int start, int end, int flags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 int nstart = start;
525 int nend = end;
526
527 checkRange("setSpan", start, end);
528
529 if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
530 if (start != 0 && start != length()) {
531 char c = charAt(start - 1);
532
533 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700534 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 }
536 }
537
538 if ((flags & END_MASK) == PARAGRAPH) {
539 if (end != 0 && end != length()) {
540 char c = charAt(end - 1);
541
542 if (c != '\n')
Gilles Debunne6435a562011-08-04 21:22:30 -0700543 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 }
545 }
546
Gilles Debunne6435a562011-08-04 21:22:30 -0700547 if (start > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 start += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700549 } else if (start == mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 int flag = (flags & START_MASK) >> START_SHIFT;
551
552 if (flag == POINT || (flag == PARAGRAPH && start == length()))
553 start += mGapLength;
554 }
555
Gilles Debunne6435a562011-08-04 21:22:30 -0700556 if (end > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 end += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700558 } else if (end == mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 int flag = (flags & END_MASK);
560
561 if (flag == POINT || (flag == PARAGRAPH && end == length()))
562 end += mGapLength;
563 }
564
565 int count = mSpanCount;
566 Object[] spans = mSpans;
567
568 for (int i = 0; i < count; i++) {
569 if (spans[i] == what) {
570 int ostart = mSpanStarts[i];
571 int oend = mSpanEnds[i];
572
573 if (ostart > mGapStart)
574 ostart -= mGapLength;
575 if (oend > mGapStart)
576 oend -= mGapLength;
577
578 mSpanStarts[i] = start;
579 mSpanEnds[i] = end;
580 mSpanFlags[i] = flags;
581
582 if (send)
583 sendSpanChanged(what, ostart, oend, nstart, nend);
584
585 return;
586 }
587 }
588
589 if (mSpanCount + 1 >= mSpans.length) {
590 int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
591 Object[] newspans = new Object[newsize];
592 int[] newspanstarts = new int[newsize];
593 int[] newspanends = new int[newsize];
594 int[] newspanflags = new int[newsize];
595
596 System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
597 System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
598 System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
599 System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
600
601 mSpans = newspans;
602 mSpanStarts = newspanstarts;
603 mSpanEnds = newspanends;
604 mSpanFlags = newspanflags;
605 }
606
607 mSpans[mSpanCount] = what;
608 mSpanStarts[mSpanCount] = start;
609 mSpanEnds[mSpanCount] = end;
610 mSpanFlags[mSpanCount] = flags;
611 mSpanCount++;
612
613 if (send)
614 sendSpanAdded(what, nstart, nend);
615 }
616
617 /**
618 * Remove the specified markup object from the buffer.
619 */
620 public void removeSpan(Object what) {
621 for (int i = mSpanCount - 1; i >= 0; i--) {
622 if (mSpans[i] == what) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700623 removeSpan(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 return;
625 }
626 }
627 }
628
629 /**
630 * Return the buffer offset of the beginning of the specified
631 * markup object, or -1 if it is not attached to this buffer.
632 */
633 public int getSpanStart(Object what) {
634 int count = mSpanCount;
635 Object[] spans = mSpans;
636
637 for (int i = count - 1; i >= 0; i--) {
638 if (spans[i] == what) {
639 int where = mSpanStarts[i];
640
641 if (where > mGapStart)
642 where -= mGapLength;
643
644 return where;
645 }
646 }
647
648 return -1;
649 }
650
651 /**
652 * Return the buffer offset of the end of the specified
653 * markup object, or -1 if it is not attached to this buffer.
654 */
655 public int getSpanEnd(Object what) {
656 int count = mSpanCount;
657 Object[] spans = mSpans;
658
659 for (int i = count - 1; i >= 0; i--) {
660 if (spans[i] == what) {
661 int where = mSpanEnds[i];
662
663 if (where > mGapStart)
664 where -= mGapLength;
665
666 return where;
667 }
668 }
669
670 return -1;
671 }
672
673 /**
674 * Return the flags of the end of the specified
675 * markup object, or 0 if it is not attached to this buffer.
676 */
677 public int getSpanFlags(Object what) {
678 int count = mSpanCount;
679 Object[] spans = mSpans;
680
681 for (int i = count - 1; i >= 0; i--) {
682 if (spans[i] == what) {
683 return mSpanFlags[i];
684 }
685 }
686
687 return 0;
688 }
689
690 /**
691 * Return an array of the spans of the specified type that overlap
692 * the specified range of the buffer. The kind may be Object.class to get
693 * a list of all the spans regardless of type.
694 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700695 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700697 if (kind == null) return ArrayUtils.emptyArray(kind);
698
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800699 int spanCount = mSpanCount;
700 Object[] spans = mSpans;
701 int[] starts = mSpanStarts;
702 int[] ends = mSpanEnds;
703 int[] flags = mSpanFlags;
704 int gapstart = mGapStart;
705 int gaplen = mGapLength;
706
707 int count = 0;
Gilles Debunne312cd582010-07-02 17:02:34 -0700708 T[] ret = null;
709 T ret1 = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710
711 for (int i = 0; i < spanCount; i++) {
712 int spanStart = starts[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 if (spanStart > gapstart) {
714 spanStart -= gaplen;
715 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 if (spanStart > queryEnd) {
717 continue;
718 }
Gilles Debunneb062e812011-09-27 14:58:37 -0700719
720 int spanEnd = ends[i];
721 if (spanEnd > gapstart) {
722 spanEnd -= gaplen;
723 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 if (spanEnd < queryStart) {
725 continue;
726 }
727
728 if (spanStart != spanEnd && queryStart != queryEnd) {
729 if (spanStart == queryEnd)
730 continue;
731 if (spanEnd == queryStart)
732 continue;
733 }
734
Gilles Debunne945ee9b2011-09-19 19:18:18 -0700735 // Expensive test, should be performed after the previous tests
736 if (!kind.isInstance(spans[i])) continue;
737
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 if (count == 0) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700739 // Safe conversion thanks to the isInstance test above
740 ret1 = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 count++;
742 } else {
743 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700744 // Safe conversion, but requires a suppressWarning
745 ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 ret[0] = ret1;
747 }
748
749 int prio = flags[i] & SPAN_PRIORITY;
750 if (prio != 0) {
751 int j;
752
753 for (j = 0; j < count; j++) {
754 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
755
756 if (prio > p) {
757 break;
758 }
759 }
760
761 System.arraycopy(ret, j, ret, j + 1, count - j);
Gilles Debunne312cd582010-07-02 17:02:34 -0700762 // Safe conversion thanks to the isInstance test above
763 ret[j] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 count++;
765 } else {
Gilles Debunne312cd582010-07-02 17:02:34 -0700766 // Safe conversion thanks to the isInstance test above
767 ret[count++] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800768 }
769 }
770 }
771
772 if (count == 0) {
Doug Feltf47d7402010-04-21 16:01:52 -0700773 return ArrayUtils.emptyArray(kind);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 }
775 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700776 // Safe conversion, but requires a suppressWarning
777 ret = (T[]) Array.newInstance(kind, 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 ret[0] = ret1;
Gilles Debunne312cd582010-07-02 17:02:34 -0700779 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 }
781 if (count == ret.length) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700782 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784
Gilles Debunne312cd582010-07-02 17:02:34 -0700785 // Safe conversion, but requires a suppressWarning
786 T[] nret = (T[]) Array.newInstance(kind, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 System.arraycopy(ret, 0, nret, 0, count);
Gilles Debunne312cd582010-07-02 17:02:34 -0700788 return nret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789 }
790
791 /**
792 * Return the next offset after <code>start</code> but less than or
793 * equal to <code>limit</code> where a span of the specified type
794 * begins or ends.
795 */
796 public int nextSpanTransition(int start, int limit, Class kind) {
797 int count = mSpanCount;
798 Object[] spans = mSpans;
799 int[] starts = mSpanStarts;
800 int[] ends = mSpanEnds;
801 int gapstart = mGapStart;
802 int gaplen = mGapLength;
803
804 if (kind == null) {
805 kind = Object.class;
806 }
807
808 for (int i = 0; i < count; i++) {
809 int st = starts[i];
810 int en = ends[i];
811
812 if (st > gapstart)
813 st -= gaplen;
814 if (en > gapstart)
815 en -= gaplen;
816
817 if (st > start && st < limit && kind.isInstance(spans[i]))
818 limit = st;
819 if (en > start && en < limit && kind.isInstance(spans[i]))
820 limit = en;
821 }
822
823 return limit;
824 }
825
826 /**
827 * Return a new CharSequence containing a copy of the specified
828 * range of this buffer, including the overlapping spans.
829 */
830 public CharSequence subSequence(int start, int end) {
831 return new SpannableStringBuilder(this, start, end);
832 }
833
834 /**
835 * Copy the specified range of chars from this buffer into the
836 * specified array, beginning at the specified offset.
837 */
838 public void getChars(int start, int end, char[] dest, int destoff) {
839 checkRange("getChars", start, end);
840
841 if (end <= mGapStart) {
842 System.arraycopy(mText, start, dest, destoff, end - start);
843 } else if (start >= mGapStart) {
844 System.arraycopy(mText, start + mGapLength,
845 dest, destoff, end - start);
846 } else {
847 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
848 System.arraycopy(mText, mGapStart + mGapLength,
849 dest, destoff + (mGapStart - start),
850 end - mGapStart);
851 }
852 }
853
854 /**
855 * Return a String containing a copy of the chars in this buffer.
856 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700857 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 public String toString() {
859 int len = length();
860 char[] buf = new char[len];
861
862 getChars(0, len, buf, 0);
863 return new String(buf);
864 }
865
Gilles Debunne653d3a22011-12-07 10:35:59 -0800866 /**
867 * Return a String containing a copy of the chars in this buffer, limited to the
868 * [start, end[ range.
869 * @hide
870 */
871 public String substring(int start, int end) {
872 char[] buf = new char[end - start];
873 getChars(start, end, buf, 0);
874 return new String(buf);
875 }
876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 private TextWatcher[] sendTextWillChange(int start, int before, int after) {
878 TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class);
879 int n = recip.length;
880
881 for (int i = 0; i < n; i++) {
882 recip[i].beforeTextChanged(this, start, before, after);
883 }
884
885 return recip;
886 }
887
Gilles Debunne6435a562011-08-04 21:22:30 -0700888 private void sendTextChange(TextWatcher[] recip, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 int n = recip.length;
890
891 for (int i = 0; i < n; i++) {
892 recip[i].onTextChanged(this, start, before, after);
893 }
894 }
895
896 private void sendTextHasChanged(TextWatcher[] recip) {
897 int n = recip.length;
898
899 for (int i = 0; i < n; i++) {
900 recip[i].afterTextChanged(this);
901 }
902 }
903
904 private void sendSpanAdded(Object what, int start, int end) {
905 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
906 int n = recip.length;
907
908 for (int i = 0; i < n; i++) {
909 recip[i].onSpanAdded(this, what, start, end);
910 }
911 }
912
913 private void sendSpanRemoved(Object what, int start, int end) {
914 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
915 int n = recip.length;
916
917 for (int i = 0; i < n; i++) {
918 recip[i].onSpanRemoved(this, what, start, end);
919 }
920 }
921
922 private void sendSpanChanged(Object what, int s, int e, int st, int en) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700923 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 int n = recip.length;
925
926 for (int i = 0; i < n; i++) {
927 recip[i].onSpanChanged(this, what, s, e, st, en);
928 }
929 }
930
931 private static String region(int start, int end) {
932 return "(" + start + " ... " + end + ")";
933 }
934
935 private void checkRange(final String operation, int start, int end) {
936 if (end < start) {
937 throw new IndexOutOfBoundsException(operation + " " +
938 region(start, end) +
939 " has end before start");
940 }
941
942 int len = length();
943
944 if (start > len || end > len) {
945 throw new IndexOutOfBoundsException(operation + " " +
946 region(start, end) +
947 " ends beyond length " + len);
948 }
949
950 if (start < 0 || end < 0) {
951 throw new IndexOutOfBoundsException(operation + " " +
952 region(start, end) +
953 " starts before 0");
954 }
955 }
956
Gilles Debunne312cd582010-07-02 17:02:34 -0700957/*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958 private boolean isprint(char c) { // XXX
959 if (c >= ' ' && c <= '~')
960 return true;
961 else
962 return false;
963 }
964
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800965 private static final int startFlag(int flag) {
966 return (flag >> 4) & 0x0F;
967 }
968
969 private static final int endFlag(int flag) {
970 return flag & 0x0F;
971 }
972
973 public void dump() { // XXX
974 for (int i = 0; i < mGapStart; i++) {
975 System.out.print('|');
976 System.out.print(' ');
977 System.out.print(isprint(mText[i]) ? mText[i] : '.');
978 System.out.print(' ');
979 }
980
981 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
982 System.out.print('|');
983 System.out.print('(');
984 System.out.print(isprint(mText[i]) ? mText[i] : '.');
985 System.out.print(')');
986 }
987
988 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
989 System.out.print('|');
990 System.out.print(' ');
991 System.out.print(isprint(mText[i]) ? mText[i] : '.');
992 System.out.print(' ');
993 }
994
995 System.out.print('\n');
996
997 for (int i = 0; i < mText.length + 1; i++) {
998 int found = 0;
999 int wfound = 0;
1000
1001 for (int j = 0; j < mSpanCount; j++) {
1002 if (mSpanStarts[j] == i) {
1003 found = 1;
1004 wfound = j;
1005 break;
1006 }
1007
1008 if (mSpanEnds[j] == i) {
1009 found = 2;
1010 wfound = j;
1011 break;
1012 }
1013 }
1014
1015 if (found == 1) {
1016 if (startFlag(mSpanFlags[wfound]) == MARK)
1017 System.out.print("( ");
1018 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1019 System.out.print("< ");
1020 else
1021 System.out.print("[ ");
1022 } else if (found == 2) {
1023 if (endFlag(mSpanFlags[wfound]) == POINT)
1024 System.out.print(") ");
1025 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1026 System.out.print("> ");
1027 else
1028 System.out.print("] ");
1029 } else {
1030 System.out.print(" ");
1031 }
1032 }
1033
1034 System.out.print("\n");
1035 }
1036*/
1037
1038 /**
1039 * Don't call this yourself -- exists for Canvas to use internally.
1040 * {@hide}
1041 */
1042 public void drawText(Canvas c, int start, int end,
1043 float x, float y, Paint p) {
1044 checkRange("drawText", start, end);
1045
1046 if (end <= mGapStart) {
1047 c.drawText(mText, start, end - start, x, y, p);
1048 } else if (start >= mGapStart) {
1049 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1050 } else {
1051 char[] buf = TextUtils.obtain(end - start);
1052
1053 getChars(start, end, buf, 0);
1054 c.drawText(buf, 0, end - start, x, y, p);
1055 TextUtils.recycle(buf);
1056 }
1057 }
1058
Doug Felt0c702b82010-05-14 10:55:42 -07001059
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 /**
Doug Feltf47d7402010-04-21 16:01:52 -07001061 * Don't call this yourself -- exists for Canvas to use internally.
1062 * {@hide}
1063 */
1064 public void drawTextRun(Canvas c, int start, int end,
Doug Felt0c702b82010-05-14 10:55:42 -07001065 int contextStart, int contextEnd,
1066 float x, float y, int flags, Paint p) {
Doug Feltf47d7402010-04-21 16:01:52 -07001067 checkRange("drawTextRun", start, end);
1068
Doug Felt0c702b82010-05-14 10:55:42 -07001069 int contextLen = contextEnd - contextStart;
1070 int len = end - start;
1071 if (contextEnd <= mGapStart) {
1072 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
1073 } else if (contextStart >= mGapStart) {
1074 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
1075 contextLen, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001076 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001077 char[] buf = TextUtils.obtain(contextLen);
1078 getChars(contextStart, contextEnd, buf, 0);
1079 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001080 TextUtils.recycle(buf);
1081 }
1082 }
1083
1084 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 * Don't call this yourself -- exists for Paint to use internally.
1086 * {@hide}
1087 */
1088 public float measureText(int start, int end, Paint p) {
1089 checkRange("measureText", start, end);
1090
1091 float ret;
1092
1093 if (end <= mGapStart) {
1094 ret = p.measureText(mText, start, end - start);
1095 } else if (start >= mGapStart) {
1096 ret = p.measureText(mText, start + mGapLength, end - start);
1097 } else {
1098 char[] buf = TextUtils.obtain(end - start);
1099
1100 getChars(start, end, buf, 0);
1101 ret = p.measureText(buf, 0, end - start);
1102 TextUtils.recycle(buf);
1103 }
1104
1105 return ret;
1106 }
1107
1108 /**
1109 * Don't call this yourself -- exists for Paint to use internally.
1110 * {@hide}
1111 */
1112 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1113 checkRange("getTextWidths", start, end);
1114
1115 int ret;
1116
1117 if (end <= mGapStart) {
1118 ret = p.getTextWidths(mText, start, end - start, widths);
1119 } else if (start >= mGapStart) {
1120 ret = p.getTextWidths(mText, start + mGapLength, end - start,
1121 widths);
1122 } else {
1123 char[] buf = TextUtils.obtain(end - start);
1124
1125 getChars(start, end, buf, 0);
1126 ret = p.getTextWidths(buf, 0, end - start, widths);
1127 TextUtils.recycle(buf);
1128 }
1129
1130 return ret;
1131 }
1132
Doug Felt0c702b82010-05-14 10:55:42 -07001133 /**
1134 * Don't call this yourself -- exists for Paint to use internally.
1135 * {@hide}
1136 */
1137 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1138 float[] advances, int advancesPos, Paint p) {
1139
1140 float ret;
1141
1142 int contextLen = contextEnd - contextStart;
1143 int len = end - start;
1144
1145 if (end <= mGapStart) {
1146 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1147 flags, advances, advancesPos);
1148 } else if (start >= mGapStart) {
1149 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1150 contextStart + mGapLength, contextLen, flags, advances, advancesPos);
1151 } else {
1152 char[] buf = TextUtils.obtain(contextLen);
1153 getChars(contextStart, contextEnd, buf, 0);
1154 ret = p.getTextRunAdvances(buf, start - contextStart, len,
1155 0, contextLen, flags, advances, advancesPos);
1156 TextUtils.recycle(buf);
1157 }
1158
1159 return ret;
1160 }
1161
Gilles Debunnef09d5102011-03-02 17:28:31 -08001162 /**
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07001163 * Don't call this yourself -- exists for Paint to use internally.
1164 * {@hide}
1165 */
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07001166 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1167 float[] advances, int advancesPos, Paint p, int reserved) {
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07001168
1169 float ret;
1170
1171 int contextLen = contextEnd - contextStart;
1172 int len = end - start;
1173
1174 if (end <= mGapStart) {
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07001175 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1176 flags, advances, advancesPos, reserved);
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07001177 } else if (start >= mGapStart) {
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07001178 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1179 contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07001180 } else {
1181 char[] buf = TextUtils.obtain(contextLen);
1182 getChars(contextStart, contextEnd, buf, 0);
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07001183 ret = p.getTextRunAdvances(buf, start - contextStart, len,
1184 0, contextLen, flags, advances, advancesPos, reserved);
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07001185 TextUtils.recycle(buf);
1186 }
1187
1188 return ret;
1189 }
1190
1191 /**
Gilles Debunnef09d5102011-03-02 17:28:31 -08001192 * Returns the next cursor position in the run. This avoids placing the cursor between
1193 * surrogates, between characters that form conjuncts, between base characters and combining
1194 * marks, or within a reordering cluster.
1195 *
1196 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1197 * span enclosing the cursor in the direction of movement.
1198 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1199 * the start of the string.</p>
1200 *
Gilles Debunne616f3832011-03-02 19:50:16 -08001201 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
Gilles Debunnef09d5102011-03-02 17:28:31 -08001202 * this returns -1. Otherwise this will never return a value before contextStart or after
1203 * contextEnd.</p>
1204 *
1205 * @param contextStart the start index of the context
1206 * @param contextEnd the (non-inclusive) end index of the context
Gilles Debunne616f3832011-03-02 19:50:16 -08001207 * @param flags either DIRECTION_RTL or DIRECTION_LTR
Gilles Debunnef09d5102011-03-02 17:28:31 -08001208 * @param offset the cursor position to move from
Gilles Debunne616f3832011-03-02 19:50:16 -08001209 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1210 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1211 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
Gilles Debunnef09d5102011-03-02 17:28:31 -08001212 * @param p the Paint object that is requesting this information
1213 * @return the offset of the next position, or -1
Gilles Debunneb0b22562011-03-03 14:51:39 -08001214 * @deprecated This is an internal method, refrain from using it in your code
Gilles Debunnef09d5102011-03-02 17:28:31 -08001215 */
Gilles Debunneb0b22562011-03-03 14:51:39 -08001216 @Deprecated
Doug Felt0c702b82010-05-14 10:55:42 -07001217 public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
1218 int cursorOpt, Paint p) {
1219
1220 int ret;
1221
1222 int contextLen = contextEnd - contextStart;
1223 if (contextEnd <= mGapStart) {
1224 ret = p.getTextRunCursor(mText, contextStart, contextLen,
1225 flags, offset, cursorOpt);
1226 } else if (contextStart >= mGapStart) {
1227 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
Gilles Debunne312cd582010-07-02 17:02:34 -07001228 flags, offset + mGapLength, cursorOpt) - mGapLength;
Doug Felt0c702b82010-05-14 10:55:42 -07001229 } else {
1230 char[] buf = TextUtils.obtain(contextLen);
1231 getChars(contextStart, contextEnd, buf, 0);
1232 ret = p.getTextRunCursor(buf, 0, contextLen,
1233 flags, offset - contextStart, cursorOpt) + contextStart;
1234 TextUtils.recycle(buf);
1235 }
1236
1237 return ret;
1238 }
1239
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240 // Documentation from interface
1241 public void setFilters(InputFilter[] filters) {
1242 if (filters == null) {
1243 throw new IllegalArgumentException();
1244 }
1245
1246 mFilters = filters;
1247 }
1248
1249 // Documentation from interface
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 public InputFilter[] getFilters() {
1251 return mFilters;
1252 }
1253
1254 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1255 private InputFilter[] mFilters = NO_FILTERS;
1256
1257 private char[] mText;
1258 private int mGapStart;
1259 private int mGapLength;
1260
1261 private Object[] mSpans;
1262 private int[] mSpanStarts;
1263 private int[] mSpanEnds;
1264 private int[] mSpanFlags;
1265 private int mSpanCount;
1266
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267 private static final int POINT = 2;
1268 private static final int PARAGRAPH = 3;
1269
1270 private static final int START_MASK = 0xF0;
1271 private static final int END_MASK = 0x0F;
1272 private static final int START_SHIFT = 4;
1273}