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