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