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