1141 lines
35 KiB
Java
1141 lines
35 KiB
Java
|
/*
|
||
|
* 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;
|
||
|
}
|