blob: caaafa1473976ea153e614e36df24da26b9b81a2 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.text;
import com.android.internal.util.ArrayUtils;
import android.graphics.Paint;
import android.graphics.Canvas;
import java.lang.reflect.Array;
/**
* This is the class for text whose content and markup can both be changed.
*/
public class SpannableStringBuilder
implements CharSequence, GetChars, Spannable, Editable, Appendable,
GraphicsOperations
{
/**
* Create a new SpannableStringBuilder with empty contents
*/
public SpannableStringBuilder() {
this("");
}
/**
* Create a new SpannableStringBuilder containing a copy of the
* specified text, including its spans if any.
*/
public SpannableStringBuilder(CharSequence text) {
this(text, 0, text.length());
}
/**
* Create a new SpannableStringBuilder containing a copy of the
* specified slice of the specified text, including its spans if any.
*/
public SpannableStringBuilder(CharSequence text, int start, int end) {
int srclen = end - start;
int len = ArrayUtils.idealCharArraySize(srclen + 1);
mText = new char[len];
mGapStart = srclen;
mGapLength = len - srclen;
TextUtils.getChars(text, start, end, mText, 0);
mSpanCount = 0;
int alloc = ArrayUtils.idealIntArraySize(0);
mSpans = new Object[alloc];
mSpanStarts = new int[alloc];
mSpanEnds = new int[alloc];
mSpanFlags = new int[alloc];
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
Object[] spans = sp.getSpans(start, end, Object.class);
for (int i = 0; i < spans.length; i++) {
if (spans[i] instanceof NoCopySpan) {
continue;
}
int st = sp.getSpanStart(spans[i]) - start;
int en = sp.getSpanEnd(spans[i]) - start;
int fl = sp.getSpanFlags(spans[i]);
if (st < 0)
st = 0;
if (st > end - start)
st = end - start;
if (en < 0)
en = 0;
if (en > end - start)
en = end - start;
setSpan(spans[i], st, en, fl);
}
}
}
public static SpannableStringBuilder valueOf(CharSequence source) {
if (source instanceof SpannableStringBuilder) {
return (SpannableStringBuilder) source;
} else {
return new SpannableStringBuilder(source);
}
}
/**
* Return the char at the specified offset within the buffer.
*/
public char charAt(int where) {
int len = length();
if (where < 0) {
throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
} else if (where >= len) {
throw new IndexOutOfBoundsException("charAt: " + where +
" >= length " + len);
}
if (where >= mGapStart)
return mText[where + mGapLength];
else
return mText[where];
}
/**
* Return the number of chars in the buffer.
*/
public int length() {
return mText.length - mGapLength;
}
private void resizeFor(int size) {
int newlen = ArrayUtils.idealCharArraySize(size + 1);
char[] newtext = new char[newlen];
int after = mText.length - (mGapStart + mGapLength);
System.arraycopy(mText, 0, newtext, 0, mGapStart);
System.arraycopy(mText, mText.length - after,
newtext, newlen - after, after);
for (int i = 0; i < mSpanCount; i++) {
if (mSpanStarts[i] > mGapStart)
mSpanStarts[i] += newlen - mText.length;
if (mSpanEnds[i] > mGapStart)
mSpanEnds[i] += newlen - mText.length;
}
int oldlen = mText.length;
mText = newtext;
mGapLength += mText.length - oldlen;
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
}
private void moveGapTo(int where) {
if (where == mGapStart)
return;
boolean atend = (where == length());
if (where < mGapStart) {
int overlap = mGapStart - where;
System.arraycopy(mText, where,
mText, mGapStart + mGapLength - overlap, overlap);
} else /* where > mGapStart */ {
int overlap = where - mGapStart;
System.arraycopy(mText, where + mGapLength - overlap,
mText, mGapStart, overlap);
}
// XXX be more clever
for (int i = 0; i < mSpanCount; i++) {
int start = mSpanStarts[i];
int end = mSpanEnds[i];
if (start > mGapStart)
start -= mGapLength;
if (start > where)
start += mGapLength;
else if (start == where) {
int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
if (flag == POINT || (atend && flag == PARAGRAPH))
start += mGapLength;
}
if (end > mGapStart)
end -= mGapLength;
if (end > where)
end += mGapLength;
else if (end == where) {
int flag = (mSpanFlags[i] & END_MASK);
if (flag == POINT || (atend && flag == PARAGRAPH))
end += mGapLength;
}
mSpanStarts[i] = start;
mSpanEnds[i] = end;
}
mGapStart = where;
}
// Documentation from interface
public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
return replace(where, where, tb, start, end);
}
// Documentation from interface
public SpannableStringBuilder insert(int where, CharSequence tb) {
return replace(where, where, tb, 0, tb.length());
}
// Documentation from interface
public SpannableStringBuilder delete(int start, int end) {
SpannableStringBuilder ret = replace(start, end, "", 0, 0);
if (mGapLength > 2 * length())
resizeFor(length());
return ret; // == this
}
// Documentation from interface
public void clear() {
replace(0, length(), "", 0, 0);
}
// Documentation from interface
public void clearSpans() {
for (int i = mSpanCount - 1; i >= 0; i--) {
Object what = mSpans[i];
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
if (ostart > mGapStart)
ostart -= mGapLength;
if (oend > mGapStart)
oend -= mGapLength;
mSpanCount = i;
mSpans[i] = null;
sendSpanRemoved(what, ostart, oend);
}
}
// Documentation from interface
public SpannableStringBuilder append(CharSequence text) {
int length = length();
return replace(length, length, text, 0, text.length());
}
// Documentation from interface
public SpannableStringBuilder append(CharSequence text, int start, int end) {
int length = length();
return replace(length, length, text, start, end);
}
// Documentation from interface
public SpannableStringBuilder append(char text) {
return append(String.valueOf(text));
}
private int change(int start, int end,
CharSequence tb, int tbstart, int tbend) {
return change(true, start, end, tb, tbstart, tbend);
}
private int change(boolean notify, int start, int end,
CharSequence tb, int tbstart, int tbend) {
checkRange("replace", start, end);
int ret = tbend - tbstart;
TextWatcher[] recipients = null;
if (notify)
recipients = sendTextWillChange(start, end - start,
tbend - tbstart);
for (int i = mSpanCount - 1; i >= 0; i--) {
if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
int st = mSpanStarts[i];
if (st > mGapStart)
st -= mGapLength;
int en = mSpanEnds[i];
if (en > mGapStart)
en -= mGapLength;
int ost = st;
int oen = en;
int clen = length();
if (st > start && st <= end) {
for (st = end; st < clen; st++)
if (st > end && charAt(st - 1) == '\n')
break;
}
if (en > start && en <= end) {
for (en = end; en < clen; en++)
if (en > end && charAt(en - 1) == '\n')
break;
}
if (st != ost || en != oen)
setSpan(mSpans[i], st, en, mSpanFlags[i]);
}
}
moveGapTo(end);
if (tbend - tbstart >= mGapLength + (end - start))
resizeFor(mText.length - mGapLength +
tbend - tbstart - (end - start));
mGapStart += tbend - tbstart - (end - start);
mGapLength -= tbend - tbstart - (end - start);
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
TextUtils.getChars(tb, tbstart, tbend, mText, start);
if (tb instanceof Spanned) {
Spanned sp = (Spanned) tb;
Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
for (int i = 0; i < spans.length; i++) {
int st = sp.getSpanStart(spans[i]);
int en = sp.getSpanEnd(spans[i]);
if (st < tbstart)
st = tbstart;
if (en > tbend)
en = tbend;
if (getSpanStart(spans[i]) < 0) {
setSpan(false, spans[i],
st - tbstart + start,
en - tbstart + start,
sp.getSpanFlags(spans[i]));
}
}
}
// no need for span fixup on pure insertion
if (tbend > tbstart && end - start == 0) {
if (notify) {
sendTextChange(recipients, start, end - start, tbend - tbstart);
sendTextHasChanged(recipients);
}
return ret;
}
boolean atend = (mGapStart + mGapLength == mText.length);
for (int i = mSpanCount - 1; i >= 0; i--) {
if (mSpanStarts[i] >= start &&
mSpanStarts[i] < mGapStart + mGapLength) {
int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
if (flag == POINT || (flag == PARAGRAPH && atend))
mSpanStarts[i] = mGapStart + mGapLength;
else
mSpanStarts[i] = start;
}
if (mSpanEnds[i] >= start &&
mSpanEnds[i] < mGapStart + mGapLength) {
int flag = (mSpanFlags[i] & END_MASK);
if (flag == POINT || (flag == PARAGRAPH && atend))
mSpanEnds[i] = mGapStart + mGapLength;
else
mSpanEnds[i] = start;
}
// remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
// XXX send notification on removal
if (mSpanEnds[i] < mSpanStarts[i]) {
System.arraycopy(mSpans, i + 1,
mSpans, i, mSpanCount - (i + 1));
System.arraycopy(mSpanStarts, i + 1,
mSpanStarts, i, mSpanCount - (i + 1));
System.arraycopy(mSpanEnds, i + 1,
mSpanEnds, i, mSpanCount - (i + 1));
System.arraycopy(mSpanFlags, i + 1,
mSpanFlags, i, mSpanCount - (i + 1));
mSpanCount--;
}
}
if (notify) {
sendTextChange(recipients, start, end - start, tbend - tbstart);
sendTextHasChanged(recipients);
}
return ret;
}
// Documentation from interface
public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
return replace(start, end, tb, 0, tb.length());
}
// Documentation from interface
public SpannableStringBuilder replace(final int start, final int end,
CharSequence tb, int tbstart, int tbend) {
int filtercount = mFilters.length;
for (int i = 0; i < filtercount; i++) {
CharSequence repl = mFilters[i].filter(tb, tbstart, tbend,
this, start, end);
if (repl != null) {
tb = repl;
tbstart = 0;
tbend = repl.length();
}
}
if (end == start && tbstart == tbend) {
return this;
}
if (end == start || tbstart == tbend) {
change(start, end, tb, tbstart, tbend);
} else {
int selstart = Selection.getSelectionStart(this);
int selend = Selection.getSelectionEnd(this);
// XXX just make the span fixups in change() do the right thing
// instead of this madness!
checkRange("replace", start, end);
moveGapTo(end);
TextWatcher[] recipients;
recipients = sendTextWillChange(start, end - start,
tbend - tbstart);
int origlen = end - start;
if (mGapLength < 2)
resizeFor(length() + 1);
for (int i = mSpanCount - 1; i >= 0; i--) {
if (mSpanStarts[i] == mGapStart)
mSpanStarts[i]++;
if (mSpanEnds[i] == mGapStart)
mSpanEnds[i]++;
}
mText[mGapStart] = ' ';
mGapStart++;
mGapLength--;
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
int oldlen = (end + 1) - start;
int inserted = change(false, start + 1, start + 1,
tb, tbstart, tbend);
change(false, start, start + 1, "", 0, 0);
change(false, start + inserted, start + inserted + oldlen - 1,
"", 0, 0);
/*
* Special case to keep the cursor in the same position
* if it was somewhere in the middle of the replaced region.
* If it was at the start or the end or crossing the whole
* replacement, it should already be where it belongs.
* TODO: Is there some more general mechanism that could
* accomplish this?
*/
if (selstart > start && selstart < end) {
long off = selstart - start;
off = off * inserted / (end - start);
selstart = (int) off + start;
setSpan(false, Selection.SELECTION_START, selstart, selstart,
Spanned.SPAN_POINT_POINT);
}
if (selend > start && selend < end) {
long off = selend - start;
off = off * inserted / (end - start);
selend = (int) off + start;
setSpan(false, Selection.SELECTION_END, selend, selend,
Spanned.SPAN_POINT_POINT);
}
sendTextChange(recipients, start, origlen, inserted);
sendTextHasChanged(recipients);
}
return this;
}
/**
* Mark the specified range of text with the specified object.
* The flags determine how the span will behave when text is
* inserted at the start or end of the span's range.
*/
public void setSpan(Object what, int start, int end, int flags) {
setSpan(true, what, start, end, flags);
}
private void setSpan(boolean send,
Object what, int start, int end, int flags) {
int nstart = start;
int nend = end;
checkRange("setSpan", start, end);
if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
if (start != 0 && start != length()) {
char c = charAt(start - 1);
if (c != '\n')
throw new RuntimeException(
"PARAGRAPH span must start at paragraph boundary");
}
}
if ((flags & END_MASK) == PARAGRAPH) {
if (end != 0 && end != length()) {
char c = charAt(end - 1);
if (c != '\n')
throw new RuntimeException(
"PARAGRAPH span must end at paragraph boundary");
}
}
if (start > mGapStart)
start += mGapLength;
else if (start == mGapStart) {
int flag = (flags & START_MASK) >> START_SHIFT;
if (flag == POINT || (flag == PARAGRAPH && start == length()))
start += mGapLength;
}
if (end > mGapStart)
end += mGapLength;
else if (end == mGapStart) {
int flag = (flags & END_MASK);
if (flag == POINT || (flag == PARAGRAPH && end == length()))
end += mGapLength;
}
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = 0; i < count; i++) {
if (spans[i] == what) {
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
if (ostart > mGapStart)
ostart -= mGapLength;
if (oend > mGapStart)
oend -= mGapLength;
mSpanStarts[i] = start;
mSpanEnds[i] = end;
mSpanFlags[i] = flags;
if (send)
sendSpanChanged(what, ostart, oend, nstart, nend);
return;
}
}
if (mSpanCount + 1 >= mSpans.length) {
int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
Object[] newspans = new Object[newsize];
int[] newspanstarts = new int[newsize];
int[] newspanends = new int[newsize];
int[] newspanflags = new int[newsize];
System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
mSpans = newspans;
mSpanStarts = newspanstarts;
mSpanEnds = newspanends;
mSpanFlags = newspanflags;
}
mSpans[mSpanCount] = what;
mSpanStarts[mSpanCount] = start;
mSpanEnds[mSpanCount] = end;
mSpanFlags[mSpanCount] = flags;
mSpanCount++;
if (send)
sendSpanAdded(what, nstart, nend);
}
/**
* Remove the specified markup object from the buffer.
*/
public void removeSpan(Object what) {
for (int i = mSpanCount - 1; i >= 0; i--) {
if (mSpans[i] == what) {
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
if (ostart > mGapStart)
ostart -= mGapLength;
if (oend > mGapStart)
oend -= mGapLength;
int count = mSpanCount - (i + 1);
System.arraycopy(mSpans, i + 1, mSpans, i, count);
System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
mSpanCount--;
mSpans[mSpanCount] = null;
sendSpanRemoved(what, ostart, oend);
return;
}
}
}
/**
* Return the buffer offset of the beginning of the specified
* markup object, or -1 if it is not attached to this buffer.
*/
public int getSpanStart(Object what) {
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
int where = mSpanStarts[i];
if (where > mGapStart)
where -= mGapLength;
return where;
}
}
return -1;
}
/**
* Return the buffer offset of the end of the specified
* markup object, or -1 if it is not attached to this buffer.
*/
public int getSpanEnd(Object what) {
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
int where = mSpanEnds[i];
if (where > mGapStart)
where -= mGapLength;
return where;
}
}
return -1;
}
/**
* Return the flags of the end of the specified
* markup object, or 0 if it is not attached to this buffer.
*/
public int getSpanFlags(Object what) {
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = count - 1; i >= 0; i--) {
if (spans[i] == what) {
return mSpanFlags[i];
}
}
return 0;
}
/**
* Return an array of the spans of the specified type that overlap
* the specified range of the buffer. The kind may be Object.class to get
* a list of all the spans regardless of type.
*/
public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
int spanCount = mSpanCount;
Object[] spans = mSpans;
int[] starts = mSpanStarts;
int[] ends = mSpanEnds;
int[] flags = mSpanFlags;
int gapstart = mGapStart;
int gaplen = mGapLength;
int count = 0;
Object[] ret = null;
Object ret1 = null;
for (int i = 0; i < spanCount; i++) {
int spanStart = starts[i];
int spanEnd = ends[i];
if (spanStart > gapstart) {
spanStart -= gaplen;
}
if (spanEnd > gapstart) {
spanEnd -= gaplen;
}
if (spanStart > queryEnd) {
continue;
}
if (spanEnd < queryStart) {
continue;
}
if (spanStart != spanEnd && queryStart != queryEnd) {
if (spanStart == queryEnd)
continue;
if (spanEnd == queryStart)
continue;
}
if (kind != null && !kind.isInstance(spans[i])) {
continue;
}
if (count == 0) {
ret1 = spans[i];
count++;
} else {
if (count == 1) {
ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
ret[0] = ret1;
}
int prio = flags[i] & SPAN_PRIORITY;
if (prio != 0) {
int j;
for (j = 0; j < count; j++) {
int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
if (prio > p) {
break;
}
}
System.arraycopy(ret, j, ret, j + 1, count - j);
ret[j] = spans[i];
count++;
} else {
ret[count++] = spans[i];
}
}
}
if (count == 0) {
return (T[]) ArrayUtils.emptyArray(kind);
}
if (count == 1) {
ret = (Object[]) Array.newInstance(kind, 1);
ret[0] = ret1;
return (T[]) ret;
}
if (count == ret.length) {
return (T[]) ret;
}
Object[] nret = (Object[]) Array.newInstance(kind, count);
System.arraycopy(ret, 0, nret, 0, count);
return (T[]) nret;
}
/**
* Return the next offset after <code>start</code> but less than or
* equal to <code>limit</code> where a span of the specified type
* begins or ends.
*/
public int nextSpanTransition(int start, int limit, Class kind) {
int count = mSpanCount;
Object[] spans = mSpans;
int[] starts = mSpanStarts;
int[] ends = mSpanEnds;
int gapstart = mGapStart;
int gaplen = mGapLength;
if (kind == null) {
kind = Object.class;
}
for (int i = 0; i < count; i++) {
int st = starts[i];
int en = ends[i];
if (st > gapstart)
st -= gaplen;
if (en > gapstart)
en -= gaplen;
if (st > start && st < limit && kind.isInstance(spans[i]))
limit = st;
if (en > start && en < limit && kind.isInstance(spans[i]))
limit = en;
}
return limit;
}
/**
* Return a new CharSequence containing a copy of the specified
* range of this buffer, including the overlapping spans.
*/
public CharSequence subSequence(int start, int end) {
return new SpannableStringBuilder(this, start, end);
}
/**
* Copy the specified range of chars from this buffer into the
* specified array, beginning at the specified offset.
*/
public void getChars(int start, int end, char[] dest, int destoff) {
checkRange("getChars", start, end);
if (end <= mGapStart) {
System.arraycopy(mText, start, dest, destoff, end - start);
} else if (start >= mGapStart) {
System.arraycopy(mText, start + mGapLength,
dest, destoff, end - start);
} else {
System.arraycopy(mText, start, dest, destoff, mGapStart - start);
System.arraycopy(mText, mGapStart + mGapLength,
dest, destoff + (mGapStart - start),
end - mGapStart);
}
}
/**
* Return a String containing a copy of the chars in this buffer.
*/
public String toString() {
int len = length();
char[] buf = new char[len];
getChars(0, len, buf, 0);
return new String(buf);
}
private TextWatcher[] sendTextWillChange(int start, int before, int after) {
TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].beforeTextChanged(this, start, before, after);
}
return recip;
}
private void sendTextChange(TextWatcher[] recip, int start, int before,
int after) {
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onTextChanged(this, start, before, after);
}
}
private void sendTextHasChanged(TextWatcher[] recip) {
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].afterTextChanged(this);
}
}
private void sendSpanAdded(Object what, int start, int end) {
SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onSpanAdded(this, what, start, end);
}
}
private void sendSpanRemoved(Object what, int start, int end) {
SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onSpanRemoved(this, what, start, end);
}
}
private void sendSpanChanged(Object what, int s, int e, int st, int en) {
SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
SpanWatcher.class);
int n = recip.length;
for (int i = 0; i < n; i++) {
recip[i].onSpanChanged(this, what, s, e, st, en);
}
}
private static String region(int start, int end) {
return "(" + start + " ... " + end + ")";
}
private void checkRange(final String operation, int start, int end) {
if (end < start) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) +
" has end before start");
}
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) +
" ends beyond length " + len);
}
if (start < 0 || end < 0) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) +
" starts before 0");
}
}
private boolean isprint(char c) { // XXX
if (c >= ' ' && c <= '~')
return true;
else
return false;
}
/*
private static final int startFlag(int flag) {
return (flag >> 4) & 0x0F;
}
private static final int endFlag(int flag) {
return flag & 0x0F;
}
public void dump() { // XXX
for (int i = 0; i < mGapStart; i++) {
System.out.print('|');
System.out.print(' ');
System.out.print(isprint(mText[i]) ? mText[i] : '.');
System.out.print(' ');
}
for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
System.out.print('|');
System.out.print('(');
System.out.print(isprint(mText[i]) ? mText[i] : '.');
System.out.print(')');
}
for (int i = mGapStart + mGapLength; i < mText.length; i++) {
System.out.print('|');
System.out.print(' ');
System.out.print(isprint(mText[i]) ? mText[i] : '.');
System.out.print(' ');
}
System.out.print('\n');
for (int i = 0; i < mText.length + 1; i++) {
int found = 0;
int wfound = 0;
for (int j = 0; j < mSpanCount; j++) {
if (mSpanStarts[j] == i) {
found = 1;
wfound = j;
break;
}
if (mSpanEnds[j] == i) {
found = 2;
wfound = j;
break;
}
}
if (found == 1) {
if (startFlag(mSpanFlags[wfound]) == MARK)
System.out.print("( ");
if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
System.out.print("< ");
else
System.out.print("[ ");
} else if (found == 2) {
if (endFlag(mSpanFlags[wfound]) == POINT)
System.out.print(") ");
if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
System.out.print("> ");
else
System.out.print("] ");
} else {
System.out.print(" ");
}
}
System.out.print("\n");
}
*/
/**
* Don't call this yourself -- exists for Canvas to use internally.
* {@hide}
*/
public void drawText(Canvas c, int start, int end,
float x, float y, Paint p) {
checkRange("drawText", start, end);
if (end <= mGapStart) {
c.drawText(mText, start, end - start, x, y, p);
} else if (start >= mGapStart) {
c.drawText(mText, start + mGapLength, end - start, x, y, p);
} else {
char[] buf = TextUtils.obtain(end - start);
getChars(start, end, buf, 0);
c.drawText(buf, 0, end - start, x, y, p);
TextUtils.recycle(buf);
}
}
/**
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
public float measureText(int start, int end, Paint p) {
checkRange("measureText", start, end);
float ret;
if (end <= mGapStart) {
ret = p.measureText(mText, start, end - start);
} else if (start >= mGapStart) {
ret = p.measureText(mText, start + mGapLength, end - start);
} else {
char[] buf = TextUtils.obtain(end - start);
getChars(start, end, buf, 0);
ret = p.measureText(buf, 0, end - start);
TextUtils.recycle(buf);
}
return ret;
}
/**
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
public int getTextWidths(int start, int end, float[] widths, Paint p) {
checkRange("getTextWidths", start, end);
int ret;
if (end <= mGapStart) {
ret = p.getTextWidths(mText, start, end - start, widths);
} else if (start >= mGapStart) {
ret = p.getTextWidths(mText, start + mGapLength, end - start,
widths);
} else {
char[] buf = TextUtils.obtain(end - start);
getChars(start, end, buf, 0);
ret = p.getTextWidths(buf, 0, end - start, widths);
TextUtils.recycle(buf);
}
return ret;
}
// Documentation from interface
public void setFilters(InputFilter[] filters) {
if (filters == null) {
throw new IllegalArgumentException();
}
mFilters = filters;
}
// Documentation from interface
public InputFilter[] getFilters() {
return mFilters;
}
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
private InputFilter[] mFilters = NO_FILTERS;
private char[] mText;
private int mGapStart;
private int mGapLength;
private Object[] mSpans;
private int[] mSpanStarts;
private int[] mSpanEnds;
private int[] mSpanFlags;
private int mSpanCount;
private static final int MARK = 1;
private static final int POINT = 2;
private static final int PARAGRAPH = 3;
private static final int START_MASK = 0xF0;
private static final int END_MASK = 0x0F;
private static final int START_SHIFT = 4;
}