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