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