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