blob: caaafa1473976ea153e614e36df24da26b9b81a2 [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;
20import android.graphics.Paint;
21import android.graphics.Canvas;
22
23import java.lang.reflect.Array;
24
25/**
26 * This is the class for text whose content and markup can both be changed.
27 */
28public class SpannableStringBuilder
29implements CharSequence, GetChars, Spannable, Editable, Appendable,
30 GraphicsOperations
31{
32 /**
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
54 int len = ArrayUtils.idealCharArraySize(srclen + 1);
55 mText = new char[len];
56 mGapStart = srclen;
57 mGapLength = len - srclen;
58
59 TextUtils.getChars(text, start, end, mText, 0);
60
61 mSpanCount = 0;
62 int alloc = ArrayUtils.idealIntArraySize(0);
63 mSpans = new Object[alloc];
64 mSpanStarts = new int[alloc];
65 mSpanEnds = new int[alloc];
66 mSpanFlags = new int[alloc];
67
68 if (text instanceof Spanned) {
69 Spanned sp = (Spanned) text;
70 Object[] spans = sp.getSpans(start, end, Object.class);
71
72 for (int i = 0; i < spans.length; i++) {
73 if (spans[i] instanceof NoCopySpan) {
74 continue;
75 }
76
77 int st = sp.getSpanStart(spans[i]) - start;
78 int en = sp.getSpanEnd(spans[i]) - start;
79 int fl = sp.getSpanFlags(spans[i]);
80
81 if (st < 0)
82 st = 0;
83 if (st > end - start)
84 st = end - start;
85
86 if (en < 0)
87 en = 0;
88 if (en > end - start)
89 en = end - start;
90
91 setSpan(spans[i], st, en, fl);
92 }
93 }
94 }
95
96 public static SpannableStringBuilder valueOf(CharSequence source) {
97 if (source instanceof SpannableStringBuilder) {
98 return (SpannableStringBuilder) source;
99 } else {
100 return new SpannableStringBuilder(source);
101 }
102 }
103
104 /**
105 * Return the char at the specified offset within the buffer.
106 */
107 public char charAt(int where) {
108 int len = length();
109 if (where < 0) {
110 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
111 } else if (where >= len) {
112 throw new IndexOutOfBoundsException("charAt: " + where +
113 " >= length " + len);
114 }
115
116 if (where >= mGapStart)
117 return mText[where + mGapLength];
118 else
119 return mText[where];
120 }
121
122 /**
123 * Return the number of chars in the buffer.
124 */
125 public int length() {
126 return mText.length - mGapLength;
127 }
128
129 private void resizeFor(int size) {
130 int newlen = ArrayUtils.idealCharArraySize(size + 1);
131 char[] newtext = new char[newlen];
132
133 int after = mText.length - (mGapStart + mGapLength);
134
135 System.arraycopy(mText, 0, newtext, 0, mGapStart);
136 System.arraycopy(mText, mText.length - after,
137 newtext, newlen - after, after);
138
139 for (int i = 0; i < mSpanCount; i++) {
140 if (mSpanStarts[i] > mGapStart)
141 mSpanStarts[i] += newlen - mText.length;
142 if (mSpanEnds[i] > mGapStart)
143 mSpanEnds[i] += newlen - mText.length;
144 }
145
146 int oldlen = mText.length;
147 mText = newtext;
148 mGapLength += mText.length - oldlen;
149
150 if (mGapLength < 1)
151 new Exception("mGapLength < 1").printStackTrace();
152 }
153
154 private void moveGapTo(int where) {
155 if (where == mGapStart)
156 return;
157
158 boolean atend = (where == length());
159
160 if (where < mGapStart) {
161 int overlap = mGapStart - where;
162
163 System.arraycopy(mText, where,
164 mText, mGapStart + mGapLength - overlap, overlap);
165 } else /* where > mGapStart */ {
166 int overlap = where - mGapStart;
167
168 System.arraycopy(mText, where + mGapLength - overlap,
169 mText, mGapStart, overlap);
170 }
171
172 // XXX be more clever
173 for (int i = 0; i < mSpanCount; i++) {
174 int start = mSpanStarts[i];
175 int end = mSpanEnds[i];
176
177 if (start > mGapStart)
178 start -= mGapLength;
179 if (start > where)
180 start += mGapLength;
181 else if (start == where) {
182 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
183
184 if (flag == POINT || (atend && flag == PARAGRAPH))
185 start += mGapLength;
186 }
187
188 if (end > mGapStart)
189 end -= mGapLength;
190 if (end > where)
191 end += mGapLength;
192 else if (end == where) {
193 int flag = (mSpanFlags[i] & END_MASK);
194
195 if (flag == POINT || (atend && flag == PARAGRAPH))
196 end += mGapLength;
197 }
198
199 mSpanStarts[i] = start;
200 mSpanEnds[i] = end;
201 }
202
203 mGapStart = where;
204 }
205
206 // Documentation from interface
207 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
208 return replace(where, where, tb, start, end);
209 }
210
211 // Documentation from interface
212 public SpannableStringBuilder insert(int where, CharSequence tb) {
213 return replace(where, where, tb, 0, tb.length());
214 }
215
216 // Documentation from interface
217 public SpannableStringBuilder delete(int start, int end) {
218 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
219
220 if (mGapLength > 2 * length())
221 resizeFor(length());
222
223 return ret; // == this
224 }
225
226 // Documentation from interface
227 public void clear() {
228 replace(0, length(), "", 0, 0);
229 }
230
231 // Documentation from interface
232 public void clearSpans() {
233 for (int i = mSpanCount - 1; i >= 0; i--) {
234 Object what = mSpans[i];
235 int ostart = mSpanStarts[i];
236 int oend = mSpanEnds[i];
237
238 if (ostart > mGapStart)
239 ostart -= mGapLength;
240 if (oend > mGapStart)
241 oend -= mGapLength;
242
243 mSpanCount = i;
244 mSpans[i] = null;
245
246 sendSpanRemoved(what, ostart, oend);
247 }
248 }
249
250 // Documentation from interface
251 public SpannableStringBuilder append(CharSequence text) {
252 int length = length();
253 return replace(length, length, text, 0, text.length());
254 }
255
256 // Documentation from interface
257 public SpannableStringBuilder append(CharSequence text, int start, int end) {
258 int length = length();
259 return replace(length, length, text, start, end);
260 }
261
262 // Documentation from interface
263 public SpannableStringBuilder append(char text) {
264 return append(String.valueOf(text));
265 }
266
267 private int change(int start, int end,
268 CharSequence tb, int tbstart, int tbend) {
269 return change(true, start, end, tb, tbstart, tbend);
270 }
271
272 private int change(boolean notify, int start, int end,
273 CharSequence tb, int tbstart, int tbend) {
274 checkRange("replace", start, end);
275 int ret = tbend - tbstart;
276 TextWatcher[] recipients = null;
277
278 if (notify)
279 recipients = sendTextWillChange(start, end - start,
280 tbend - tbstart);
281
282 for (int i = mSpanCount - 1; i >= 0; i--) {
283 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
284 int st = mSpanStarts[i];
285 if (st > mGapStart)
286 st -= mGapLength;
287
288 int en = mSpanEnds[i];
289 if (en > mGapStart)
290 en -= mGapLength;
291
292 int ost = st;
293 int oen = en;
294 int clen = length();
295
296 if (st > start && st <= end) {
297 for (st = end; st < clen; st++)
298 if (st > end && charAt(st - 1) == '\n')
299 break;
300 }
301
302 if (en > start && en <= end) {
303 for (en = end; en < clen; en++)
304 if (en > end && charAt(en - 1) == '\n')
305 break;
306 }
307
308 if (st != ost || en != oen)
309 setSpan(mSpans[i], st, en, mSpanFlags[i]);
310 }
311 }
312
313 moveGapTo(end);
314
315 if (tbend - tbstart >= mGapLength + (end - start))
316 resizeFor(mText.length - mGapLength +
317 tbend - tbstart - (end - start));
318
319 mGapStart += tbend - tbstart - (end - start);
320 mGapLength -= tbend - tbstart - (end - start);
321
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
383 // XXX send notification on removal
384
385 if (mSpanEnds[i] < mSpanStarts[i]) {
386 System.arraycopy(mSpans, i + 1,
387 mSpans, i, mSpanCount - (i + 1));
388 System.arraycopy(mSpanStarts, i + 1,
389 mSpanStarts, i, mSpanCount - (i + 1));
390 System.arraycopy(mSpanEnds, i + 1,
391 mSpanEnds, i, mSpanCount - (i + 1));
392 System.arraycopy(mSpanFlags, i + 1,
393 mSpanFlags, i, mSpanCount - (i + 1));
394
395 mSpanCount--;
396 }
397 }
398
399 if (notify) {
400 sendTextChange(recipients, start, end - start, tbend - tbstart);
401 sendTextHasChanged(recipients);
402 }
403
404 return ret;
405 }
406
407 // Documentation from interface
408 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
409 return replace(start, end, tb, 0, tb.length());
410 }
411
412 // Documentation from interface
413 public SpannableStringBuilder replace(final int start, final int end,
414 CharSequence tb, int tbstart, int tbend) {
415 int filtercount = mFilters.length;
416 for (int i = 0; i < filtercount; i++) {
417 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend,
418 this, start, end);
419
420 if (repl != null) {
421 tb = repl;
422 tbstart = 0;
423 tbend = repl.length();
424 }
425 }
426
427 if (end == start && tbstart == tbend) {
428 return this;
429 }
430
431 if (end == start || tbstart == tbend) {
432 change(start, end, tb, tbstart, tbend);
433 } else {
434 int selstart = Selection.getSelectionStart(this);
435 int selend = Selection.getSelectionEnd(this);
436
437 // XXX just make the span fixups in change() do the right thing
438 // instead of this madness!
439
440 checkRange("replace", start, end);
441 moveGapTo(end);
442 TextWatcher[] recipients;
443
444 recipients = sendTextWillChange(start, end - start,
445 tbend - tbstart);
446
447 int origlen = end - start;
448
449 if (mGapLength < 2)
450 resizeFor(length() + 1);
451
452 for (int i = mSpanCount - 1; i >= 0; i--) {
453 if (mSpanStarts[i] == mGapStart)
454 mSpanStarts[i]++;
455
456 if (mSpanEnds[i] == mGapStart)
457 mSpanEnds[i]++;
458 }
459
460 mText[mGapStart] = ' ';
461 mGapStart++;
462 mGapLength--;
463
464 if (mGapLength < 1)
465 new Exception("mGapLength < 1").printStackTrace();
466
467 int oldlen = (end + 1) - start;
468
469 int inserted = change(false, start + 1, start + 1,
470 tb, tbstart, tbend);
471 change(false, start, start + 1, "", 0, 0);
472 change(false, start + inserted, start + inserted + oldlen - 1,
473 "", 0, 0);
474
475 /*
476 * Special case to keep the cursor in the same position
477 * if it was somewhere in the middle of the replaced region.
478 * If it was at the start or the end or crossing the whole
479 * replacement, it should already be where it belongs.
480 * TODO: Is there some more general mechanism that could
481 * accomplish this?
482 */
483 if (selstart > start && selstart < end) {
484 long off = selstart - start;
485
486 off = off * inserted / (end - start);
487 selstart = (int) off + start;
488
489 setSpan(false, Selection.SELECTION_START, selstart, selstart,
490 Spanned.SPAN_POINT_POINT);
491 }
492 if (selend > start && selend < end) {
493 long off = selend - start;
494
495 off = off * inserted / (end - start);
496 selend = (int) off + start;
497
498 setSpan(false, Selection.SELECTION_END, selend, selend,
499 Spanned.SPAN_POINT_POINT);
500 }
501
502 sendTextChange(recipients, start, origlen, inserted);
503 sendTextHasChanged(recipients);
504 }
505 return this;
506 }
507
508 /**
509 * Mark the specified range of text with the specified object.
510 * The flags determine how the span will behave when text is
511 * inserted at the start or end of the span's range.
512 */
513 public void setSpan(Object what, int start, int end, int flags) {
514 setSpan(true, what, start, end, flags);
515 }
516
517 private void setSpan(boolean send,
518 Object what, int start, int end, int flags) {
519 int nstart = start;
520 int nend = end;
521
522 checkRange("setSpan", start, end);
523
524 if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
525 if (start != 0 && start != length()) {
526 char c = charAt(start - 1);
527
528 if (c != '\n')
529 throw new RuntimeException(
530 "PARAGRAPH span must start at paragraph boundary");
531 }
532 }
533
534 if ((flags & END_MASK) == PARAGRAPH) {
535 if (end != 0 && end != length()) {
536 char c = charAt(end - 1);
537
538 if (c != '\n')
539 throw new RuntimeException(
540 "PARAGRAPH span must end at paragraph boundary");
541 }
542 }
543
544 if (start > mGapStart)
545 start += mGapLength;
546 else if (start == mGapStart) {
547 int flag = (flags & START_MASK) >> START_SHIFT;
548
549 if (flag == POINT || (flag == PARAGRAPH && start == length()))
550 start += mGapLength;
551 }
552
553 if (end > mGapStart)
554 end += mGapLength;
555 else if (end == mGapStart) {
556 int flag = (flags & END_MASK);
557
558 if (flag == POINT || (flag == PARAGRAPH && end == length()))
559 end += mGapLength;
560 }
561
562 int count = mSpanCount;
563 Object[] spans = mSpans;
564
565 for (int i = 0; i < count; i++) {
566 if (spans[i] == what) {
567 int ostart = mSpanStarts[i];
568 int oend = mSpanEnds[i];
569
570 if (ostart > mGapStart)
571 ostart -= mGapLength;
572 if (oend > mGapStart)
573 oend -= mGapLength;
574
575 mSpanStarts[i] = start;
576 mSpanEnds[i] = end;
577 mSpanFlags[i] = flags;
578
579 if (send)
580 sendSpanChanged(what, ostart, oend, nstart, nend);
581
582 return;
583 }
584 }
585
586 if (mSpanCount + 1 >= mSpans.length) {
587 int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
588 Object[] newspans = new Object[newsize];
589 int[] newspanstarts = new int[newsize];
590 int[] newspanends = new int[newsize];
591 int[] newspanflags = new int[newsize];
592
593 System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
594 System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
595 System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
596 System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
597
598 mSpans = newspans;
599 mSpanStarts = newspanstarts;
600 mSpanEnds = newspanends;
601 mSpanFlags = newspanflags;
602 }
603
604 mSpans[mSpanCount] = what;
605 mSpanStarts[mSpanCount] = start;
606 mSpanEnds[mSpanCount] = end;
607 mSpanFlags[mSpanCount] = flags;
608 mSpanCount++;
609
610 if (send)
611 sendSpanAdded(what, nstart, nend);
612 }
613
614 /**
615 * Remove the specified markup object from the buffer.
616 */
617 public void removeSpan(Object what) {
618 for (int i = mSpanCount - 1; i >= 0; i--) {
619 if (mSpans[i] == what) {
620 int ostart = mSpanStarts[i];
621 int oend = mSpanEnds[i];
622
623 if (ostart > mGapStart)
624 ostart -= mGapLength;
625 if (oend > mGapStart)
626 oend -= mGapLength;
627
628 int count = mSpanCount - (i + 1);
629
630 System.arraycopy(mSpans, i + 1, mSpans, i, count);
631 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
632 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
633 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
634
635 mSpanCount--;
636 mSpans[mSpanCount] = null;
637
638 sendSpanRemoved(what, ostart, oend);
639 return;
640 }
641 }
642 }
643
644 /**
645 * Return the buffer offset of the beginning of the specified
646 * markup object, or -1 if it is not attached to this buffer.
647 */
648 public int getSpanStart(Object what) {
649 int count = mSpanCount;
650 Object[] spans = mSpans;
651
652 for (int i = count - 1; i >= 0; i--) {
653 if (spans[i] == what) {
654 int where = mSpanStarts[i];
655
656 if (where > mGapStart)
657 where -= mGapLength;
658
659 return where;
660 }
661 }
662
663 return -1;
664 }
665
666 /**
667 * Return the buffer offset of the end of the specified
668 * markup object, or -1 if it is not attached to this buffer.
669 */
670 public int getSpanEnd(Object what) {
671 int count = mSpanCount;
672 Object[] spans = mSpans;
673
674 for (int i = count - 1; i >= 0; i--) {
675 if (spans[i] == what) {
676 int where = mSpanEnds[i];
677
678 if (where > mGapStart)
679 where -= mGapLength;
680
681 return where;
682 }
683 }
684
685 return -1;
686 }
687
688 /**
689 * Return the flags of the end of the specified
690 * markup object, or 0 if it is not attached to this buffer.
691 */
692 public int getSpanFlags(Object what) {
693 int count = mSpanCount;
694 Object[] spans = mSpans;
695
696 for (int i = count - 1; i >= 0; i--) {
697 if (spans[i] == what) {
698 return mSpanFlags[i];
699 }
700 }
701
702 return 0;
703 }
704
705 /**
706 * Return an array of the spans of the specified type that overlap
707 * the specified range of the buffer. The kind may be Object.class to get
708 * a list of all the spans regardless of type.
709 */
710 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
711 int spanCount = mSpanCount;
712 Object[] spans = mSpans;
713 int[] starts = mSpanStarts;
714 int[] ends = mSpanEnds;
715 int[] flags = mSpanFlags;
716 int gapstart = mGapStart;
717 int gaplen = mGapLength;
718
719 int count = 0;
720 Object[] ret = null;
721 Object ret1 = null;
722
723 for (int i = 0; i < spanCount; i++) {
724 int spanStart = starts[i];
725 int spanEnd = ends[i];
726
727 if (spanStart > gapstart) {
728 spanStart -= gaplen;
729 }
730 if (spanEnd > gapstart) {
731 spanEnd -= gaplen;
732 }
733
734 if (spanStart > queryEnd) {
735 continue;
736 }
737 if (spanEnd < queryStart) {
738 continue;
739 }
740
741 if (spanStart != spanEnd && queryStart != queryEnd) {
742 if (spanStart == queryEnd)
743 continue;
744 if (spanEnd == queryStart)
745 continue;
746 }
747
748 if (kind != null && !kind.isInstance(spans[i])) {
749 continue;
750 }
751
752 if (count == 0) {
753 ret1 = spans[i];
754 count++;
755 } else {
756 if (count == 1) {
757 ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
758 ret[0] = ret1;
759 }
760
761 int prio = flags[i] & SPAN_PRIORITY;
762 if (prio != 0) {
763 int j;
764
765 for (j = 0; j < count; j++) {
766 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
767
768 if (prio > p) {
769 break;
770 }
771 }
772
773 System.arraycopy(ret, j, ret, j + 1, count - j);
774 ret[j] = spans[i];
775 count++;
776 } else {
777 ret[count++] = spans[i];
778 }
779 }
780 }
781
782 if (count == 0) {
783 return (T[]) ArrayUtils.emptyArray(kind);
784 }
785 if (count == 1) {
786 ret = (Object[]) Array.newInstance(kind, 1);
787 ret[0] = ret1;
788 return (T[]) ret;
789 }
790 if (count == ret.length) {
791 return (T[]) ret;
792 }
793
794 Object[] nret = (Object[]) Array.newInstance(kind, count);
795 System.arraycopy(ret, 0, nret, 0, count);
796 return (T[]) nret;
797 }
798
799 /**
800 * Return the next offset after <code>start</code> but less than or
801 * equal to <code>limit</code> where a span of the specified type
802 * begins or ends.
803 */
804 public int nextSpanTransition(int start, int limit, Class kind) {
805 int count = mSpanCount;
806 Object[] spans = mSpans;
807 int[] starts = mSpanStarts;
808 int[] ends = mSpanEnds;
809 int gapstart = mGapStart;
810 int gaplen = mGapLength;
811
812 if (kind == null) {
813 kind = Object.class;
814 }
815
816 for (int i = 0; i < count; i++) {
817 int st = starts[i];
818 int en = ends[i];
819
820 if (st > gapstart)
821 st -= gaplen;
822 if (en > gapstart)
823 en -= gaplen;
824
825 if (st > start && st < limit && kind.isInstance(spans[i]))
826 limit = st;
827 if (en > start && en < limit && kind.isInstance(spans[i]))
828 limit = en;
829 }
830
831 return limit;
832 }
833
834 /**
835 * Return a new CharSequence containing a copy of the specified
836 * range of this buffer, including the overlapping spans.
837 */
838 public CharSequence subSequence(int start, int end) {
839 return new SpannableStringBuilder(this, start, end);
840 }
841
842 /**
843 * Copy the specified range of chars from this buffer into the
844 * specified array, beginning at the specified offset.
845 */
846 public void getChars(int start, int end, char[] dest, int destoff) {
847 checkRange("getChars", start, end);
848
849 if (end <= mGapStart) {
850 System.arraycopy(mText, start, dest, destoff, end - start);
851 } else if (start >= mGapStart) {
852 System.arraycopy(mText, start + mGapLength,
853 dest, destoff, end - start);
854 } else {
855 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
856 System.arraycopy(mText, mGapStart + mGapLength,
857 dest, destoff + (mGapStart - start),
858 end - mGapStart);
859 }
860 }
861
862 /**
863 * Return a String containing a copy of the chars in this buffer.
864 */
865 public String toString() {
866 int len = length();
867 char[] buf = new char[len];
868
869 getChars(0, len, buf, 0);
870 return new String(buf);
871 }
872
873 private TextWatcher[] sendTextWillChange(int start, int before, int after) {
874 TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class);
875 int n = recip.length;
876
877 for (int i = 0; i < n; i++) {
878 recip[i].beforeTextChanged(this, start, before, after);
879 }
880
881 return recip;
882 }
883
884 private void sendTextChange(TextWatcher[] recip, int start, int before,
885 int after) {
886 int n = recip.length;
887
888 for (int i = 0; i < n; i++) {
889 recip[i].onTextChanged(this, start, before, after);
890 }
891 }
892
893 private void sendTextHasChanged(TextWatcher[] recip) {
894 int n = recip.length;
895
896 for (int i = 0; i < n; i++) {
897 recip[i].afterTextChanged(this);
898 }
899 }
900
901 private void sendSpanAdded(Object what, int start, int end) {
902 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
903 int n = recip.length;
904
905 for (int i = 0; i < n; i++) {
906 recip[i].onSpanAdded(this, what, start, end);
907 }
908 }
909
910 private void sendSpanRemoved(Object what, int start, int end) {
911 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
912 int n = recip.length;
913
914 for (int i = 0; i < n; i++) {
915 recip[i].onSpanRemoved(this, what, start, end);
916 }
917 }
918
919 private void sendSpanChanged(Object what, int s, int e, int st, int en) {
920 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
921 SpanWatcher.class);
922 int n = recip.length;
923
924 for (int i = 0; i < n; i++) {
925 recip[i].onSpanChanged(this, what, s, e, st, en);
926 }
927 }
928
929 private static String region(int start, int end) {
930 return "(" + start + " ... " + end + ")";
931 }
932
933 private void checkRange(final String operation, int start, int end) {
934 if (end < start) {
935 throw new IndexOutOfBoundsException(operation + " " +
936 region(start, end) +
937 " has end before start");
938 }
939
940 int len = length();
941
942 if (start > len || end > len) {
943 throw new IndexOutOfBoundsException(operation + " " +
944 region(start, end) +
945 " ends beyond length " + len);
946 }
947
948 if (start < 0 || end < 0) {
949 throw new IndexOutOfBoundsException(operation + " " +
950 region(start, end) +
951 " starts before 0");
952 }
953 }
954
955 private boolean isprint(char c) { // XXX
956 if (c >= ' ' && c <= '~')
957 return true;
958 else
959 return false;
960 }
961
962/*
963 private static final int startFlag(int flag) {
964 return (flag >> 4) & 0x0F;
965 }
966
967 private static final int endFlag(int flag) {
968 return flag & 0x0F;
969 }
970
971 public void dump() { // XXX
972 for (int i = 0; i < mGapStart; i++) {
973 System.out.print('|');
974 System.out.print(' ');
975 System.out.print(isprint(mText[i]) ? mText[i] : '.');
976 System.out.print(' ');
977 }
978
979 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
980 System.out.print('|');
981 System.out.print('(');
982 System.out.print(isprint(mText[i]) ? mText[i] : '.');
983 System.out.print(')');
984 }
985
986 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
987 System.out.print('|');
988 System.out.print(' ');
989 System.out.print(isprint(mText[i]) ? mText[i] : '.');
990 System.out.print(' ');
991 }
992
993 System.out.print('\n');
994
995 for (int i = 0; i < mText.length + 1; i++) {
996 int found = 0;
997 int wfound = 0;
998
999 for (int j = 0; j < mSpanCount; j++) {
1000 if (mSpanStarts[j] == i) {
1001 found = 1;
1002 wfound = j;
1003 break;
1004 }
1005
1006 if (mSpanEnds[j] == i) {
1007 found = 2;
1008 wfound = j;
1009 break;
1010 }
1011 }
1012
1013 if (found == 1) {
1014 if (startFlag(mSpanFlags[wfound]) == MARK)
1015 System.out.print("( ");
1016 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1017 System.out.print("< ");
1018 else
1019 System.out.print("[ ");
1020 } else if (found == 2) {
1021 if (endFlag(mSpanFlags[wfound]) == POINT)
1022 System.out.print(") ");
1023 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1024 System.out.print("> ");
1025 else
1026 System.out.print("] ");
1027 } else {
1028 System.out.print(" ");
1029 }
1030 }
1031
1032 System.out.print("\n");
1033 }
1034*/
1035
1036 /**
1037 * Don't call this yourself -- exists for Canvas to use internally.
1038 * {@hide}
1039 */
1040 public void drawText(Canvas c, int start, int end,
1041 float x, float y, Paint p) {
1042 checkRange("drawText", start, end);
1043
1044 if (end <= mGapStart) {
1045 c.drawText(mText, start, end - start, x, y, p);
1046 } else if (start >= mGapStart) {
1047 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1048 } else {
1049 char[] buf = TextUtils.obtain(end - start);
1050
1051 getChars(start, end, buf, 0);
1052 c.drawText(buf, 0, end - start, x, y, p);
1053 TextUtils.recycle(buf);
1054 }
1055 }
1056
1057 /**
1058 * Don't call this yourself -- exists for Paint to use internally.
1059 * {@hide}
1060 */
1061 public float measureText(int start, int end, Paint p) {
1062 checkRange("measureText", start, end);
1063
1064 float ret;
1065
1066 if (end <= mGapStart) {
1067 ret = p.measureText(mText, start, end - start);
1068 } else if (start >= mGapStart) {
1069 ret = p.measureText(mText, start + mGapLength, end - start);
1070 } else {
1071 char[] buf = TextUtils.obtain(end - start);
1072
1073 getChars(start, end, buf, 0);
1074 ret = p.measureText(buf, 0, end - start);
1075 TextUtils.recycle(buf);
1076 }
1077
1078 return ret;
1079 }
1080
1081 /**
1082 * Don't call this yourself -- exists for Paint to use internally.
1083 * {@hide}
1084 */
1085 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1086 checkRange("getTextWidths", start, end);
1087
1088 int ret;
1089
1090 if (end <= mGapStart) {
1091 ret = p.getTextWidths(mText, start, end - start, widths);
1092 } else if (start >= mGapStart) {
1093 ret = p.getTextWidths(mText, start + mGapLength, end - start,
1094 widths);
1095 } else {
1096 char[] buf = TextUtils.obtain(end - start);
1097
1098 getChars(start, end, buf, 0);
1099 ret = p.getTextWidths(buf, 0, end - start, widths);
1100 TextUtils.recycle(buf);
1101 }
1102
1103 return ret;
1104 }
1105
1106 // Documentation from interface
1107 public void setFilters(InputFilter[] filters) {
1108 if (filters == null) {
1109 throw new IllegalArgumentException();
1110 }
1111
1112 mFilters = filters;
1113 }
1114
1115 // Documentation from interface
1116 public InputFilter[] getFilters() {
1117 return mFilters;
1118 }
1119
1120 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1121 private InputFilter[] mFilters = NO_FILTERS;
1122
1123 private char[] mText;
1124 private int mGapStart;
1125 private int mGapLength;
1126
1127 private Object[] mSpans;
1128 private int[] mSpanStarts;
1129 private int[] mSpanEnds;
1130 private int[] mSpanFlags;
1131 private int mSpanCount;
1132
1133 private static final int MARK = 1;
1134 private static final int POINT = 2;
1135 private static final int PARAGRAPH = 3;
1136
1137 private static final int START_MASK = 0xF0;
1138 private static final int END_MASK = 0x0F;
1139 private static final int START_SHIFT = 4;
1140}