Home | History | Annotate | Download | only in text
      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 
     17 package android.text;
     18 
     19 import android.graphics.Canvas;
     20 import android.graphics.Paint;
     21 import android.util.Log;
     22 
     23 import com.android.internal.util.ArrayUtils;
     24 
     25 import java.lang.reflect.Array;
     26 
     27 /**
     28  * This is the class for text whose content and markup can both be changed.
     29  */
     30 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
     31         Appendable, GraphicsOperations {
     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         if (srclen < 0) throw new StringIndexOutOfBoundsException();
     55 
     56         int len = ArrayUtils.idealCharArraySize(srclen + 1);
     57         mText = new char[len];
     58         mGapStart = srclen;
     59         mGapLength = len - srclen;
     60 
     61         TextUtils.getChars(text, start, end, mText, 0);
     62 
     63         mSpanCount = 0;
     64         int alloc = ArrayUtils.idealIntArraySize(0);
     65         mSpans = new Object[alloc];
     66         mSpanStarts = new int[alloc];
     67         mSpanEnds = new int[alloc];
     68         mSpanFlags = new int[alloc];
     69 
     70         if (text instanceof Spanned) {
     71             Spanned sp = (Spanned) text;
     72             Object[] spans = sp.getSpans(start, end, Object.class);
     73 
     74             for (int i = 0; i < spans.length; i++) {
     75                 if (spans[i] instanceof NoCopySpan) {
     76                     continue;
     77                 }
     78 
     79                 int st = sp.getSpanStart(spans[i]) - start;
     80                 int en = sp.getSpanEnd(spans[i]) - start;
     81                 int fl = sp.getSpanFlags(spans[i]);
     82 
     83                 if (st < 0)
     84                     st = 0;
     85                 if (st > end - start)
     86                     st = end - start;
     87 
     88                 if (en < 0)
     89                     en = 0;
     90                 if (en > end - start)
     91                     en = end - start;
     92 
     93                 setSpan(false, spans[i], st, en, fl);
     94             }
     95         }
     96     }
     97 
     98     public static SpannableStringBuilder valueOf(CharSequence source) {
     99         if (source instanceof SpannableStringBuilder) {
    100             return (SpannableStringBuilder) source;
    101         } else {
    102             return new SpannableStringBuilder(source);
    103         }
    104     }
    105 
    106     /**
    107      * Return the char at the specified offset within the buffer.
    108      */
    109     public char charAt(int where) {
    110         int len = length();
    111         if (where < 0) {
    112             throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
    113         } else if (where >= len) {
    114             throw new IndexOutOfBoundsException("charAt: " + where + " >= 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         final int oldLength = mText.length;
    132         final int newLength = ArrayUtils.idealCharArraySize(size + 1);
    133         final int delta = newLength - oldLength;
    134         if (delta == 0) return;
    135 
    136         char[] newText = new char[newLength];
    137         System.arraycopy(mText, 0, newText, 0, mGapStart);
    138         final int after = oldLength - (mGapStart + mGapLength);
    139         System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
    140         mText = newText;
    141 
    142         mGapLength += delta;
    143         if (mGapLength < 1)
    144             new Exception("mGapLength < 1").printStackTrace();
    145 
    146         for (int i = 0; i < mSpanCount; i++) {
    147             if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
    148             if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
    149         }
    150     }
    151 
    152     private void moveGapTo(int where) {
    153         if (where == mGapStart)
    154             return;
    155 
    156         boolean atEnd = (where == length());
    157 
    158         if (where < mGapStart) {
    159             int overlap = mGapStart - where;
    160             System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
    161         } else /* where > mGapStart */ {
    162             int overlap = where - mGapStart;
    163             System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
    164         }
    165 
    166         // XXX be more clever
    167         for (int i = 0; i < mSpanCount; i++) {
    168             int start = mSpanStarts[i];
    169             int end = mSpanEnds[i];
    170 
    171             if (start > mGapStart)
    172                 start -= mGapLength;
    173             if (start > where)
    174                 start += mGapLength;
    175             else if (start == where) {
    176                 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    177 
    178                 if (flag == POINT || (atEnd && flag == PARAGRAPH))
    179                     start += mGapLength;
    180             }
    181 
    182             if (end > mGapStart)
    183                 end -= mGapLength;
    184             if (end > where)
    185                 end += mGapLength;
    186             else if (end == where) {
    187                 int flag = (mSpanFlags[i] & END_MASK);
    188 
    189                 if (flag == POINT || (atEnd && flag == PARAGRAPH))
    190                     end += mGapLength;
    191             }
    192 
    193             mSpanStarts[i] = start;
    194             mSpanEnds[i] = end;
    195         }
    196 
    197         mGapStart = where;
    198     }
    199 
    200     // Documentation from interface
    201     public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
    202         return replace(where, where, tb, start, end);
    203     }
    204 
    205     // Documentation from interface
    206     public SpannableStringBuilder insert(int where, CharSequence tb) {
    207         return replace(where, where, tb, 0, tb.length());
    208     }
    209 
    210     // Documentation from interface
    211     public SpannableStringBuilder delete(int start, int end) {
    212         SpannableStringBuilder ret = replace(start, end, "", 0, 0);
    213 
    214         if (mGapLength > 2 * length())
    215             resizeFor(length());
    216 
    217         return ret; // == this
    218     }
    219 
    220     // Documentation from interface
    221     public void clear() {
    222         replace(0, length(), "", 0, 0);
    223     }
    224 
    225     // Documentation from interface
    226     public void clearSpans() {
    227         for (int i = mSpanCount - 1; i >= 0; i--) {
    228             Object what = mSpans[i];
    229             int ostart = mSpanStarts[i];
    230             int oend = mSpanEnds[i];
    231 
    232             if (ostart > mGapStart)
    233                 ostart -= mGapLength;
    234             if (oend > mGapStart)
    235                 oend -= mGapLength;
    236 
    237             mSpanCount = i;
    238             mSpans[i] = null;
    239 
    240             sendSpanRemoved(what, ostart, oend);
    241         }
    242     }
    243 
    244     // Documentation from interface
    245     public SpannableStringBuilder append(CharSequence text) {
    246         int length = length();
    247         return replace(length, length, text, 0, text.length());
    248     }
    249 
    250     // Documentation from interface
    251     public SpannableStringBuilder append(CharSequence text, int start, int end) {
    252         int length = length();
    253         return replace(length, length, text, start, end);
    254     }
    255 
    256     // Documentation from interface
    257     public SpannableStringBuilder append(char text) {
    258         return append(String.valueOf(text));
    259     }
    260 
    261     private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
    262         // Can be negative
    263         final int replacedLength = end - start;
    264         final int replacementLength = csEnd - csStart;
    265         final int nbNewChars = replacementLength - replacedLength;
    266 
    267         for (int i = mSpanCount - 1; i >= 0; i--) {
    268             int spanStart = mSpanStarts[i];
    269             if (spanStart > mGapStart)
    270                 spanStart -= mGapLength;
    271 
    272             int spanEnd = mSpanEnds[i];
    273             if (spanEnd > mGapStart)
    274                 spanEnd -= mGapLength;
    275 
    276             if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
    277                 int ost = spanStart;
    278                 int oen = spanEnd;
    279                 int clen = length();
    280 
    281                 if (spanStart > start && spanStart <= end) {
    282                     for (spanStart = end; spanStart < clen; spanStart++)
    283                         if (spanStart > end && charAt(spanStart - 1) == '\n')
    284                             break;
    285                 }
    286 
    287                 if (spanEnd > start && spanEnd <= end) {
    288                     for (spanEnd = end; spanEnd < clen; spanEnd++)
    289                         if (spanEnd > end && charAt(spanEnd - 1) == '\n')
    290                             break;
    291                 }
    292 
    293                 if (spanStart != ost || spanEnd != oen)
    294                     setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
    295             }
    296 
    297             int flags = 0;
    298             if (spanStart == start) flags |= SPAN_START_AT_START;
    299             else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
    300             if (spanEnd == start) flags |= SPAN_END_AT_START;
    301             else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
    302             mSpanFlags[i] |= flags;
    303         }
    304 
    305         moveGapTo(end);
    306 
    307         if (nbNewChars >= mGapLength) {
    308             resizeFor(mText.length + nbNewChars - mGapLength);
    309         }
    310 
    311         final boolean textIsRemoved = replacementLength == 0;
    312         // The removal pass needs to be done before the gap is updated in order to broadcast the
    313         // correct previous positions to the correct intersecting SpanWatchers
    314         if (replacedLength > 0) { // no need for span fixup on pure insertion
    315             // A for loop will not work because the array is being modified
    316             // Do not iterate in reverse to keep the SpanWatchers notified in ordering
    317             // Also, a removed SpanWatcher should not get notified of removed spans located
    318             // further in the span array.
    319             int i = 0;
    320             while (i < mSpanCount) {
    321                 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
    322                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
    323                         mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
    324                         mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
    325                         // This condition indicates that the span would become empty
    326                         (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
    327                     removeSpan(i);
    328                     continue; // do not increment i, spans will be shifted left in the array
    329                 }
    330 
    331                 i++;
    332             }
    333         }
    334 
    335         mGapStart += nbNewChars;
    336         mGapLength -= nbNewChars;
    337 
    338         if (mGapLength < 1)
    339             new Exception("mGapLength < 1").printStackTrace();
    340 
    341         TextUtils.getChars(cs, csStart, csEnd, mText, start);
    342 
    343         if (replacedLength > 0) { // no need for span fixup on pure insertion
    344             final boolean atEnd = (mGapStart + mGapLength == mText.length);
    345 
    346             for (int i = 0; i < mSpanCount; i++) {
    347                 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    348                 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
    349                         atEnd, textIsRemoved);
    350 
    351                 final int endFlag = (mSpanFlags[i] & END_MASK);
    352                 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
    353                         atEnd, textIsRemoved);
    354             }
    355         }
    356 
    357         mSpanCountBeforeAdd = mSpanCount;
    358 
    359         if (cs instanceof Spanned) {
    360             Spanned sp = (Spanned) cs;
    361             Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
    362 
    363             for (int i = 0; i < spans.length; i++) {
    364                 int st = sp.getSpanStart(spans[i]);
    365                 int en = sp.getSpanEnd(spans[i]);
    366 
    367                 if (st < csStart) st = csStart;
    368                 if (en > csEnd) en = csEnd;
    369 
    370                 // Add span only if this object is not yet used as a span in this string
    371                 if (getSpanStart(spans[i]) < 0) {
    372                     setSpan(false, spans[i], st - csStart + start, en - csStart + start,
    373                             sp.getSpanFlags(spans[i]));
    374                 }
    375             }
    376         }
    377     }
    378 
    379     private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
    380             boolean textIsRemoved) {
    381         if (offset >= start && offset < mGapStart + mGapLength) {
    382             if (flag == POINT) {
    383                 // A POINT located inside the replaced range should be moved to the end of the
    384                 // replaced text.
    385                 // The exception is when the point is at the start of the range and we are doing a
    386                 // text replacement (as opposed to a deletion): the point stays there.
    387                 if (textIsRemoved || offset > start) {
    388                     return mGapStart + mGapLength;
    389                 }
    390             } else {
    391                 if (flag == PARAGRAPH) {
    392                     if (atEnd) {
    393                         return mGapStart + mGapLength;
    394                     }
    395                 } else { // MARK
    396                     // MARKs should be moved to the start, with the exception of a mark located at
    397                     // the end of the range (which will be < mGapStart + mGapLength since mGapLength
    398                     // is > 0, which should stay 'unchanged' at the end of the replaced text.
    399                     if (textIsRemoved || offset < mGapStart - nbNewChars) {
    400                         return start;
    401                     } else {
    402                         // Move to the end of replaced text (needed if nbNewChars != 0)
    403                         return mGapStart;
    404                     }
    405                 }
    406             }
    407         }
    408         return offset;
    409     }
    410 
    411     private void removeSpan(int i) {
    412         Object object = mSpans[i];
    413 
    414         int start = mSpanStarts[i];
    415         int end = mSpanEnds[i];
    416 
    417         if (start > mGapStart) start -= mGapLength;
    418         if (end > mGapStart) end -= mGapLength;
    419 
    420         int count = mSpanCount - (i + 1);
    421         System.arraycopy(mSpans, i + 1, mSpans, i, count);
    422         System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
    423         System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
    424         System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
    425 
    426         mSpanCount--;
    427 
    428         mSpans[mSpanCount] = null;
    429 
    430         sendSpanRemoved(object, start, end);
    431     }
    432 
    433     // Documentation from interface
    434     public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
    435         return replace(start, end, tb, 0, tb.length());
    436     }
    437 
    438     // Documentation from interface
    439     public SpannableStringBuilder replace(final int start, final int end,
    440             CharSequence tb, int tbstart, int tbend) {
    441         checkRange("replace", start, end);
    442 
    443         int filtercount = mFilters.length;
    444         for (int i = 0; i < filtercount; i++) {
    445             CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
    446 
    447             if (repl != null) {
    448                 tb = repl;
    449                 tbstart = 0;
    450                 tbend = repl.length();
    451             }
    452         }
    453 
    454         final int origLen = end - start;
    455         final int newLen = tbend - tbstart;
    456 
    457         if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
    458             // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
    459             // Early exit so that the text watchers do not get notified
    460             return this;
    461         }
    462 
    463         TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
    464         sendBeforeTextChanged(textWatchers, start, origLen, newLen);
    465 
    466         // Try to keep the cursor / selection at the same relative position during
    467         // a text replacement. If replaced or replacement text length is zero, this
    468         // is already taken care of.
    469         boolean adjustSelection = origLen != 0 && newLen != 0;
    470         int selectionStart = 0;
    471         int selectionEnd = 0;
    472         if (adjustSelection) {
    473             selectionStart = Selection.getSelectionStart(this);
    474             selectionEnd = Selection.getSelectionEnd(this);
    475         }
    476 
    477         change(start, end, tb, tbstart, tbend);
    478 
    479         if (adjustSelection) {
    480             if (selectionStart > start && selectionStart < end) {
    481                 final int offset = (selectionStart - start) * newLen / origLen;
    482                 selectionStart = start + offset;
    483 
    484                 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
    485                         Spanned.SPAN_POINT_POINT);
    486             }
    487             if (selectionEnd > start && selectionEnd < end) {
    488                 final int offset = (selectionEnd - start) * newLen / origLen;
    489                 selectionEnd = start + offset;
    490 
    491                 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
    492                         Spanned.SPAN_POINT_POINT);
    493             }
    494         }
    495 
    496         sendTextChanged(textWatchers, start, origLen, newLen);
    497         sendAfterTextChanged(textWatchers);
    498 
    499         // Span watchers need to be called after text watchers, which may update the layout
    500         sendToSpanWatchers(start, end, newLen - origLen);
    501 
    502         return this;
    503     }
    504 
    505     private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
    506         if (text instanceof Spanned) {
    507             Spanned spanned = (Spanned) text;
    508             Object[] spans = spanned.getSpans(offset, offset, Object.class);
    509             final int length = spans.length;
    510             for (int i = 0; i < length; i++) {
    511                 Object span = spans[i];
    512                 int flags = spanned.getSpanFlags(span);
    513                 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
    514             }
    515         }
    516         return false;
    517     }
    518 
    519     private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
    520         for (int i = 0; i < mSpanCountBeforeAdd; i++) {
    521             int spanStart = mSpanStarts[i];
    522             int spanEnd = mSpanEnds[i];
    523             if (spanStart > mGapStart) spanStart -= mGapLength;
    524             if (spanEnd > mGapStart) spanEnd -= mGapLength;
    525             int spanFlags = mSpanFlags[i];
    526 
    527             int newReplaceEnd = replaceEnd + nbNewChars;
    528             boolean spanChanged = false;
    529 
    530             int previousSpanStart = spanStart;
    531             if (spanStart > newReplaceEnd) {
    532                 if (nbNewChars != 0) {
    533                     previousSpanStart -= nbNewChars;
    534                     spanChanged = true;
    535                 }
    536             } else if (spanStart >= replaceStart) {
    537                 // No change if span start was already at replace interval boundaries before replace
    538                 if ((spanStart != replaceStart ||
    539                         ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
    540                         (spanStart != newReplaceEnd ||
    541                         ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
    542                     // TODO A correct previousSpanStart cannot be computed at this point.
    543                     // It would require to save all the previous spans' positions before the replace
    544                     // Using an invalid -1 value to convey this would break the broacast range
    545                     spanChanged = true;
    546                 }
    547             }
    548 
    549             int previousSpanEnd = spanEnd;
    550             if (spanEnd > newReplaceEnd) {
    551                 if (nbNewChars != 0) {
    552                     previousSpanEnd -= nbNewChars;
    553                     spanChanged = true;
    554                 }
    555             } else if (spanEnd >= replaceStart) {
    556                 // No change if span start was already at replace interval boundaries before replace
    557                 if ((spanEnd != replaceStart ||
    558                         ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
    559                         (spanEnd != newReplaceEnd ||
    560                         ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
    561                     // TODO same as above for previousSpanEnd
    562                     spanChanged = true;
    563                 }
    564             }
    565 
    566             if (spanChanged) {
    567                 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
    568             }
    569             mSpanFlags[i] &= ~SPAN_START_END_MASK;
    570         }
    571 
    572         // The spans starting at mIntermediateSpanCount were added from the replacement text
    573         for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
    574             int spanStart = mSpanStarts[i];
    575             int spanEnd = mSpanEnds[i];
    576             if (spanStart > mGapStart) spanStart -= mGapLength;
    577             if (spanEnd > mGapStart) spanEnd -= mGapLength;
    578             sendSpanAdded(mSpans[i], spanStart, spanEnd);
    579         }
    580     }
    581 
    582     /**
    583      * Mark the specified range of text with the specified object.
    584      * The flags determine how the span will behave when text is
    585      * inserted at the start or end of the span's range.
    586      */
    587     public void setSpan(Object what, int start, int end, int flags) {
    588         setSpan(true, what, start, end, flags);
    589     }
    590 
    591     private void setSpan(boolean send, Object what, int start, int end, int flags) {
    592         checkRange("setSpan", start, end);
    593 
    594         int flagsStart = (flags & START_MASK) >> START_SHIFT;
    595         if (flagsStart == PARAGRAPH) {
    596             if (start != 0 && start != length()) {
    597                 char c = charAt(start - 1);
    598 
    599                 if (c != '\n')
    600                     throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
    601             }
    602         }
    603 
    604         int flagsEnd = flags & END_MASK;
    605         if (flagsEnd == PARAGRAPH) {
    606             if (end != 0 && end != length()) {
    607                 char c = charAt(end - 1);
    608 
    609                 if (c != '\n')
    610                     throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
    611             }
    612         }
    613 
    614         // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
    615         if (flagsStart == POINT && flagsEnd == MARK && start == end) {
    616             if (send) Log.e("SpannableStringBuilder",
    617                     "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
    618             // Silently ignore invalid spans when they are created from this class.
    619             // This avoids the duplication of the above test code before all the
    620             // calls to setSpan that are done in this class
    621             return;
    622         }
    623 
    624         int nstart = start;
    625         int nend = end;
    626 
    627         if (start > mGapStart) {
    628             start += mGapLength;
    629         } else if (start == mGapStart) {
    630             if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
    631                 start += mGapLength;
    632         }
    633 
    634         if (end > mGapStart) {
    635             end += mGapLength;
    636         } else if (end == mGapStart) {
    637             if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
    638                 end += mGapLength;
    639         }
    640 
    641         int count = mSpanCount;
    642         Object[] spans = mSpans;
    643 
    644         for (int i = 0; i < count; i++) {
    645             if (spans[i] == what) {
    646                 int ostart = mSpanStarts[i];
    647                 int oend = mSpanEnds[i];
    648 
    649                 if (ostart > mGapStart)
    650                     ostart -= mGapLength;
    651                 if (oend > mGapStart)
    652                     oend -= mGapLength;
    653 
    654                 mSpanStarts[i] = start;
    655                 mSpanEnds[i] = end;
    656                 mSpanFlags[i] = flags;
    657 
    658                 if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
    659 
    660                 return;
    661             }
    662         }
    663 
    664         if (mSpanCount + 1 >= mSpans.length) {
    665             int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
    666             Object[] newspans = new Object[newsize];
    667             int[] newspanstarts = new int[newsize];
    668             int[] newspanends = new int[newsize];
    669             int[] newspanflags = new int[newsize];
    670 
    671             System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
    672             System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
    673             System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
    674             System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
    675 
    676             mSpans = newspans;
    677             mSpanStarts = newspanstarts;
    678             mSpanEnds = newspanends;
    679             mSpanFlags = newspanflags;
    680         }
    681 
    682         mSpans[mSpanCount] = what;
    683         mSpanStarts[mSpanCount] = start;
    684         mSpanEnds[mSpanCount] = end;
    685         mSpanFlags[mSpanCount] = flags;
    686         mSpanCount++;
    687 
    688         if (send) sendSpanAdded(what, nstart, nend);
    689     }
    690 
    691     /**
    692      * Remove the specified markup object from the buffer.
    693      */
    694     public void removeSpan(Object what) {
    695         for (int i = mSpanCount - 1; i >= 0; i--) {
    696             if (mSpans[i] == what) {
    697                 removeSpan(i);
    698                 return;
    699             }
    700         }
    701     }
    702 
    703     /**
    704      * Return the buffer offset of the beginning of the specified
    705      * markup object, or -1 if it is not attached to this buffer.
    706      */
    707     public int getSpanStart(Object what) {
    708         int count = mSpanCount;
    709         Object[] spans = mSpans;
    710 
    711         for (int i = count - 1; i >= 0; i--) {
    712             if (spans[i] == what) {
    713                 int where = mSpanStarts[i];
    714 
    715                 if (where > mGapStart)
    716                     where -= mGapLength;
    717 
    718                 return where;
    719             }
    720         }
    721 
    722         return -1;
    723     }
    724 
    725     /**
    726      * Return the buffer offset of the end of the specified
    727      * markup object, or -1 if it is not attached to this buffer.
    728      */
    729     public int getSpanEnd(Object what) {
    730         int count = mSpanCount;
    731         Object[] spans = mSpans;
    732 
    733         for (int i = count - 1; i >= 0; i--) {
    734             if (spans[i] == what) {
    735                 int where = mSpanEnds[i];
    736 
    737                 if (where > mGapStart)
    738                     where -= mGapLength;
    739 
    740                 return where;
    741             }
    742         }
    743 
    744         return -1;
    745     }
    746 
    747     /**
    748      * Return the flags of the end of the specified
    749      * markup object, or 0 if it is not attached to this buffer.
    750      */
    751     public int getSpanFlags(Object what) {
    752         int count = mSpanCount;
    753         Object[] spans = mSpans;
    754 
    755         for (int i = count - 1; i >= 0; i--) {
    756             if (spans[i] == what) {
    757                 return mSpanFlags[i];
    758             }
    759         }
    760 
    761         return 0;
    762     }
    763 
    764     /**
    765      * Return an array of the spans of the specified type that overlap
    766      * the specified range of the buffer.  The kind may be Object.class to get
    767      * a list of all the spans regardless of type.
    768      */
    769     @SuppressWarnings("unchecked")
    770     public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
    771         if (kind == null) return ArrayUtils.emptyArray(kind);
    772 
    773         int spanCount = mSpanCount;
    774         Object[] spans = mSpans;
    775         int[] starts = mSpanStarts;
    776         int[] ends = mSpanEnds;
    777         int[] flags = mSpanFlags;
    778         int gapstart = mGapStart;
    779         int gaplen = mGapLength;
    780 
    781         int count = 0;
    782         T[] ret = null;
    783         T ret1 = null;
    784 
    785         for (int i = 0; i < spanCount; i++) {
    786             int spanStart = starts[i];
    787             if (spanStart > gapstart) {
    788                 spanStart -= gaplen;
    789             }
    790             if (spanStart > queryEnd) {
    791                 continue;
    792             }
    793 
    794             int spanEnd = ends[i];
    795             if (spanEnd > gapstart) {
    796                 spanEnd -= gaplen;
    797             }
    798             if (spanEnd < queryStart) {
    799                 continue;
    800             }
    801 
    802             if (spanStart != spanEnd && queryStart != queryEnd) {
    803                 if (spanStart == queryEnd)
    804                     continue;
    805                 if (spanEnd == queryStart)
    806                     continue;
    807             }
    808 
    809             // Expensive test, should be performed after the previous tests
    810             if (!kind.isInstance(spans[i])) continue;
    811 
    812             if (count == 0) {
    813                 // Safe conversion thanks to the isInstance test above
    814                 ret1 = (T) spans[i];
    815                 count++;
    816             } else {
    817                 if (count == 1) {
    818                     // Safe conversion, but requires a suppressWarning
    819                     ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
    820                     ret[0] = ret1;
    821                 }
    822 
    823                 int prio = flags[i] & SPAN_PRIORITY;
    824                 if (prio != 0) {
    825                     int j;
    826 
    827                     for (j = 0; j < count; j++) {
    828                         int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
    829 
    830                         if (prio > p) {
    831                             break;
    832                         }
    833                     }
    834 
    835                     System.arraycopy(ret, j, ret, j + 1, count - j);
    836                     // Safe conversion thanks to the isInstance test above
    837                     ret[j] = (T) spans[i];
    838                     count++;
    839                 } else {
    840                     // Safe conversion thanks to the isInstance test above
    841                     ret[count++] = (T) spans[i];
    842                 }
    843             }
    844         }
    845 
    846         if (count == 0) {
    847             return ArrayUtils.emptyArray(kind);
    848         }
    849         if (count == 1) {
    850             // Safe conversion, but requires a suppressWarning
    851             ret = (T[]) Array.newInstance(kind, 1);
    852             ret[0] = ret1;
    853             return ret;
    854         }
    855         if (count == ret.length) {
    856             return ret;
    857         }
    858 
    859         // Safe conversion, but requires a suppressWarning
    860         T[] nret = (T[]) Array.newInstance(kind, count);
    861         System.arraycopy(ret, 0, nret, 0, count);
    862         return nret;
    863     }
    864 
    865     /**
    866      * Return the next offset after <code>start</code> but less than or
    867      * equal to <code>limit</code> where a span of the specified type
    868      * begins or ends.
    869      */
    870     public int nextSpanTransition(int start, int limit, Class kind) {
    871         int count = mSpanCount;
    872         Object[] spans = mSpans;
    873         int[] starts = mSpanStarts;
    874         int[] ends = mSpanEnds;
    875         int gapstart = mGapStart;
    876         int gaplen = mGapLength;
    877 
    878         if (kind == null) {
    879             kind = Object.class;
    880         }
    881 
    882         for (int i = 0; i < count; i++) {
    883             int st = starts[i];
    884             int en = ends[i];
    885 
    886             if (st > gapstart)
    887                 st -= gaplen;
    888             if (en > gapstart)
    889                 en -= gaplen;
    890 
    891             if (st > start && st < limit && kind.isInstance(spans[i]))
    892                 limit = st;
    893             if (en > start && en < limit && kind.isInstance(spans[i]))
    894                 limit = en;
    895         }
    896 
    897         return limit;
    898     }
    899 
    900     /**
    901      * Return a new CharSequence containing a copy of the specified
    902      * range of this buffer, including the overlapping spans.
    903      */
    904     public CharSequence subSequence(int start, int end) {
    905         return new SpannableStringBuilder(this, start, end);
    906     }
    907 
    908     /**
    909      * Copy the specified range of chars from this buffer into the
    910      * specified array, beginning at the specified offset.
    911      */
    912     public void getChars(int start, int end, char[] dest, int destoff) {
    913         checkRange("getChars", start, end);
    914 
    915         if (end <= mGapStart) {
    916             System.arraycopy(mText, start, dest, destoff, end - start);
    917         } else if (start >= mGapStart) {
    918             System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
    919         } else {
    920             System.arraycopy(mText, start, dest, destoff, mGapStart - start);
    921             System.arraycopy(mText, mGapStart + mGapLength,
    922                     dest, destoff + (mGapStart - start),
    923                     end - mGapStart);
    924         }
    925     }
    926 
    927     /**
    928      * Return a String containing a copy of the chars in this buffer.
    929      */
    930     @Override
    931     public String toString() {
    932         int len = length();
    933         char[] buf = new char[len];
    934 
    935         getChars(0, len, buf, 0);
    936         return new String(buf);
    937     }
    938 
    939     /**
    940      * Return a String containing a copy of the chars in this buffer, limited to the
    941      * [start, end[ range.
    942      * @hide
    943      */
    944     public String substring(int start, int end) {
    945         char[] buf = new char[end - start];
    946         getChars(start, end, buf, 0);
    947         return new String(buf);
    948     }
    949 
    950     private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
    951         int n = watchers.length;
    952 
    953         for (int i = 0; i < n; i++) {
    954             watchers[i].beforeTextChanged(this, start, before, after);
    955         }
    956     }
    957 
    958     private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
    959         int n = watchers.length;
    960 
    961         for (int i = 0; i < n; i++) {
    962             watchers[i].onTextChanged(this, start, before, after);
    963         }
    964     }
    965 
    966     private void sendAfterTextChanged(TextWatcher[] watchers) {
    967         int n = watchers.length;
    968 
    969         for (int i = 0; i < n; i++) {
    970             watchers[i].afterTextChanged(this);
    971         }
    972     }
    973 
    974     private void sendSpanAdded(Object what, int start, int end) {
    975         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
    976         int n = recip.length;
    977 
    978         for (int i = 0; i < n; i++) {
    979             recip[i].onSpanAdded(this, what, start, end);
    980         }
    981     }
    982 
    983     private void sendSpanRemoved(Object what, int start, int end) {
    984         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
    985         int n = recip.length;
    986 
    987         for (int i = 0; i < n; i++) {
    988             recip[i].onSpanRemoved(this, what, start, end);
    989         }
    990     }
    991 
    992     private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
    993         // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
    994         // called, so that the order of the span does not affect this broadcast.
    995         SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
    996                 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
    997         int n = spanWatchers.length;
    998         for (int i = 0; i < n; i++) {
    999             spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
   1000         }
   1001     }
   1002 
   1003     private static String region(int start, int end) {
   1004         return "(" + start + " ... " + end + ")";
   1005     }
   1006 
   1007     private void checkRange(final String operation, int start, int end) {
   1008         if (end < start) {
   1009             throw new IndexOutOfBoundsException(operation + " " +
   1010                     region(start, end) + " has end before start");
   1011         }
   1012 
   1013         int len = length();
   1014 
   1015         if (start > len || end > len) {
   1016             throw new IndexOutOfBoundsException(operation + " " +
   1017                     region(start, end) + " ends beyond length " + len);
   1018         }
   1019 
   1020         if (start < 0 || end < 0) {
   1021             throw new IndexOutOfBoundsException(operation + " " +
   1022                     region(start, end) + " starts before 0");
   1023         }
   1024     }
   1025 
   1026     /*
   1027     private boolean isprint(char c) { // XXX
   1028         if (c >= ' ' && c <= '~')
   1029             return true;
   1030         else
   1031             return false;
   1032     }
   1033 
   1034     private static final int startFlag(int flag) {
   1035         return (flag >> 4) & 0x0F;
   1036     }
   1037 
   1038     private static final int endFlag(int flag) {
   1039         return flag & 0x0F;
   1040     }
   1041 
   1042     public void dump() { // XXX
   1043         for (int i = 0; i < mGapStart; i++) {
   1044             System.out.print('|');
   1045             System.out.print(' ');
   1046             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1047             System.out.print(' ');
   1048         }
   1049 
   1050         for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
   1051             System.out.print('|');
   1052             System.out.print('(');
   1053             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1054             System.out.print(')');
   1055         }
   1056 
   1057         for (int i = mGapStart + mGapLength; i < mText.length; i++) {
   1058             System.out.print('|');
   1059             System.out.print(' ');
   1060             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1061             System.out.print(' ');
   1062         }
   1063 
   1064         System.out.print('\n');
   1065 
   1066         for (int i = 0; i < mText.length + 1; i++) {
   1067             int found = 0;
   1068             int wfound = 0;
   1069 
   1070             for (int j = 0; j < mSpanCount; j++) {
   1071                 if (mSpanStarts[j] == i) {
   1072                     found = 1;
   1073                     wfound = j;
   1074                     break;
   1075                 }
   1076 
   1077                 if (mSpanEnds[j] == i) {
   1078                     found = 2;
   1079                     wfound = j;
   1080                     break;
   1081                 }
   1082             }
   1083 
   1084             if (found == 1) {
   1085                 if (startFlag(mSpanFlags[wfound]) == MARK)
   1086                     System.out.print("(   ");
   1087                 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1088                     System.out.print("<   ");
   1089                 else
   1090                     System.out.print("[   ");
   1091             } else if (found == 2) {
   1092                 if (endFlag(mSpanFlags[wfound]) == POINT)
   1093                     System.out.print(")   ");
   1094                 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1095                     System.out.print(">   ");
   1096                 else
   1097                     System.out.print("]   ");
   1098             } else {
   1099                 System.out.print("    ");
   1100             }
   1101         }
   1102 
   1103         System.out.print("\n");
   1104     }
   1105     */
   1106 
   1107     /**
   1108      * Don't call this yourself -- exists for Canvas to use internally.
   1109      * {@hide}
   1110      */
   1111     public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
   1112         checkRange("drawText", start, end);
   1113 
   1114         if (end <= mGapStart) {
   1115             c.drawText(mText, start, end - start, x, y, p);
   1116         } else if (start >= mGapStart) {
   1117             c.drawText(mText, start + mGapLength, end - start, x, y, p);
   1118         } else {
   1119             char[] buf = TextUtils.obtain(end - start);
   1120 
   1121             getChars(start, end, buf, 0);
   1122             c.drawText(buf, 0, end - start, x, y, p);
   1123             TextUtils.recycle(buf);
   1124         }
   1125     }
   1126 
   1127 
   1128     /**
   1129      * Don't call this yourself -- exists for Canvas to use internally.
   1130      * {@hide}
   1131      */
   1132     public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
   1133             float x, float y, int flags, Paint p) {
   1134         checkRange("drawTextRun", start, end);
   1135 
   1136         int contextLen = contextEnd - contextStart;
   1137         int len = end - start;
   1138         if (contextEnd <= mGapStart) {
   1139             c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
   1140         } else if (contextStart >= mGapStart) {
   1141             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
   1142                     contextLen, x, y, flags, p);
   1143         } else {
   1144             char[] buf = TextUtils.obtain(contextLen);
   1145             getChars(contextStart, contextEnd, buf, 0);
   1146             c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
   1147             TextUtils.recycle(buf);
   1148         }
   1149     }
   1150 
   1151     /**
   1152      * Don't call this yourself -- exists for Paint to use internally.
   1153      * {@hide}
   1154      */
   1155     public float measureText(int start, int end, Paint p) {
   1156         checkRange("measureText", start, end);
   1157 
   1158         float ret;
   1159 
   1160         if (end <= mGapStart) {
   1161             ret = p.measureText(mText, start, end - start);
   1162         } else if (start >= mGapStart) {
   1163             ret = p.measureText(mText, start + mGapLength, end - start);
   1164         } else {
   1165             char[] buf = TextUtils.obtain(end - start);
   1166 
   1167             getChars(start, end, buf, 0);
   1168             ret = p.measureText(buf, 0, end - start);
   1169             TextUtils.recycle(buf);
   1170         }
   1171 
   1172         return ret;
   1173     }
   1174 
   1175     /**
   1176      * Don't call this yourself -- exists for Paint to use internally.
   1177      * {@hide}
   1178      */
   1179     public int getTextWidths(int start, int end, float[] widths, Paint p) {
   1180         checkRange("getTextWidths", start, end);
   1181 
   1182         int ret;
   1183 
   1184         if (end <= mGapStart) {
   1185             ret = p.getTextWidths(mText, start, end - start, widths);
   1186         } else if (start >= mGapStart) {
   1187             ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
   1188         } else {
   1189             char[] buf = TextUtils.obtain(end - start);
   1190 
   1191             getChars(start, end, buf, 0);
   1192             ret = p.getTextWidths(buf, 0, end - start, widths);
   1193             TextUtils.recycle(buf);
   1194         }
   1195 
   1196         return ret;
   1197     }
   1198 
   1199     /**
   1200      * Don't call this yourself -- exists for Paint to use internally.
   1201      * {@hide}
   1202      */
   1203     public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
   1204             float[] advances, int advancesPos, Paint p) {
   1205 
   1206         float ret;
   1207 
   1208         int contextLen = contextEnd - contextStart;
   1209         int len = end - start;
   1210 
   1211         if (end <= mGapStart) {
   1212             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
   1213                     flags, advances, advancesPos);
   1214         } else if (start >= mGapStart) {
   1215             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
   1216                     contextStart + mGapLength, contextLen, flags, advances, advancesPos);
   1217         } else {
   1218             char[] buf = TextUtils.obtain(contextLen);
   1219             getChars(contextStart, contextEnd, buf, 0);
   1220             ret = p.getTextRunAdvances(buf, start - contextStart, len,
   1221                     0, contextLen, flags, advances, advancesPos);
   1222             TextUtils.recycle(buf);
   1223         }
   1224 
   1225         return ret;
   1226     }
   1227 
   1228     /**
   1229      * Returns the next cursor position in the run.  This avoids placing the cursor between
   1230      * surrogates, between characters that form conjuncts, between base characters and combining
   1231      * marks, or within a reordering cluster.
   1232      *
   1233      * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
   1234      * span enclosing the cursor in the direction of movement.
   1235      * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
   1236      * the start of the string.</p>
   1237      *
   1238      * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
   1239      * this returns -1.  Otherwise this will never return a value before contextStart or after
   1240      * contextEnd.</p>
   1241      *
   1242      * @param contextStart the start index of the context
   1243      * @param contextEnd the (non-inclusive) end index of the context
   1244      * @param flags either DIRECTION_RTL or DIRECTION_LTR
   1245      * @param offset the cursor position to move from
   1246      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
   1247      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
   1248      * CURSOR_AT_OR_BEFORE, or CURSOR_AT
   1249      * @param p the Paint object that is requesting this information
   1250      * @return the offset of the next position, or -1
   1251      * @deprecated This is an internal method, refrain from using it in your code
   1252      */
   1253     @Deprecated
   1254     public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
   1255             int cursorOpt, Paint p) {
   1256 
   1257         int ret;
   1258 
   1259         int contextLen = contextEnd - contextStart;
   1260         if (contextEnd <= mGapStart) {
   1261             ret = p.getTextRunCursor(mText, contextStart, contextLen,
   1262                     flags, offset, cursorOpt);
   1263         } else if (contextStart >= mGapStart) {
   1264             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
   1265                     flags, offset + mGapLength, cursorOpt) - mGapLength;
   1266         } else {
   1267             char[] buf = TextUtils.obtain(contextLen);
   1268             getChars(contextStart, contextEnd, buf, 0);
   1269             ret = p.getTextRunCursor(buf, 0, contextLen,
   1270                     flags, offset - contextStart, cursorOpt) + contextStart;
   1271             TextUtils.recycle(buf);
   1272         }
   1273 
   1274         return ret;
   1275     }
   1276 
   1277     // Documentation from interface
   1278     public void setFilters(InputFilter[] filters) {
   1279         if (filters == null) {
   1280             throw new IllegalArgumentException();
   1281         }
   1282 
   1283         mFilters = filters;
   1284     }
   1285 
   1286     // Documentation from interface
   1287     public InputFilter[] getFilters() {
   1288         return mFilters;
   1289     }
   1290 
   1291     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
   1292     private InputFilter[] mFilters = NO_FILTERS;
   1293 
   1294     private char[] mText;
   1295     private int mGapStart;
   1296     private int mGapLength;
   1297 
   1298     private Object[] mSpans;
   1299     private int[] mSpanStarts;
   1300     private int[] mSpanEnds;
   1301     private int[] mSpanFlags;
   1302     private int mSpanCount;
   1303     private int mSpanCountBeforeAdd;
   1304 
   1305     // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
   1306     private static final int MARK = 1;
   1307     private static final int POINT = 2;
   1308     private static final int PARAGRAPH = 3;
   1309 
   1310     private static final int START_MASK = 0xF0;
   1311     private static final int END_MASK = 0x0F;
   1312     private static final int START_SHIFT = 4;
   1313 
   1314     // These bits are not (currently) used by SPANNED flags
   1315     private static final int SPAN_START_AT_START = 0x1000;
   1316     private static final int SPAN_START_AT_END = 0x2000;
   1317     private static final int SPAN_END_AT_START = 0x4000;
   1318     private static final int SPAN_END_AT_END = 0x8000;
   1319     private static final int SPAN_START_END_MASK = 0xF000;
   1320 }
   1321