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