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