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