SpannableStringBuilder.java revision d24b8183b93e781080b2c16c487e60d51c12da31
1/*
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}
1141