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 22 import com.android.internal.util.ArrayUtils; 23 24 import java.lang.reflect.Array; 25 26 /** 27 * This is the class for text whose content and markup can both be changed. 28 */ 29 public 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 if (spanStart > gapstart) { 714 spanStart -= gaplen; 715 } 716 if (spanStart > queryEnd) { 717 continue; 718 } 719 720 int spanEnd = ends[i]; 721 if (spanEnd > gapstart) { 722 spanEnd -= gaplen; 723 } 724 if (spanEnd < queryStart) { 725 continue; 726 } 727 728 if (spanStart != spanEnd && queryStart != queryEnd) { 729 if (spanStart == queryEnd) 730 continue; 731 if (spanEnd == queryStart) 732 continue; 733 } 734 735 // Expensive test, should be performed after the previous tests 736 if (!kind.isInstance(spans[i])) continue; 737 738 if (count == 0) { 739 // Safe conversion thanks to the isInstance test above 740 ret1 = (T) spans[i]; 741 count++; 742 } else { 743 if (count == 1) { 744 // Safe conversion, but requires a suppressWarning 745 ret = (T[]) Array.newInstance(kind, spanCount - i + 1); 746 ret[0] = ret1; 747 } 748 749 int prio = flags[i] & SPAN_PRIORITY; 750 if (prio != 0) { 751 int j; 752 753 for (j = 0; j < count; j++) { 754 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY; 755 756 if (prio > p) { 757 break; 758 } 759 } 760 761 System.arraycopy(ret, j, ret, j + 1, count - j); 762 // Safe conversion thanks to the isInstance test above 763 ret[j] = (T) spans[i]; 764 count++; 765 } else { 766 // Safe conversion thanks to the isInstance test above 767 ret[count++] = (T) spans[i]; 768 } 769 } 770 } 771 772 if (count == 0) { 773 return ArrayUtils.emptyArray(kind); 774 } 775 if (count == 1) { 776 // Safe conversion, but requires a suppressWarning 777 ret = (T[]) Array.newInstance(kind, 1); 778 ret[0] = ret1; 779 return ret; 780 } 781 if (count == ret.length) { 782 return ret; 783 } 784 785 // Safe conversion, but requires a suppressWarning 786 T[] nret = (T[]) Array.newInstance(kind, count); 787 System.arraycopy(ret, 0, nret, 0, count); 788 return nret; 789 } 790 791 /** 792 * Return the next offset after <code>start</code> but less than or 793 * equal to <code>limit</code> where a span of the specified type 794 * begins or ends. 795 */ 796 public int nextSpanTransition(int start, int limit, Class kind) { 797 int count = mSpanCount; 798 Object[] spans = mSpans; 799 int[] starts = mSpanStarts; 800 int[] ends = mSpanEnds; 801 int gapstart = mGapStart; 802 int gaplen = mGapLength; 803 804 if (kind == null) { 805 kind = Object.class; 806 } 807 808 for (int i = 0; i < count; i++) { 809 int st = starts[i]; 810 int en = ends[i]; 811 812 if (st > gapstart) 813 st -= gaplen; 814 if (en > gapstart) 815 en -= gaplen; 816 817 if (st > start && st < limit && kind.isInstance(spans[i])) 818 limit = st; 819 if (en > start && en < limit && kind.isInstance(spans[i])) 820 limit = en; 821 } 822 823 return limit; 824 } 825 826 /** 827 * Return a new CharSequence containing a copy of the specified 828 * range of this buffer, including the overlapping spans. 829 */ 830 public CharSequence subSequence(int start, int end) { 831 return new SpannableStringBuilder(this, start, end); 832 } 833 834 /** 835 * Copy the specified range of chars from this buffer into the 836 * specified array, beginning at the specified offset. 837 */ 838 public void getChars(int start, int end, char[] dest, int destoff) { 839 checkRange("getChars", start, end); 840 841 if (end <= mGapStart) { 842 System.arraycopy(mText, start, dest, destoff, end - start); 843 } else if (start >= mGapStart) { 844 System.arraycopy(mText, start + mGapLength, 845 dest, destoff, end - start); 846 } else { 847 System.arraycopy(mText, start, dest, destoff, mGapStart - start); 848 System.arraycopy(mText, mGapStart + mGapLength, 849 dest, destoff + (mGapStart - start), 850 end - mGapStart); 851 } 852 } 853 854 /** 855 * Return a String containing a copy of the chars in this buffer. 856 */ 857 @Override 858 public String toString() { 859 int len = length(); 860 char[] buf = new char[len]; 861 862 getChars(0, len, buf, 0); 863 return new String(buf); 864 } 865 866 /** 867 * Return a String containing a copy of the chars in this buffer, limited to the 868 * [start, end[ range. 869 * @hide 870 */ 871 public String substring(int start, int end) { 872 char[] buf = new char[end - start]; 873 getChars(start, end, buf, 0); 874 return new String(buf); 875 } 876 877 private TextWatcher[] sendTextWillChange(int start, int before, int after) { 878 TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class); 879 int n = recip.length; 880 881 for (int i = 0; i < n; i++) { 882 recip[i].beforeTextChanged(this, start, before, after); 883 } 884 885 return recip; 886 } 887 888 private void sendTextChange(TextWatcher[] recip, int start, int before, int after) { 889 int n = recip.length; 890 891 for (int i = 0; i < n; i++) { 892 recip[i].onTextChanged(this, start, before, after); 893 } 894 } 895 896 private void sendTextHasChanged(TextWatcher[] recip) { 897 int n = recip.length; 898 899 for (int i = 0; i < n; i++) { 900 recip[i].afterTextChanged(this); 901 } 902 } 903 904 private void sendSpanAdded(Object what, int start, int end) { 905 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 906 int n = recip.length; 907 908 for (int i = 0; i < n; i++) { 909 recip[i].onSpanAdded(this, what, start, end); 910 } 911 } 912 913 private void sendSpanRemoved(Object what, int start, int end) { 914 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 915 int n = recip.length; 916 917 for (int i = 0; i < n; i++) { 918 recip[i].onSpanRemoved(this, what, start, end); 919 } 920 } 921 922 private void sendSpanChanged(Object what, int s, int e, int st, int en) { 923 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class); 924 int n = recip.length; 925 926 for (int i = 0; i < n; i++) { 927 recip[i].onSpanChanged(this, what, s, e, st, en); 928 } 929 } 930 931 private static String region(int start, int end) { 932 return "(" + start + " ... " + end + ")"; 933 } 934 935 private void checkRange(final String operation, int start, int end) { 936 if (end < start) { 937 throw new IndexOutOfBoundsException(operation + " " + 938 region(start, end) + 939 " has end before start"); 940 } 941 942 int len = length(); 943 944 if (start > len || end > len) { 945 throw new IndexOutOfBoundsException(operation + " " + 946 region(start, end) + 947 " ends beyond length " + len); 948 } 949 950 if (start < 0 || end < 0) { 951 throw new IndexOutOfBoundsException(operation + " " + 952 region(start, end) + 953 " starts before 0"); 954 } 955 } 956 957 /* 958 private boolean isprint(char c) { // XXX 959 if (c >= ' ' && c <= '~') 960 return true; 961 else 962 return false; 963 } 964 965 private static final int startFlag(int flag) { 966 return (flag >> 4) & 0x0F; 967 } 968 969 private static final int endFlag(int flag) { 970 return flag & 0x0F; 971 } 972 973 public void dump() { // XXX 974 for (int i = 0; i < mGapStart; i++) { 975 System.out.print('|'); 976 System.out.print(' '); 977 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 978 System.out.print(' '); 979 } 980 981 for (int i = mGapStart; i < mGapStart + mGapLength; i++) { 982 System.out.print('|'); 983 System.out.print('('); 984 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 985 System.out.print(')'); 986 } 987 988 for (int i = mGapStart + mGapLength; i < mText.length; i++) { 989 System.out.print('|'); 990 System.out.print(' '); 991 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 992 System.out.print(' '); 993 } 994 995 System.out.print('\n'); 996 997 for (int i = 0; i < mText.length + 1; i++) { 998 int found = 0; 999 int wfound = 0; 1000 1001 for (int j = 0; j < mSpanCount; j++) { 1002 if (mSpanStarts[j] == i) { 1003 found = 1; 1004 wfound = j; 1005 break; 1006 } 1007 1008 if (mSpanEnds[j] == i) { 1009 found = 2; 1010 wfound = j; 1011 break; 1012 } 1013 } 1014 1015 if (found == 1) { 1016 if (startFlag(mSpanFlags[wfound]) == MARK) 1017 System.out.print("( "); 1018 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH) 1019 System.out.print("< "); 1020 else 1021 System.out.print("[ "); 1022 } else if (found == 2) { 1023 if (endFlag(mSpanFlags[wfound]) == POINT) 1024 System.out.print(") "); 1025 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH) 1026 System.out.print("> "); 1027 else 1028 System.out.print("] "); 1029 } else { 1030 System.out.print(" "); 1031 } 1032 } 1033 1034 System.out.print("\n"); 1035 } 1036 */ 1037 1038 /** 1039 * Don't call this yourself -- exists for Canvas to use internally. 1040 * {@hide} 1041 */ 1042 public void drawText(Canvas c, int start, int end, 1043 float x, float y, Paint p) { 1044 checkRange("drawText", start, end); 1045 1046 if (end <= mGapStart) { 1047 c.drawText(mText, start, end - start, x, y, p); 1048 } else if (start >= mGapStart) { 1049 c.drawText(mText, start + mGapLength, end - start, x, y, p); 1050 } else { 1051 char[] buf = TextUtils.obtain(end - start); 1052 1053 getChars(start, end, buf, 0); 1054 c.drawText(buf, 0, end - start, x, y, p); 1055 TextUtils.recycle(buf); 1056 } 1057 } 1058 1059 1060 /** 1061 * Don't call this yourself -- exists for Canvas to use internally. 1062 * {@hide} 1063 */ 1064 public void drawTextRun(Canvas c, int start, int end, 1065 int contextStart, int contextEnd, 1066 float x, float y, int flags, Paint p) { 1067 checkRange("drawTextRun", start, end); 1068 1069 int contextLen = contextEnd - contextStart; 1070 int len = end - start; 1071 if (contextEnd <= mGapStart) { 1072 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p); 1073 } else if (contextStart >= mGapStart) { 1074 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength, 1075 contextLen, x, y, flags, p); 1076 } else { 1077 char[] buf = TextUtils.obtain(contextLen); 1078 getChars(contextStart, contextEnd, buf, 0); 1079 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p); 1080 TextUtils.recycle(buf); 1081 } 1082 } 1083 1084 /** 1085 * Don't call this yourself -- exists for Paint to use internally. 1086 * {@hide} 1087 */ 1088 public float measureText(int start, int end, Paint p) { 1089 checkRange("measureText", start, end); 1090 1091 float ret; 1092 1093 if (end <= mGapStart) { 1094 ret = p.measureText(mText, start, end - start); 1095 } else if (start >= mGapStart) { 1096 ret = p.measureText(mText, start + mGapLength, end - start); 1097 } else { 1098 char[] buf = TextUtils.obtain(end - start); 1099 1100 getChars(start, end, buf, 0); 1101 ret = p.measureText(buf, 0, end - start); 1102 TextUtils.recycle(buf); 1103 } 1104 1105 return ret; 1106 } 1107 1108 /** 1109 * Don't call this yourself -- exists for Paint to use internally. 1110 * {@hide} 1111 */ 1112 public int getTextWidths(int start, int end, float[] widths, Paint p) { 1113 checkRange("getTextWidths", start, end); 1114 1115 int ret; 1116 1117 if (end <= mGapStart) { 1118 ret = p.getTextWidths(mText, start, end - start, widths); 1119 } else if (start >= mGapStart) { 1120 ret = p.getTextWidths(mText, start + mGapLength, end - start, 1121 widths); 1122 } else { 1123 char[] buf = TextUtils.obtain(end - start); 1124 1125 getChars(start, end, buf, 0); 1126 ret = p.getTextWidths(buf, 0, end - start, widths); 1127 TextUtils.recycle(buf); 1128 } 1129 1130 return ret; 1131 } 1132 1133 /** 1134 * Don't call this yourself -- exists for Paint to use internally. 1135 * {@hide} 1136 */ 1137 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, 1138 float[] advances, int advancesPos, Paint p) { 1139 1140 float ret; 1141 1142 int contextLen = contextEnd - contextStart; 1143 int len = end - start; 1144 1145 if (end <= mGapStart) { 1146 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, 1147 flags, advances, advancesPos); 1148 } else if (start >= mGapStart) { 1149 ret = p.getTextRunAdvances(mText, start + mGapLength, len, 1150 contextStart + mGapLength, contextLen, flags, advances, advancesPos); 1151 } else { 1152 char[] buf = TextUtils.obtain(contextLen); 1153 getChars(contextStart, contextEnd, buf, 0); 1154 ret = p.getTextRunAdvances(buf, start - contextStart, len, 1155 0, contextLen, flags, advances, advancesPos); 1156 TextUtils.recycle(buf); 1157 } 1158 1159 return ret; 1160 } 1161 1162 /** 1163 * Don't call this yourself -- exists for Paint to use internally. 1164 * {@hide} 1165 */ 1166 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, 1167 float[] advances, int advancesPos, Paint p, int reserved) { 1168 1169 float ret; 1170 1171 int contextLen = contextEnd - contextStart; 1172 int len = end - start; 1173 1174 if (end <= mGapStart) { 1175 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, 1176 flags, advances, advancesPos, reserved); 1177 } else if (start >= mGapStart) { 1178 ret = p.getTextRunAdvances(mText, start + mGapLength, len, 1179 contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved); 1180 } else { 1181 char[] buf = TextUtils.obtain(contextLen); 1182 getChars(contextStart, contextEnd, buf, 0); 1183 ret = p.getTextRunAdvances(buf, start - contextStart, len, 1184 0, contextLen, flags, advances, advancesPos, reserved); 1185 TextUtils.recycle(buf); 1186 } 1187 1188 return ret; 1189 } 1190 1191 /** 1192 * Returns the next cursor position in the run. This avoids placing the cursor between 1193 * surrogates, between characters that form conjuncts, between base characters and combining 1194 * marks, or within a reordering cluster. 1195 * 1196 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric 1197 * span enclosing the cursor in the direction of movement. 1198 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to 1199 * the start of the string.</p> 1200 * 1201 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position, 1202 * this returns -1. Otherwise this will never return a value before contextStart or after 1203 * contextEnd.</p> 1204 * 1205 * @param contextStart the start index of the context 1206 * @param contextEnd the (non-inclusive) end index of the context 1207 * @param flags either DIRECTION_RTL or DIRECTION_LTR 1208 * @param offset the cursor position to move from 1209 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER, 1210 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE, 1211 * CURSOR_AT_OR_BEFORE, or CURSOR_AT 1212 * @param p the Paint object that is requesting this information 1213 * @return the offset of the next position, or -1 1214 * @deprecated This is an internal method, refrain from using it in your code 1215 */ 1216 @Deprecated 1217 public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, 1218 int cursorOpt, Paint p) { 1219 1220 int ret; 1221 1222 int contextLen = contextEnd - contextStart; 1223 if (contextEnd <= mGapStart) { 1224 ret = p.getTextRunCursor(mText, contextStart, contextLen, 1225 flags, offset, cursorOpt); 1226 } else if (contextStart >= mGapStart) { 1227 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen, 1228 flags, offset + mGapLength, cursorOpt) - mGapLength; 1229 } else { 1230 char[] buf = TextUtils.obtain(contextLen); 1231 getChars(contextStart, contextEnd, buf, 0); 1232 ret = p.getTextRunCursor(buf, 0, contextLen, 1233 flags, offset - contextStart, cursorOpt) + contextStart; 1234 TextUtils.recycle(buf); 1235 } 1236 1237 return ret; 1238 } 1239 1240 // Documentation from interface 1241 public void setFilters(InputFilter[] filters) { 1242 if (filters == null) { 1243 throw new IllegalArgumentException(); 1244 } 1245 1246 mFilters = filters; 1247 } 1248 1249 // Documentation from interface 1250 public InputFilter[] getFilters() { 1251 return mFilters; 1252 } 1253 1254 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 1255 private InputFilter[] mFilters = NO_FILTERS; 1256 1257 private char[] mText; 1258 private int mGapStart; 1259 private int mGapLength; 1260 1261 private Object[] mSpans; 1262 private int[] mSpanStarts; 1263 private int[] mSpanEnds; 1264 private int[] mSpanFlags; 1265 private int mSpanCount; 1266 1267 private static final int POINT = 2; 1268 private static final int PARAGRAPH = 3; 1269 1270 private static final int START_MASK = 0xF0; 1271 private static final int END_MASK = 0x0F; 1272 private static final int START_SHIFT = 4; 1273 } 1274