SpannableStringBuilder.java revision b062e81e3a16af43db3619d721aa522c137d1aa9
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 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 private TextWatcher[] sendTextWillChange(int start, int before, int after) { 867 TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class); 868 int n = recip.length; 869 870 for (int i = 0; i < n; i++) { 871 recip[i].beforeTextChanged(this, start, before, after); 872 } 873 874 return recip; 875 } 876 877 private void sendTextChange(TextWatcher[] recip, int start, int before, int after) { 878 int n = recip.length; 879 880 for (int i = 0; i < n; i++) { 881 recip[i].onTextChanged(this, start, before, after); 882 } 883 } 884 885 private void sendTextHasChanged(TextWatcher[] recip) { 886 int n = recip.length; 887 888 for (int i = 0; i < n; i++) { 889 recip[i].afterTextChanged(this); 890 } 891 } 892 893 private void sendSpanAdded(Object what, int start, int end) { 894 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 895 int n = recip.length; 896 897 for (int i = 0; i < n; i++) { 898 recip[i].onSpanAdded(this, what, start, end); 899 } 900 } 901 902 private void sendSpanRemoved(Object what, int start, int end) { 903 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 904 int n = recip.length; 905 906 for (int i = 0; i < n; i++) { 907 recip[i].onSpanRemoved(this, what, start, end); 908 } 909 } 910 911 private void sendSpanChanged(Object what, int s, int e, int st, int en) { 912 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class); 913 int n = recip.length; 914 915 for (int i = 0; i < n; i++) { 916 recip[i].onSpanChanged(this, what, s, e, st, en); 917 } 918 } 919 920 private static String region(int start, int end) { 921 return "(" + start + " ... " + end + ")"; 922 } 923 924 private void checkRange(final String operation, int start, int end) { 925 if (end < start) { 926 throw new IndexOutOfBoundsException(operation + " " + 927 region(start, end) + 928 " has end before start"); 929 } 930 931 int len = length(); 932 933 if (start > len || end > len) { 934 throw new IndexOutOfBoundsException(operation + " " + 935 region(start, end) + 936 " ends beyond length " + len); 937 } 938 939 if (start < 0 || end < 0) { 940 throw new IndexOutOfBoundsException(operation + " " + 941 region(start, end) + 942 " starts before 0"); 943 } 944 } 945 946/* 947 private boolean isprint(char c) { // XXX 948 if (c >= ' ' && c <= '~') 949 return true; 950 else 951 return false; 952 } 953 954 private static final int startFlag(int flag) { 955 return (flag >> 4) & 0x0F; 956 } 957 958 private static final int endFlag(int flag) { 959 return flag & 0x0F; 960 } 961 962 public void dump() { // XXX 963 for (int i = 0; i < mGapStart; i++) { 964 System.out.print('|'); 965 System.out.print(' '); 966 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 967 System.out.print(' '); 968 } 969 970 for (int i = mGapStart; i < mGapStart + mGapLength; i++) { 971 System.out.print('|'); 972 System.out.print('('); 973 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 974 System.out.print(')'); 975 } 976 977 for (int i = mGapStart + mGapLength; i < mText.length; i++) { 978 System.out.print('|'); 979 System.out.print(' '); 980 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 981 System.out.print(' '); 982 } 983 984 System.out.print('\n'); 985 986 for (int i = 0; i < mText.length + 1; i++) { 987 int found = 0; 988 int wfound = 0; 989 990 for (int j = 0; j < mSpanCount; j++) { 991 if (mSpanStarts[j] == i) { 992 found = 1; 993 wfound = j; 994 break; 995 } 996 997 if (mSpanEnds[j] == i) { 998 found = 2; 999 wfound = j; 1000 break; 1001 } 1002 } 1003 1004 if (found == 1) { 1005 if (startFlag(mSpanFlags[wfound]) == MARK) 1006 System.out.print("( "); 1007 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH) 1008 System.out.print("< "); 1009 else 1010 System.out.print("[ "); 1011 } else if (found == 2) { 1012 if (endFlag(mSpanFlags[wfound]) == POINT) 1013 System.out.print(") "); 1014 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH) 1015 System.out.print("> "); 1016 else 1017 System.out.print("] "); 1018 } else { 1019 System.out.print(" "); 1020 } 1021 } 1022 1023 System.out.print("\n"); 1024 } 1025*/ 1026 1027 /** 1028 * Don't call this yourself -- exists for Canvas to use internally. 1029 * {@hide} 1030 */ 1031 public void drawText(Canvas c, int start, int end, 1032 float x, float y, Paint p) { 1033 checkRange("drawText", start, end); 1034 1035 if (end <= mGapStart) { 1036 c.drawText(mText, start, end - start, x, y, p); 1037 } else if (start >= mGapStart) { 1038 c.drawText(mText, start + mGapLength, end - start, x, y, p); 1039 } else { 1040 char[] buf = TextUtils.obtain(end - start); 1041 1042 getChars(start, end, buf, 0); 1043 c.drawText(buf, 0, end - start, x, y, p); 1044 TextUtils.recycle(buf); 1045 } 1046 } 1047 1048 1049 /** 1050 * Don't call this yourself -- exists for Canvas to use internally. 1051 * {@hide} 1052 */ 1053 public void drawTextRun(Canvas c, int start, int end, 1054 int contextStart, int contextEnd, 1055 float x, float y, int flags, Paint p) { 1056 checkRange("drawTextRun", start, end); 1057 1058 int contextLen = contextEnd - contextStart; 1059 int len = end - start; 1060 if (contextEnd <= mGapStart) { 1061 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p); 1062 } else if (contextStart >= mGapStart) { 1063 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength, 1064 contextLen, x, y, flags, p); 1065 } else { 1066 char[] buf = TextUtils.obtain(contextLen); 1067 getChars(contextStart, contextEnd, buf, 0); 1068 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p); 1069 TextUtils.recycle(buf); 1070 } 1071 } 1072 1073 /** 1074 * Don't call this yourself -- exists for Paint to use internally. 1075 * {@hide} 1076 */ 1077 public float measureText(int start, int end, Paint p) { 1078 checkRange("measureText", start, end); 1079 1080 float ret; 1081 1082 if (end <= mGapStart) { 1083 ret = p.measureText(mText, start, end - start); 1084 } else if (start >= mGapStart) { 1085 ret = p.measureText(mText, start + mGapLength, end - start); 1086 } else { 1087 char[] buf = TextUtils.obtain(end - start); 1088 1089 getChars(start, end, buf, 0); 1090 ret = p.measureText(buf, 0, end - start); 1091 TextUtils.recycle(buf); 1092 } 1093 1094 return ret; 1095 } 1096 1097 /** 1098 * Don't call this yourself -- exists for Paint to use internally. 1099 * {@hide} 1100 */ 1101 public int getTextWidths(int start, int end, float[] widths, Paint p) { 1102 checkRange("getTextWidths", start, end); 1103 1104 int ret; 1105 1106 if (end <= mGapStart) { 1107 ret = p.getTextWidths(mText, start, end - start, widths); 1108 } else if (start >= mGapStart) { 1109 ret = p.getTextWidths(mText, start + mGapLength, end - start, 1110 widths); 1111 } else { 1112 char[] buf = TextUtils.obtain(end - start); 1113 1114 getChars(start, end, buf, 0); 1115 ret = p.getTextWidths(buf, 0, end - start, widths); 1116 TextUtils.recycle(buf); 1117 } 1118 1119 return ret; 1120 } 1121 1122 /** 1123 * Don't call this yourself -- exists for Paint to use internally. 1124 * {@hide} 1125 */ 1126 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, 1127 float[] advances, int advancesPos, Paint p) { 1128 1129 float ret; 1130 1131 int contextLen = contextEnd - contextStart; 1132 int len = end - start; 1133 1134 if (end <= mGapStart) { 1135 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, 1136 flags, advances, advancesPos); 1137 } else if (start >= mGapStart) { 1138 ret = p.getTextRunAdvances(mText, start + mGapLength, len, 1139 contextStart + mGapLength, contextLen, flags, advances, advancesPos); 1140 } else { 1141 char[] buf = TextUtils.obtain(contextLen); 1142 getChars(contextStart, contextEnd, buf, 0); 1143 ret = p.getTextRunAdvances(buf, start - contextStart, len, 1144 0, contextLen, flags, advances, advancesPos); 1145 TextUtils.recycle(buf); 1146 } 1147 1148 return ret; 1149 } 1150 1151 /** 1152 * Don't call this yourself -- exists for Paint to use internally. 1153 * {@hide} 1154 */ 1155 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, 1156 float[] advances, int advancesPos, Paint p, int reserved) { 1157 1158 float ret; 1159 1160 int contextLen = contextEnd - contextStart; 1161 int len = end - start; 1162 1163 if (end <= mGapStart) { 1164 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, 1165 flags, advances, advancesPos, reserved); 1166 } else if (start >= mGapStart) { 1167 ret = p.getTextRunAdvances(mText, start + mGapLength, len, 1168 contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved); 1169 } else { 1170 char[] buf = TextUtils.obtain(contextLen); 1171 getChars(contextStart, contextEnd, buf, 0); 1172 ret = p.getTextRunAdvances(buf, start - contextStart, len, 1173 0, contextLen, flags, advances, advancesPos, reserved); 1174 TextUtils.recycle(buf); 1175 } 1176 1177 return ret; 1178 } 1179 1180 /** 1181 * Returns the next cursor position in the run. This avoids placing the cursor between 1182 * surrogates, between characters that form conjuncts, between base characters and combining 1183 * marks, or within a reordering cluster. 1184 * 1185 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric 1186 * span enclosing the cursor in the direction of movement. 1187 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to 1188 * the start of the string.</p> 1189 * 1190 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position, 1191 * this returns -1. Otherwise this will never return a value before contextStart or after 1192 * contextEnd.</p> 1193 * 1194 * @param contextStart the start index of the context 1195 * @param contextEnd the (non-inclusive) end index of the context 1196 * @param flags either DIRECTION_RTL or DIRECTION_LTR 1197 * @param offset the cursor position to move from 1198 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER, 1199 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE, 1200 * CURSOR_AT_OR_BEFORE, or CURSOR_AT 1201 * @param p the Paint object that is requesting this information 1202 * @return the offset of the next position, or -1 1203 * @deprecated This is an internal method, refrain from using it in your code 1204 */ 1205 @Deprecated 1206 public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, 1207 int cursorOpt, Paint p) { 1208 1209 int ret; 1210 1211 int contextLen = contextEnd - contextStart; 1212 if (contextEnd <= mGapStart) { 1213 ret = p.getTextRunCursor(mText, contextStart, contextLen, 1214 flags, offset, cursorOpt); 1215 } else if (contextStart >= mGapStart) { 1216 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen, 1217 flags, offset + mGapLength, cursorOpt) - mGapLength; 1218 } else { 1219 char[] buf = TextUtils.obtain(contextLen); 1220 getChars(contextStart, contextEnd, buf, 0); 1221 ret = p.getTextRunCursor(buf, 0, contextLen, 1222 flags, offset - contextStart, cursorOpt) + contextStart; 1223 TextUtils.recycle(buf); 1224 } 1225 1226 return ret; 1227 } 1228 1229 // Documentation from interface 1230 public void setFilters(InputFilter[] filters) { 1231 if (filters == null) { 1232 throw new IllegalArgumentException(); 1233 } 1234 1235 mFilters = filters; 1236 } 1237 1238 // Documentation from interface 1239 public InputFilter[] getFilters() { 1240 return mFilters; 1241 } 1242 1243 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 1244 private InputFilter[] mFilters = NO_FILTERS; 1245 1246 private char[] mText; 1247 private int mGapStart; 1248 private int mGapLength; 1249 1250 private Object[] mSpans; 1251 private int[] mSpanStarts; 1252 private int[] mSpanEnds; 1253 private int[] mSpanFlags; 1254 private int mSpanCount; 1255 1256 private static final int POINT = 2; 1257 private static final int PARAGRAPH = 3; 1258 1259 private static final int START_MASK = 0xF0; 1260 private static final int END_MASK = 0x0F; 1261 private static final int START_SHIFT = 4; 1262} 1263