blob: ea5cdfebfdea992a429d291ed3f70f06029427b1 [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
19import com.android.internal.util.ArrayUtils;
Doug Feltf47d7402010-04-21 16:01:52 -070020
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.graphics.Canvas;
Doug Feltf47d7402010-04-21 16:01:52 -070022import android.graphics.Paint;
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 */
29public class SpannableStringBuilder
30implements CharSequence, GetChars, Spannable, Editable, Appendable,
31 GraphicsOperations
32{
33 /**
34 * Create a new SpannableStringBuilder with empty contents
35 */
36 public SpannableStringBuilder() {
37 this("");
38 }
39
40 /**
41 * Create a new SpannableStringBuilder containing a copy of the
42 * specified text, including its spans if any.
43 */
44 public SpannableStringBuilder(CharSequence text) {
45 this(text, 0, text.length());
46 }
47
48 /**
49 * Create a new SpannableStringBuilder containing a copy of the
50 * specified slice of the specified text, including its spans if any.
51 */
52 public SpannableStringBuilder(CharSequence text, int start, int end) {
53 int srclen = end - start;
54
55 int len = ArrayUtils.idealCharArraySize(srclen + 1);
56 mText = new char[len];
57 mGapStart = srclen;
58 mGapLength = len - srclen;
59
60 TextUtils.getChars(text, start, end, mText, 0);
61
62 mSpanCount = 0;
63 int alloc = ArrayUtils.idealIntArraySize(0);
64 mSpans = new Object[alloc];
65 mSpanStarts = new int[alloc];
66 mSpanEnds = new int[alloc];
67 mSpanFlags = new int[alloc];
68
69 if (text instanceof Spanned) {
70 Spanned sp = (Spanned) text;
71 Object[] spans = sp.getSpans(start, end, Object.class);
72
73 for (int i = 0; i < spans.length; i++) {
74 if (spans[i] instanceof NoCopySpan) {
75 continue;
76 }
77
78 int st = sp.getSpanStart(spans[i]) - start;
79 int en = sp.getSpanEnd(spans[i]) - start;
80 int fl = sp.getSpanFlags(spans[i]);
81
82 if (st < 0)
83 st = 0;
84 if (st > end - start)
85 st = end - start;
86
87 if (en < 0)
88 en = 0;
89 if (en > end - start)
90 en = end - start;
91
92 setSpan(spans[i], st, en, fl);
93 }
94 }
95 }
96
97 public static SpannableStringBuilder valueOf(CharSequence source) {
98 if (source instanceof SpannableStringBuilder) {
99 return (SpannableStringBuilder) source;
100 } else {
101 return new SpannableStringBuilder(source);
102 }
103 }
104
105 /**
106 * Return the char at the specified offset within the buffer.
107 */
108 public char charAt(int where) {
109 int len = length();
110 if (where < 0) {
111 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
112 } else if (where >= len) {
113 throw new IndexOutOfBoundsException("charAt: " + where +
114 " >= length " + len);
115 }
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) {
131 int newlen = ArrayUtils.idealCharArraySize(size + 1);
132 char[] newtext = new char[newlen];
133
134 int after = mText.length - (mGapStart + mGapLength);
135
136 System.arraycopy(mText, 0, newtext, 0, mGapStart);
137 System.arraycopy(mText, mText.length - after,
138 newtext, newlen - after, after);
139
140 for (int i = 0; i < mSpanCount; i++) {
141 if (mSpanStarts[i] > mGapStart)
142 mSpanStarts[i] += newlen - mText.length;
143 if (mSpanEnds[i] > mGapStart)
144 mSpanEnds[i] += newlen - mText.length;
145 }
146
147 int oldlen = mText.length;
148 mText = newtext;
149 mGapLength += mText.length - oldlen;
150
151 if (mGapLength < 1)
152 new Exception("mGapLength < 1").printStackTrace();
153 }
154
155 private void moveGapTo(int where) {
156 if (where == mGapStart)
157 return;
158
159 boolean atend = (where == length());
160
161 if (where < mGapStart) {
162 int overlap = mGapStart - where;
163
164 System.arraycopy(mText, where,
165 mText, mGapStart + mGapLength - overlap, overlap);
166 } else /* where > mGapStart */ {
167 int overlap = where - mGapStart;
168
169 System.arraycopy(mText, where + mGapLength - overlap,
170 mText, mGapStart, overlap);
171 }
172
173 // XXX be more clever
174 for (int i = 0; i < mSpanCount; i++) {
175 int start = mSpanStarts[i];
176 int end = mSpanEnds[i];
177
178 if (start > mGapStart)
179 start -= mGapLength;
180 if (start > where)
181 start += mGapLength;
182 else if (start == where) {
183 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
184
185 if (flag == POINT || (atend && flag == PARAGRAPH))
186 start += mGapLength;
187 }
188
189 if (end > mGapStart)
190 end -= mGapLength;
191 if (end > where)
192 end += mGapLength;
193 else if (end == where) {
194 int flag = (mSpanFlags[i] & END_MASK);
195
196 if (flag == POINT || (atend && flag == PARAGRAPH))
197 end += mGapLength;
198 }
199
200 mSpanStarts[i] = start;
201 mSpanEnds[i] = end;
202 }
203
204 mGapStart = where;
205 }
206
207 // Documentation from interface
208 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
209 return replace(where, where, tb, start, end);
210 }
211
212 // Documentation from interface
213 public SpannableStringBuilder insert(int where, CharSequence tb) {
214 return replace(where, where, tb, 0, tb.length());
215 }
216
217 // Documentation from interface
218 public SpannableStringBuilder delete(int start, int end) {
219 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
220
221 if (mGapLength > 2 * length())
222 resizeFor(length());
223
224 return ret; // == this
225 }
226
227 // Documentation from interface
228 public void clear() {
229 replace(0, length(), "", 0, 0);
230 }
231
232 // Documentation from interface
233 public void clearSpans() {
234 for (int i = mSpanCount - 1; i >= 0; i--) {
235 Object what = mSpans[i];
236 int ostart = mSpanStarts[i];
237 int oend = mSpanEnds[i];
238
239 if (ostart > mGapStart)
240 ostart -= mGapLength;
241 if (oend > mGapStart)
242 oend -= mGapLength;
243
244 mSpanCount = i;
245 mSpans[i] = null;
246
247 sendSpanRemoved(what, ostart, oend);
248 }
249 }
250
251 // Documentation from interface
252 public SpannableStringBuilder append(CharSequence text) {
253 int length = length();
254 return replace(length, length, text, 0, text.length());
255 }
256
257 // Documentation from interface
258 public SpannableStringBuilder append(CharSequence text, int start, int end) {
259 int length = length();
260 return replace(length, length, text, start, end);
261 }
262
263 // Documentation from interface
264 public SpannableStringBuilder append(char text) {
265 return append(String.valueOf(text));
266 }
267
268 private int change(int start, int end,
269 CharSequence tb, int tbstart, int tbend) {
270 return change(true, start, end, tb, tbstart, tbend);
271 }
272
273 private int change(boolean notify, int start, int end,
274 CharSequence tb, int tbstart, int tbend) {
275 checkRange("replace", start, end);
276 int ret = tbend - tbstart;
277 TextWatcher[] recipients = null;
278
279 if (notify)
280 recipients = sendTextWillChange(start, end - start,
281 tbend - tbstart);
282
283 for (int i = mSpanCount - 1; i >= 0; i--) {
284 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
285 int st = mSpanStarts[i];
286 if (st > mGapStart)
287 st -= mGapLength;
288
289 int en = mSpanEnds[i];
290 if (en > mGapStart)
291 en -= mGapLength;
292
293 int ost = st;
294 int oen = en;
295 int clen = length();
296
297 if (st > start && st <= end) {
298 for (st = end; st < clen; st++)
299 if (st > end && charAt(st - 1) == '\n')
300 break;
301 }
302
303 if (en > start && en <= end) {
304 for (en = end; en < clen; en++)
305 if (en > end && charAt(en - 1) == '\n')
306 break;
307 }
308
309 if (st != ost || en != oen)
310 setSpan(mSpans[i], st, en, mSpanFlags[i]);
311 }
312 }
313
314 moveGapTo(end);
315
Gilles Debunne312cd582010-07-02 17:02:34 -0700316 // Can be negative
317 final int nbNewChars = (tbend - tbstart) - (end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318
Gilles Debunne312cd582010-07-02 17:02:34 -0700319 if (nbNewChars >= mGapLength) {
320 resizeFor(mText.length + nbNewChars - mGapLength);
321 }
322
323 mGapStart += nbNewChars;
324 mGapLength -= nbNewChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325
326 if (mGapLength < 1)
327 new Exception("mGapLength < 1").printStackTrace();
328
329 TextUtils.getChars(tb, tbstart, tbend, mText, start);
330
331 if (tb instanceof Spanned) {
332 Spanned sp = (Spanned) tb;
333 Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
334
335 for (int i = 0; i < spans.length; i++) {
336 int st = sp.getSpanStart(spans[i]);
337 int en = sp.getSpanEnd(spans[i]);
338
339 if (st < tbstart)
340 st = tbstart;
341 if (en > tbend)
342 en = tbend;
343
344 if (getSpanStart(spans[i]) < 0) {
345 setSpan(false, spans[i],
346 st - tbstart + start,
347 en - tbstart + start,
348 sp.getSpanFlags(spans[i]));
349 }
350 }
351 }
352
353 // no need for span fixup on pure insertion
354 if (tbend > tbstart && end - start == 0) {
355 if (notify) {
356 sendTextChange(recipients, start, end - start, tbend - tbstart);
357 sendTextHasChanged(recipients);
358 }
359
360 return ret;
361 }
362
363 boolean atend = (mGapStart + mGapLength == mText.length);
364
365 for (int i = mSpanCount - 1; i >= 0; i--) {
366 if (mSpanStarts[i] >= start &&
367 mSpanStarts[i] < mGapStart + mGapLength) {
368 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
369
370 if (flag == POINT || (flag == PARAGRAPH && atend))
371 mSpanStarts[i] = mGapStart + mGapLength;
372 else
373 mSpanStarts[i] = start;
374 }
375
376 if (mSpanEnds[i] >= start &&
377 mSpanEnds[i] < mGapStart + mGapLength) {
378 int flag = (mSpanFlags[i] & END_MASK);
379
380 if (flag == POINT || (flag == PARAGRAPH && atend))
381 mSpanEnds[i] = mGapStart + mGapLength;
382 else
383 mSpanEnds[i] = start;
384 }
385
386 // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
387 // XXX send notification on removal
388
389 if (mSpanEnds[i] < mSpanStarts[i]) {
390 System.arraycopy(mSpans, i + 1,
391 mSpans, i, mSpanCount - (i + 1));
392 System.arraycopy(mSpanStarts, i + 1,
393 mSpanStarts, i, mSpanCount - (i + 1));
394 System.arraycopy(mSpanEnds, i + 1,
395 mSpanEnds, i, mSpanCount - (i + 1));
396 System.arraycopy(mSpanFlags, i + 1,
397 mSpanFlags, i, mSpanCount - (i + 1));
398
399 mSpanCount--;
400 }
401 }
402
403 if (notify) {
404 sendTextChange(recipients, start, end - start, tbend - tbstart);
405 sendTextHasChanged(recipients);
406 }
407
408 return ret;
409 }
410
411 // Documentation from interface
412 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
413 return replace(start, end, tb, 0, tb.length());
414 }
415
416 // Documentation from interface
417 public SpannableStringBuilder replace(final int start, final int end,
418 CharSequence tb, int tbstart, int tbend) {
419 int filtercount = mFilters.length;
420 for (int i = 0; i < filtercount; i++) {
421 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend,
422 this, start, end);
423
424 if (repl != null) {
425 tb = repl;
426 tbstart = 0;
427 tbend = repl.length();
428 }
429 }
430
431 if (end == start && tbstart == tbend) {
432 return this;
433 }
434
435 if (end == start || tbstart == tbend) {
436 change(start, end, tb, tbstart, tbend);
437 } else {
438 int selstart = Selection.getSelectionStart(this);
439 int selend = Selection.getSelectionEnd(this);
440
441 // XXX just make the span fixups in change() do the right thing
442 // instead of this madness!
443
444 checkRange("replace", start, end);
445 moveGapTo(end);
446 TextWatcher[] recipients;
447
448 recipients = sendTextWillChange(start, end - start,
449 tbend - tbstart);
450
451 int origlen = end - start;
452
453 if (mGapLength < 2)
454 resizeFor(length() + 1);
455
456 for (int i = mSpanCount - 1; i >= 0; i--) {
457 if (mSpanStarts[i] == mGapStart)
458 mSpanStarts[i]++;
459
460 if (mSpanEnds[i] == mGapStart)
461 mSpanEnds[i]++;
462 }
463
464 mText[mGapStart] = ' ';
465 mGapStart++;
466 mGapLength--;
467
468 if (mGapLength < 1)
469 new Exception("mGapLength < 1").printStackTrace();
470
471 int oldlen = (end + 1) - start;
472
473 int inserted = change(false, start + 1, start + 1,
474 tb, tbstart, tbend);
475 change(false, start, start + 1, "", 0, 0);
476 change(false, start + inserted, start + inserted + oldlen - 1,
477 "", 0, 0);
478
479 /*
480 * Special case to keep the cursor in the same position
481 * if it was somewhere in the middle of the replaced region.
482 * If it was at the start or the end or crossing the whole
483 * replacement, it should already be where it belongs.
484 * TODO: Is there some more general mechanism that could
485 * accomplish this?
486 */
487 if (selstart > start && selstart < end) {
488 long off = selstart - start;
489
490 off = off * inserted / (end - start);
491 selstart = (int) off + start;
492
493 setSpan(false, Selection.SELECTION_START, selstart, selstart,
494 Spanned.SPAN_POINT_POINT);
495 }
496 if (selend > start && selend < end) {
497 long off = selend - start;
498
499 off = off * inserted / (end - start);
500 selend = (int) off + start;
501
502 setSpan(false, Selection.SELECTION_END, selend, selend,
503 Spanned.SPAN_POINT_POINT);
504 }
505
506 sendTextChange(recipients, start, origlen, inserted);
507 sendTextHasChanged(recipients);
508 }
509 return this;
510 }
511
512 /**
513 * Mark the specified range of text with the specified object.
514 * The flags determine how the span will behave when text is
515 * inserted at the start or end of the span's range.
516 */
517 public void setSpan(Object what, int start, int end, int flags) {
518 setSpan(true, what, start, end, flags);
519 }
520
521 private void setSpan(boolean send,
522 Object what, int start, int end, int flags) {
523 int nstart = start;
524 int nend = end;
525
526 checkRange("setSpan", start, end);
527
528 if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
529 if (start != 0 && start != length()) {
530 char c = charAt(start - 1);
531
532 if (c != '\n')
533 throw new RuntimeException(
534 "PARAGRAPH span must start at paragraph boundary");
535 }
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')
543 throw new RuntimeException(
544 "PARAGRAPH span must end at paragraph boundary");
545 }
546 }
547
548 if (start > mGapStart)
549 start += mGapLength;
550 else if (start == mGapStart) {
551 int flag = (flags & START_MASK) >> START_SHIFT;
552
553 if (flag == POINT || (flag == PARAGRAPH && start == length()))
554 start += mGapLength;
555 }
556
557 if (end > mGapStart)
558 end += mGapLength;
559 else if (end == mGapStart) {
560 int flag = (flags & END_MASK);
561
562 if (flag == POINT || (flag == PARAGRAPH && end == length()))
563 end += mGapLength;
564 }
565
566 int count = mSpanCount;
567 Object[] spans = mSpans;
568
569 for (int i = 0; i < count; i++) {
570 if (spans[i] == what) {
571 int ostart = mSpanStarts[i];
572 int oend = mSpanEnds[i];
573
574 if (ostart > mGapStart)
575 ostart -= mGapLength;
576 if (oend > mGapStart)
577 oend -= mGapLength;
578
579 mSpanStarts[i] = start;
580 mSpanEnds[i] = end;
581 mSpanFlags[i] = flags;
582
583 if (send)
584 sendSpanChanged(what, ostart, oend, nstart, nend);
585
586 return;
587 }
588 }
589
590 if (mSpanCount + 1 >= mSpans.length) {
591 int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
592 Object[] newspans = new Object[newsize];
593 int[] newspanstarts = new int[newsize];
594 int[] newspanends = new int[newsize];
595 int[] newspanflags = new int[newsize];
596
597 System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
598 System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
599 System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
600 System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
601
602 mSpans = newspans;
603 mSpanStarts = newspanstarts;
604 mSpanEnds = newspanends;
605 mSpanFlags = newspanflags;
606 }
607
608 mSpans[mSpanCount] = what;
609 mSpanStarts[mSpanCount] = start;
610 mSpanEnds[mSpanCount] = end;
611 mSpanFlags[mSpanCount] = flags;
612 mSpanCount++;
613
614 if (send)
615 sendSpanAdded(what, nstart, nend);
616 }
617
618 /**
619 * Remove the specified markup object from the buffer.
620 */
621 public void removeSpan(Object what) {
622 for (int i = mSpanCount - 1; i >= 0; i--) {
623 if (mSpans[i] == what) {
624 int ostart = mSpanStarts[i];
625 int oend = mSpanEnds[i];
626
627 if (ostart > mGapStart)
628 ostart -= mGapLength;
629 if (oend > mGapStart)
630 oend -= mGapLength;
631
632 int count = mSpanCount - (i + 1);
633
634 System.arraycopy(mSpans, i + 1, mSpans, i, count);
635 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
636 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
637 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
638
639 mSpanCount--;
640 mSpans[mSpanCount] = null;
641
642 sendSpanRemoved(what, ostart, oend);
643 return;
644 }
645 }
646 }
647
648 /**
649 * Return the buffer offset of the beginning of the specified
650 * markup object, or -1 if it is not attached to this buffer.
651 */
652 public int getSpanStart(Object what) {
653 int count = mSpanCount;
654 Object[] spans = mSpans;
655
656 for (int i = count - 1; i >= 0; i--) {
657 if (spans[i] == what) {
658 int where = mSpanStarts[i];
659
660 if (where > mGapStart)
661 where -= mGapLength;
662
663 return where;
664 }
665 }
666
667 return -1;
668 }
669
670 /**
671 * Return the buffer offset of the end of the specified
672 * markup object, or -1 if it is not attached to this buffer.
673 */
674 public int getSpanEnd(Object what) {
675 int count = mSpanCount;
676 Object[] spans = mSpans;
677
678 for (int i = count - 1; i >= 0; i--) {
679 if (spans[i] == what) {
680 int where = mSpanEnds[i];
681
682 if (where > mGapStart)
683 where -= mGapLength;
684
685 return where;
686 }
687 }
688
689 return -1;
690 }
691
692 /**
693 * Return the flags of the end of the specified
694 * markup object, or 0 if it is not attached to this buffer.
695 */
696 public int getSpanFlags(Object what) {
697 int count = mSpanCount;
698 Object[] spans = mSpans;
699
700 for (int i = count - 1; i >= 0; i--) {
701 if (spans[i] == what) {
702 return mSpanFlags[i];
703 }
704 }
705
706 return 0;
707 }
708
709 /**
710 * Return an array of the spans of the specified type that overlap
711 * the specified range of the buffer. The kind may be Object.class to get
712 * a list of all the spans regardless of type.
713 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700714 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
716 int spanCount = mSpanCount;
717 Object[] spans = mSpans;
718 int[] starts = mSpanStarts;
719 int[] ends = mSpanEnds;
720 int[] flags = mSpanFlags;
721 int gapstart = mGapStart;
722 int gaplen = mGapLength;
723
724 int count = 0;
Gilles Debunne312cd582010-07-02 17:02:34 -0700725 T[] ret = null;
726 T ret1 = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727
728 for (int i = 0; i < spanCount; i++) {
729 int spanStart = starts[i];
730 int spanEnd = ends[i];
731
732 if (spanStart > gapstart) {
733 spanStart -= gaplen;
734 }
735 if (spanEnd > gapstart) {
736 spanEnd -= gaplen;
737 }
738
739 if (spanStart > queryEnd) {
740 continue;
741 }
742 if (spanEnd < queryStart) {
743 continue;
744 }
745
746 if (spanStart != spanEnd && queryStart != queryEnd) {
747 if (spanStart == queryEnd)
748 continue;
749 if (spanEnd == queryStart)
750 continue;
751 }
752
753 if (kind != null && !kind.isInstance(spans[i])) {
754 continue;
755 }
756
757 if (count == 0) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700758 // Safe conversion thanks to the isInstance test above
759 ret1 = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 count++;
761 } else {
762 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700763 // Safe conversion, but requires a suppressWarning
764 ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 ret[0] = ret1;
766 }
767
768 int prio = flags[i] & SPAN_PRIORITY;
769 if (prio != 0) {
770 int j;
771
772 for (j = 0; j < count; j++) {
773 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
774
775 if (prio > p) {
776 break;
777 }
778 }
779
780 System.arraycopy(ret, j, ret, j + 1, count - j);
Gilles Debunne312cd582010-07-02 17:02:34 -0700781 // Safe conversion thanks to the isInstance test above
782 ret[j] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 count++;
784 } else {
Gilles Debunne312cd582010-07-02 17:02:34 -0700785 // Safe conversion thanks to the isInstance test above
786 ret[count++] = (T) spans[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 }
788 }
789 }
790
791 if (count == 0) {
Doug Feltf47d7402010-04-21 16:01:52 -0700792 return ArrayUtils.emptyArray(kind);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 }
794 if (count == 1) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700795 // Safe conversion, but requires a suppressWarning
796 ret = (T[]) Array.newInstance(kind, 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 ret[0] = ret1;
Gilles Debunne312cd582010-07-02 17:02:34 -0700798 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 }
800 if (count == ret.length) {
Gilles Debunne312cd582010-07-02 17:02:34 -0700801 return ret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
803
Gilles Debunne312cd582010-07-02 17:02:34 -0700804 // Safe conversion, but requires a suppressWarning
805 T[] nret = (T[]) Array.newInstance(kind, count);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 System.arraycopy(ret, 0, nret, 0, count);
Gilles Debunne312cd582010-07-02 17:02:34 -0700807 return nret;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 }
809
810 /**
811 * Return the next offset after <code>start</code> but less than or
812 * equal to <code>limit</code> where a span of the specified type
813 * begins or ends.
814 */
815 public int nextSpanTransition(int start, int limit, Class kind) {
816 int count = mSpanCount;
817 Object[] spans = mSpans;
818 int[] starts = mSpanStarts;
819 int[] ends = mSpanEnds;
820 int gapstart = mGapStart;
821 int gaplen = mGapLength;
822
823 if (kind == null) {
824 kind = Object.class;
825 }
826
827 for (int i = 0; i < count; i++) {
828 int st = starts[i];
829 int en = ends[i];
830
831 if (st > gapstart)
832 st -= gaplen;
833 if (en > gapstart)
834 en -= gaplen;
835
836 if (st > start && st < limit && kind.isInstance(spans[i]))
837 limit = st;
838 if (en > start && en < limit && kind.isInstance(spans[i]))
839 limit = en;
840 }
841
842 return limit;
843 }
844
845 /**
846 * Return a new CharSequence containing a copy of the specified
847 * range of this buffer, including the overlapping spans.
848 */
849 public CharSequence subSequence(int start, int end) {
850 return new SpannableStringBuilder(this, start, end);
851 }
852
853 /**
854 * Copy the specified range of chars from this buffer into the
855 * specified array, beginning at the specified offset.
856 */
857 public void getChars(int start, int end, char[] dest, int destoff) {
858 checkRange("getChars", start, end);
859
860 if (end <= mGapStart) {
861 System.arraycopy(mText, start, dest, destoff, end - start);
862 } else if (start >= mGapStart) {
863 System.arraycopy(mText, start + mGapLength,
864 dest, destoff, end - start);
865 } else {
866 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
867 System.arraycopy(mText, mGapStart + mGapLength,
868 dest, destoff + (mGapStart - start),
869 end - mGapStart);
870 }
871 }
872
873 /**
874 * Return a String containing a copy of the chars in this buffer.
875 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700876 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 public String toString() {
878 int len = length();
879 char[] buf = new char[len];
880
881 getChars(0, len, buf, 0);
882 return new String(buf);
883 }
884
885 private TextWatcher[] sendTextWillChange(int start, int before, int after) {
886 TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class);
887 int n = recip.length;
888
889 for (int i = 0; i < n; i++) {
890 recip[i].beforeTextChanged(this, start, before, after);
891 }
892
893 return recip;
894 }
895
896 private void sendTextChange(TextWatcher[] recip, int start, int before,
897 int after) {
898 int n = recip.length;
899
900 for (int i = 0; i < n; i++) {
901 recip[i].onTextChanged(this, start, before, after);
902 }
903 }
904
905 private void sendTextHasChanged(TextWatcher[] recip) {
906 int n = recip.length;
907
908 for (int i = 0; i < n; i++) {
909 recip[i].afterTextChanged(this);
910 }
911 }
912
913 private void sendSpanAdded(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].onSpanAdded(this, what, start, end);
919 }
920 }
921
922 private void sendSpanRemoved(Object what, int start, int end) {
923 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
924 int n = recip.length;
925
926 for (int i = 0; i < n; i++) {
927 recip[i].onSpanRemoved(this, what, start, end);
928 }
929 }
930
931 private void sendSpanChanged(Object what, int s, int e, int st, int en) {
932 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
933 SpanWatcher.class);
934 int n = recip.length;
935
936 for (int i = 0; i < n; i++) {
937 recip[i].onSpanChanged(this, what, s, e, st, en);
938 }
939 }
940
941 private static String region(int start, int end) {
942 return "(" + start + " ... " + end + ")";
943 }
944
945 private void checkRange(final String operation, int start, int end) {
946 if (end < start) {
947 throw new IndexOutOfBoundsException(operation + " " +
948 region(start, end) +
949 " has end before start");
950 }
951
952 int len = length();
953
954 if (start > len || end > len) {
955 throw new IndexOutOfBoundsException(operation + " " +
956 region(start, end) +
957 " ends beyond length " + len);
958 }
959
960 if (start < 0 || end < 0) {
961 throw new IndexOutOfBoundsException(operation + " " +
962 region(start, end) +
963 " starts before 0");
964 }
965 }
966
Gilles Debunne312cd582010-07-02 17:02:34 -0700967/*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 private boolean isprint(char c) { // XXX
969 if (c >= ' ' && c <= '~')
970 return true;
971 else
972 return false;
973 }
974
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 private static final int startFlag(int flag) {
976 return (flag >> 4) & 0x0F;
977 }
978
979 private static final int endFlag(int flag) {
980 return flag & 0x0F;
981 }
982
983 public void dump() { // XXX
984 for (int i = 0; i < mGapStart; i++) {
985 System.out.print('|');
986 System.out.print(' ');
987 System.out.print(isprint(mText[i]) ? mText[i] : '.');
988 System.out.print(' ');
989 }
990
991 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
992 System.out.print('|');
993 System.out.print('(');
994 System.out.print(isprint(mText[i]) ? mText[i] : '.');
995 System.out.print(')');
996 }
997
998 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
999 System.out.print('|');
1000 System.out.print(' ');
1001 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1002 System.out.print(' ');
1003 }
1004
1005 System.out.print('\n');
1006
1007 for (int i = 0; i < mText.length + 1; i++) {
1008 int found = 0;
1009 int wfound = 0;
1010
1011 for (int j = 0; j < mSpanCount; j++) {
1012 if (mSpanStarts[j] == i) {
1013 found = 1;
1014 wfound = j;
1015 break;
1016 }
1017
1018 if (mSpanEnds[j] == i) {
1019 found = 2;
1020 wfound = j;
1021 break;
1022 }
1023 }
1024
1025 if (found == 1) {
1026 if (startFlag(mSpanFlags[wfound]) == MARK)
1027 System.out.print("( ");
1028 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1029 System.out.print("< ");
1030 else
1031 System.out.print("[ ");
1032 } else if (found == 2) {
1033 if (endFlag(mSpanFlags[wfound]) == POINT)
1034 System.out.print(") ");
1035 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1036 System.out.print("> ");
1037 else
1038 System.out.print("] ");
1039 } else {
1040 System.out.print(" ");
1041 }
1042 }
1043
1044 System.out.print("\n");
1045 }
1046*/
1047
1048 /**
1049 * Don't call this yourself -- exists for Canvas to use internally.
1050 * {@hide}
1051 */
1052 public void drawText(Canvas c, int start, int end,
1053 float x, float y, Paint p) {
1054 checkRange("drawText", start, end);
1055
1056 if (end <= mGapStart) {
1057 c.drawText(mText, start, end - start, x, y, p);
1058 } else if (start >= mGapStart) {
1059 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1060 } else {
1061 char[] buf = TextUtils.obtain(end - start);
1062
1063 getChars(start, end, buf, 0);
1064 c.drawText(buf, 0, end - start, x, y, p);
1065 TextUtils.recycle(buf);
1066 }
1067 }
1068
Doug Felt0c702b82010-05-14 10:55:42 -07001069
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 /**
Doug Feltf47d7402010-04-21 16:01:52 -07001071 * Don't call this yourself -- exists for Canvas to use internally.
1072 * {@hide}
1073 */
1074 public void drawTextRun(Canvas c, int start, int end,
Doug Felt0c702b82010-05-14 10:55:42 -07001075 int contextStart, int contextEnd,
1076 float x, float y, int flags, Paint p) {
Doug Feltf47d7402010-04-21 16:01:52 -07001077 checkRange("drawTextRun", start, end);
1078
Doug Felt0c702b82010-05-14 10:55:42 -07001079 int contextLen = contextEnd - contextStart;
1080 int len = end - start;
1081 if (contextEnd <= mGapStart) {
1082 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
1083 } else if (contextStart >= mGapStart) {
1084 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
1085 contextLen, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001086 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001087 char[] buf = TextUtils.obtain(contextLen);
1088 getChars(contextStart, contextEnd, buf, 0);
1089 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001090 TextUtils.recycle(buf);
1091 }
1092 }
1093
1094 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 * Don't call this yourself -- exists for Paint to use internally.
1096 * {@hide}
1097 */
1098 public float measureText(int start, int end, Paint p) {
1099 checkRange("measureText", start, end);
1100
1101 float ret;
1102
1103 if (end <= mGapStart) {
1104 ret = p.measureText(mText, start, end - start);
1105 } else if (start >= mGapStart) {
1106 ret = p.measureText(mText, start + mGapLength, end - start);
1107 } else {
1108 char[] buf = TextUtils.obtain(end - start);
1109
1110 getChars(start, end, buf, 0);
1111 ret = p.measureText(buf, 0, end - start);
1112 TextUtils.recycle(buf);
1113 }
1114
1115 return ret;
1116 }
1117
1118 /**
1119 * Don't call this yourself -- exists for Paint to use internally.
1120 * {@hide}
1121 */
1122 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1123 checkRange("getTextWidths", start, end);
1124
1125 int ret;
1126
1127 if (end <= mGapStart) {
1128 ret = p.getTextWidths(mText, start, end - start, widths);
1129 } else if (start >= mGapStart) {
1130 ret = p.getTextWidths(mText, start + mGapLength, end - start,
1131 widths);
1132 } else {
1133 char[] buf = TextUtils.obtain(end - start);
1134
1135 getChars(start, end, buf, 0);
1136 ret = p.getTextWidths(buf, 0, end - start, widths);
1137 TextUtils.recycle(buf);
1138 }
1139
1140 return ret;
1141 }
1142
Doug Felt0c702b82010-05-14 10:55:42 -07001143 /**
1144 * Don't call this yourself -- exists for Paint to use internally.
1145 * {@hide}
1146 */
1147 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1148 float[] advances, int advancesPos, Paint p) {
1149
1150 float ret;
1151
1152 int contextLen = contextEnd - contextStart;
1153 int len = end - start;
1154
1155 if (end <= mGapStart) {
1156 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1157 flags, advances, advancesPos);
1158 } else if (start >= mGapStart) {
1159 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1160 contextStart + mGapLength, contextLen, flags, advances, advancesPos);
1161 } else {
1162 char[] buf = TextUtils.obtain(contextLen);
1163 getChars(contextStart, contextEnd, buf, 0);
1164 ret = p.getTextRunAdvances(buf, start - contextStart, len,
1165 0, contextLen, flags, advances, advancesPos);
1166 TextUtils.recycle(buf);
1167 }
1168
1169 return ret;
1170 }
1171
Gilles Debunnef09d5102011-03-02 17:28:31 -08001172 /**
1173 * Returns the next cursor position in the run. This avoids placing the cursor between
1174 * surrogates, between characters that form conjuncts, between base characters and combining
1175 * marks, or within a reordering cluster.
1176 *
1177 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1178 * span enclosing the cursor in the direction of movement.
1179 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1180 * the start of the string.</p>
1181 *
Gilles Debunne616f3832011-03-02 19:50:16 -08001182 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
Gilles Debunnef09d5102011-03-02 17:28:31 -08001183 * this returns -1. Otherwise this will never return a value before contextStart or after
1184 * contextEnd.</p>
1185 *
1186 * @param contextStart the start index of the context
1187 * @param contextEnd the (non-inclusive) end index of the context
Gilles Debunne616f3832011-03-02 19:50:16 -08001188 * @param flags either DIRECTION_RTL or DIRECTION_LTR
Gilles Debunnef09d5102011-03-02 17:28:31 -08001189 * @param offset the cursor position to move from
Gilles Debunne616f3832011-03-02 19:50:16 -08001190 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1191 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1192 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
Gilles Debunnef09d5102011-03-02 17:28:31 -08001193 * @param p the Paint object that is requesting this information
1194 * @return the offset of the next position, or -1
Gilles Debunneb0b22562011-03-03 14:51:39 -08001195 * @deprecated This is an internal method, refrain from using it in your code
Gilles Debunnef09d5102011-03-02 17:28:31 -08001196 */
Gilles Debunneb0b22562011-03-03 14:51:39 -08001197 @Deprecated
Doug Felt0c702b82010-05-14 10:55:42 -07001198 public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
1199 int cursorOpt, Paint p) {
1200
1201 int ret;
1202
1203 int contextLen = contextEnd - contextStart;
1204 if (contextEnd <= mGapStart) {
1205 ret = p.getTextRunCursor(mText, contextStart, contextLen,
1206 flags, offset, cursorOpt);
1207 } else if (contextStart >= mGapStart) {
1208 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
Gilles Debunne312cd582010-07-02 17:02:34 -07001209 flags, offset + mGapLength, cursorOpt) - mGapLength;
Doug Felt0c702b82010-05-14 10:55:42 -07001210 } else {
1211 char[] buf = TextUtils.obtain(contextLen);
1212 getChars(contextStart, contextEnd, buf, 0);
1213 ret = p.getTextRunCursor(buf, 0, contextLen,
1214 flags, offset - contextStart, cursorOpt) + contextStart;
1215 TextUtils.recycle(buf);
1216 }
1217
1218 return ret;
1219 }
1220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001221 // Documentation from interface
1222 public void setFilters(InputFilter[] filters) {
1223 if (filters == null) {
1224 throw new IllegalArgumentException();
1225 }
1226
1227 mFilters = filters;
1228 }
1229
1230 // Documentation from interface
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 public InputFilter[] getFilters() {
1232 return mFilters;
1233 }
1234
1235 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1236 private InputFilter[] mFilters = NO_FILTERS;
1237
1238 private char[] mText;
1239 private int mGapStart;
1240 private int mGapLength;
1241
1242 private Object[] mSpans;
1243 private int[] mSpanStarts;
1244 private int[] mSpanEnds;
1245 private int[] mSpanFlags;
1246 private int mSpanCount;
1247
1248 private static final int MARK = 1;
1249 private static final int POINT = 2;
1250 private static final int PARAGRAPH = 3;
1251
1252 private static final int START_MASK = 0xF0;
1253 private static final int END_MASK = 0x0F;
1254 private static final int START_SHIFT = 4;
1255}