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