Loading core/java/android/widget/Editor.java +105 −215 Original line number Diff line number Diff line Loading @@ -4198,13 +4198,6 @@ public class Editor { int mChangedStart, mChangedEnd, mChangedDelta; } /** * @return True iff (start, end) is a valid range within the text. */ private static boolean isValidRange(CharSequence text, int start, int end) { return 0 <= start && start <= end && end <= text.length(); } /** * An InputFilter that monitors text input to maintain undo history. It does not modify the * text being typed (and hence always returns null from the filter() method). Loading @@ -4220,123 +4213,97 @@ public class Editor { public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (DEBUG_UNDO) { Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ") " + "dest=" + dest + " (" + dstart + "-" + dend + ")"); Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ")"); Log.d(TAG, "filter: dest=" + dest + " (" + dstart + "-" + dend + ")"); } final UndoManager um = mEditor.mUndoManager; if (um.isInUndo()) { if (DEBUG_UNDO) Log.d(TAG, "filter: skipping, currently performing undo/redo"); if (DEBUG_UNDO) Log.d(TAG, "*** skipping, currently performing undo/redo"); return null; } // Text filters run before input operations are applied. However, some input operations // are invalid and will throw exceptions when applied. This is common in tests. Don't // attempt to undo invalid operations. if (!isValidRange(source, start, end) || !isValidRange(dest, dstart, dend)) { if (DEBUG_UNDO) Log.d(TAG, "filter: invalid op"); um.beginUpdate("Edit text"); TextModifyOperation op = um.getLastOperation( TextModifyOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); if (op != null) { if (DEBUG_UNDO) Log.d(TAG, "Last op: range=(" + op.mRangeStart + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); // See if we can continue modifying this operation. if (op.mOldText == null) { // The current operation is an add... are we adding more? We are adding // more if we are either appending new text to the end of the last edit or // completely replacing some or all of the last edit. // TODO: This sequence doesn't work right: a, left-arrow, b, undo, undo. // The two edits are incorrectly merged, so there is only one undo available. if (start < end && ((dstart >= op.mRangeStart && dend <= op.mRangeEnd) || (dstart == op.mRangeEnd && dend == op.mRangeEnd))) { op.mRangeEnd = dstart + (end-start); um.endUpdate(); if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, mRangeEnd=" + op.mRangeEnd); return null; } // Earlier filters can rewrite input to be a no-op, for example due to a length limit // on an input field. Skip no-op changes. if (start == end && dstart == dend) { if (DEBUG_UNDO) Log.d(TAG, "filter: skipping no-op"); } else { // The current operation is a delete... can we delete more? if (start == end && dend == op.mRangeStart-1) { SpannableStringBuilder str; if (op.mOldText instanceof SpannableString) { str = (SpannableStringBuilder)op.mOldText; } else { str = new SpannableStringBuilder(op.mOldText); } str.insert(0, dest, dstart, dend); op.mRangeStart = dstart; op.mOldText = str; um.endUpdate(); if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, range=(" + op.mRangeStart + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); return null; } } // Build a new operation with all the information from this edit. EditOperation edit = new EditOperation(mEditor, source, start, end, dest, dstart, dend); // Couldn't add to the current undo operation, need to start a new // undo state for a new undo operation. um.commitState(null); um.setUndoLabel("Edit text"); } // Fetch the last edit operation and attempt to merge in the new edit. um.beginUpdate("Edit text"); EditOperation lastEdit = um.getLastOperation( EditOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); if (lastEdit == null) { // Add this as the first edit. if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit); um.addOperation(edit, UndoManager.MERGE_MODE_NONE); } else if (lastEdit.mergeWith(edit)) { // Merge succeeded, nothing else to do. if (DEBUG_UNDO) Log.d(TAG, "filter: merge succeeded, created " + lastEdit); // Create a new undo state reflecting the operation being performed. op = new TextModifyOperation(mEditor.mUndoOwner); op.mRangeStart = dstart; if (start < end) { op.mRangeEnd = dstart + (end-start); } else { // Could not merge with the last edit, so commit the last edit and add this edit. if (DEBUG_UNDO) Log.d(TAG, "filter: merge failed, adding " + edit); um.commitState(mEditor.mUndoOwner); um.addOperation(edit, UndoManager.MERGE_MODE_NONE); op.mRangeEnd = dstart; } if (dstart < dend) { op.mOldText = dest.subSequence(dstart, dend); } if (DEBUG_UNDO) Log.d(TAG, "*** adding new op, range=(" + op.mRangeStart + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); um.addOperation(op, UndoManager.MERGE_MODE_NONE); um.endUpdate(); return null; // Text not changed. return null; } } /** * An operation to undo a single "edit" to a text view. */ public static class EditOperation extends UndoOperation<Editor> { private static final int TYPE_INSERT = 0; private static final int TYPE_DELETE = 1; private static final int TYPE_REPLACE = 2; private int mType; private String mOldText; private int mOldTextStart; private String mNewText; private int mNewTextStart; public static class TextModifyOperation extends UndoOperation<Editor> { int mRangeStart, mRangeEnd; CharSequence mOldText; private int mOldCursorPos; private int mNewCursorPos; /** * Constructs an edit operation from a text input operation that replaces the range * (dstart, dend) of dest with (start, end) of source. See {@link InputFilter#filter}. */ public EditOperation(Editor editor, CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { super(editor.mUndoOwner); mOldText = dest.subSequence(dstart, dend).toString(); mNewText = source.subSequence(start, end).toString(); // Determine the type of the edit and store where it occurred. Avoid storing // irrevelant data (e.g. mNewTextStart for a delete) because that makes the // merging logic more complex (e.g. merging deletes could lead to mNewTextStart being // outside the bounds of the final text). if (mNewText.length() > 0 && mOldText.length() == 0) { mType = TYPE_INSERT; mNewTextStart = dstart; } else if (mNewText.length() == 0 && mOldText.length() > 0) { mType = TYPE_DELETE; mOldTextStart = dstart; } else { mType = TYPE_REPLACE; mOldTextStart = mNewTextStart = dstart; } // Store cursor data. mOldCursorPos = editor.mTextView.getSelectionStart(); mNewCursorPos = dstart + (end - start); public TextModifyOperation(UndoOwner owner) { super(owner); } public EditOperation(Parcel src, ClassLoader loader) { public TextModifyOperation(Parcel src, ClassLoader loader) { super(src, loader); mType = src.readInt(); mOldText = src.readString(); mOldTextStart = src.readInt(); mNewText = src.readString(); mNewTextStart = src.readInt(); mOldCursorPos = src.readInt(); mNewCursorPos = src.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeString(mOldText); dest.writeInt(mOldTextStart); dest.writeString(mNewText); dest.writeInt(mNewTextStart); dest.writeInt(mOldCursorPos); dest.writeInt(mNewCursorPos); mRangeStart = src.readInt(); mRangeEnd = src.readInt(); mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); } @Override Loading @@ -4345,139 +4312,62 @@ public class Editor { @Override public void undo() { if (DEBUG_UNDO) Log.d(TAG, "undo"); // Remove the new text and insert the old. modifyText(mNewTextStart, getNewTextEnd(), mOldText, mOldTextStart, mOldCursorPos); swapText(); } @Override public void redo() { if (DEBUG_UNDO) Log.d(TAG, "redo"); // Remove the old text and insert the new. modifyText(mOldTextStart, getOldTextEnd(), mNewText, mNewTextStart, mNewCursorPos); swapText(); } /** * Attempts to merge this existing operation with a new edit. * @param edit The new edit operation. * @return If the merge succeeded, returns true. Otherwise returns false and leaves this * object unchanged. */ private boolean mergeWith(EditOperation edit) { switch (mType) { case TYPE_INSERT: return mergeInsertWith(edit); case TYPE_DELETE: return mergeDeleteWith(edit); case TYPE_REPLACE: return mergeReplaceWith(edit); default: return false; } } private boolean mergeInsertWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "mergeInsertWith " + edit); // Only merge continuous insertions. if (edit.mType != TYPE_INSERT) { return false; } // Only merge insertions that are contiguous. if (getNewTextEnd() != edit.mNewTextStart) { return false; } mNewText += edit.mNewText; mNewCursorPos = edit.mNewCursorPos; return true; } // TODO: Support forward delete. private boolean mergeDeleteWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "mergeDeleteWith " + edit); // Only merge continuous deletes. if (edit.mType != TYPE_DELETE) { return false; } // Only merge deletions that are contiguous. if (mOldTextStart != edit.getOldTextEnd()) { return false; } mOldTextStart = edit.mOldTextStart; mOldText = edit.mOldText + mOldText; mNewCursorPos = edit.mNewCursorPos; return true; } private boolean mergeReplaceWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "mergeReplaceWith " + edit); // Replacements can merge only with adjacent inserts and adjacent replacements. if (edit.mType == TYPE_DELETE || getNewTextEnd() != edit.mOldTextStart || edit.mOldTextStart != edit.mNewTextStart) { return false; } mOldText += edit.mOldText; mNewText += edit.mNewText; mNewCursorPos = edit.mNewCursorPos; return true; } private int getNewTextEnd() { return mNewTextStart + mNewText.length(); } private int getOldTextEnd() { return mOldTextStart + mOldText.length(); } private void modifyText(int deleteFrom, int deleteTo, CharSequence newText, int newTextInsertAt, int newCursorPos) { private void swapText() { // Both undo and redo involves swapping the contents of the range // in the text view with our local text. Editor editor = getOwnerData(); Editable text = (Editable) editor.mTextView.getText(); // Apply the edit if it is still valid. if (isValidRange(text, deleteFrom, deleteTo) && newTextInsertAt <= text.length() - (deleteTo - deleteFrom)) { if (deleteFrom != deleteTo) { text.delete(deleteFrom, deleteTo); } if (newText.length() != 0) { text.insert(newTextInsertAt, newText); Editable editable = (Editable)editor.mTextView.getText(); CharSequence curText; if (mRangeStart >= mRangeEnd) { curText = null; } else { curText = editable.subSequence(mRangeStart, mRangeEnd); } if (DEBUG_UNDO) { Log.d(TAG, "Swap: range=(" + mRangeStart + "-" + mRangeEnd + "), oldText=" + mOldText); Log.d(TAG, "Swap: curText=" + curText); } // Restore the cursor position. // TODO: Select all the text that was undone. if (newCursorPos <= text.length()) { Selection.setSelection(text, newCursorPos); if (mOldText == null) { editable.delete(mRangeStart, mRangeEnd); mRangeEnd = mRangeStart; } else { editable.replace(mRangeStart, mRangeEnd, mOldText); mRangeEnd = mRangeStart + mOldText.length(); } mOldText = curText; } @Override public String toString() { return "EditOperation: [" + "mType=" + mType + ", " + "mOldText=" + mOldText + ", " + "mOldTextStart=" + mOldTextStart + ", " + "mNewText=" + mNewText + ", " + "mNewTextStart=" + mNewTextStart + ", " + "mOldCursorPos=" + mOldCursorPos + ", " + "mNewCursorPos=" + mNewCursorPos + "]"; } public static final Parcelable.ClassLoaderCreator<EditOperation> CREATOR = new Parcelable.ClassLoaderCreator<EditOperation>() { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mRangeStart); dest.writeInt(mRangeEnd); TextUtils.writeToParcel(mOldText, dest, flags); } public static final Parcelable.ClassLoaderCreator<TextModifyOperation> CREATOR = new Parcelable.ClassLoaderCreator<TextModifyOperation>() { @Override public EditOperation createFromParcel(Parcel in) { return new EditOperation(in, null); public TextModifyOperation createFromParcel(Parcel in) { return new TextModifyOperation(in, null); } @Override public EditOperation createFromParcel(Parcel in, ClassLoader loader) { return new EditOperation(in, loader); public TextModifyOperation createFromParcel(Parcel in, ClassLoader loader) { return new TextModifyOperation(in, loader); } @Override public EditOperation[] newArray(int size) { return new EditOperation[size]; public TextModifyOperation[] newArray(int size) { return new TextModifyOperation[size]; } }; } Loading Loading
core/java/android/widget/Editor.java +105 −215 Original line number Diff line number Diff line Loading @@ -4198,13 +4198,6 @@ public class Editor { int mChangedStart, mChangedEnd, mChangedDelta; } /** * @return True iff (start, end) is a valid range within the text. */ private static boolean isValidRange(CharSequence text, int start, int end) { return 0 <= start && start <= end && end <= text.length(); } /** * An InputFilter that monitors text input to maintain undo history. It does not modify the * text being typed (and hence always returns null from the filter() method). Loading @@ -4220,123 +4213,97 @@ public class Editor { public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (DEBUG_UNDO) { Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ") " + "dest=" + dest + " (" + dstart + "-" + dend + ")"); Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ")"); Log.d(TAG, "filter: dest=" + dest + " (" + dstart + "-" + dend + ")"); } final UndoManager um = mEditor.mUndoManager; if (um.isInUndo()) { if (DEBUG_UNDO) Log.d(TAG, "filter: skipping, currently performing undo/redo"); if (DEBUG_UNDO) Log.d(TAG, "*** skipping, currently performing undo/redo"); return null; } // Text filters run before input operations are applied. However, some input operations // are invalid and will throw exceptions when applied. This is common in tests. Don't // attempt to undo invalid operations. if (!isValidRange(source, start, end) || !isValidRange(dest, dstart, dend)) { if (DEBUG_UNDO) Log.d(TAG, "filter: invalid op"); um.beginUpdate("Edit text"); TextModifyOperation op = um.getLastOperation( TextModifyOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); if (op != null) { if (DEBUG_UNDO) Log.d(TAG, "Last op: range=(" + op.mRangeStart + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); // See if we can continue modifying this operation. if (op.mOldText == null) { // The current operation is an add... are we adding more? We are adding // more if we are either appending new text to the end of the last edit or // completely replacing some or all of the last edit. // TODO: This sequence doesn't work right: a, left-arrow, b, undo, undo. // The two edits are incorrectly merged, so there is only one undo available. if (start < end && ((dstart >= op.mRangeStart && dend <= op.mRangeEnd) || (dstart == op.mRangeEnd && dend == op.mRangeEnd))) { op.mRangeEnd = dstart + (end-start); um.endUpdate(); if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, mRangeEnd=" + op.mRangeEnd); return null; } // Earlier filters can rewrite input to be a no-op, for example due to a length limit // on an input field. Skip no-op changes. if (start == end && dstart == dend) { if (DEBUG_UNDO) Log.d(TAG, "filter: skipping no-op"); } else { // The current operation is a delete... can we delete more? if (start == end && dend == op.mRangeStart-1) { SpannableStringBuilder str; if (op.mOldText instanceof SpannableString) { str = (SpannableStringBuilder)op.mOldText; } else { str = new SpannableStringBuilder(op.mOldText); } str.insert(0, dest, dstart, dend); op.mRangeStart = dstart; op.mOldText = str; um.endUpdate(); if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, range=(" + op.mRangeStart + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); return null; } } // Build a new operation with all the information from this edit. EditOperation edit = new EditOperation(mEditor, source, start, end, dest, dstart, dend); // Couldn't add to the current undo operation, need to start a new // undo state for a new undo operation. um.commitState(null); um.setUndoLabel("Edit text"); } // Fetch the last edit operation and attempt to merge in the new edit. um.beginUpdate("Edit text"); EditOperation lastEdit = um.getLastOperation( EditOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); if (lastEdit == null) { // Add this as the first edit. if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit); um.addOperation(edit, UndoManager.MERGE_MODE_NONE); } else if (lastEdit.mergeWith(edit)) { // Merge succeeded, nothing else to do. if (DEBUG_UNDO) Log.d(TAG, "filter: merge succeeded, created " + lastEdit); // Create a new undo state reflecting the operation being performed. op = new TextModifyOperation(mEditor.mUndoOwner); op.mRangeStart = dstart; if (start < end) { op.mRangeEnd = dstart + (end-start); } else { // Could not merge with the last edit, so commit the last edit and add this edit. if (DEBUG_UNDO) Log.d(TAG, "filter: merge failed, adding " + edit); um.commitState(mEditor.mUndoOwner); um.addOperation(edit, UndoManager.MERGE_MODE_NONE); op.mRangeEnd = dstart; } if (dstart < dend) { op.mOldText = dest.subSequence(dstart, dend); } if (DEBUG_UNDO) Log.d(TAG, "*** adding new op, range=(" + op.mRangeStart + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); um.addOperation(op, UndoManager.MERGE_MODE_NONE); um.endUpdate(); return null; // Text not changed. return null; } } /** * An operation to undo a single "edit" to a text view. */ public static class EditOperation extends UndoOperation<Editor> { private static final int TYPE_INSERT = 0; private static final int TYPE_DELETE = 1; private static final int TYPE_REPLACE = 2; private int mType; private String mOldText; private int mOldTextStart; private String mNewText; private int mNewTextStart; public static class TextModifyOperation extends UndoOperation<Editor> { int mRangeStart, mRangeEnd; CharSequence mOldText; private int mOldCursorPos; private int mNewCursorPos; /** * Constructs an edit operation from a text input operation that replaces the range * (dstart, dend) of dest with (start, end) of source. See {@link InputFilter#filter}. */ public EditOperation(Editor editor, CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { super(editor.mUndoOwner); mOldText = dest.subSequence(dstart, dend).toString(); mNewText = source.subSequence(start, end).toString(); // Determine the type of the edit and store where it occurred. Avoid storing // irrevelant data (e.g. mNewTextStart for a delete) because that makes the // merging logic more complex (e.g. merging deletes could lead to mNewTextStart being // outside the bounds of the final text). if (mNewText.length() > 0 && mOldText.length() == 0) { mType = TYPE_INSERT; mNewTextStart = dstart; } else if (mNewText.length() == 0 && mOldText.length() > 0) { mType = TYPE_DELETE; mOldTextStart = dstart; } else { mType = TYPE_REPLACE; mOldTextStart = mNewTextStart = dstart; } // Store cursor data. mOldCursorPos = editor.mTextView.getSelectionStart(); mNewCursorPos = dstart + (end - start); public TextModifyOperation(UndoOwner owner) { super(owner); } public EditOperation(Parcel src, ClassLoader loader) { public TextModifyOperation(Parcel src, ClassLoader loader) { super(src, loader); mType = src.readInt(); mOldText = src.readString(); mOldTextStart = src.readInt(); mNewText = src.readString(); mNewTextStart = src.readInt(); mOldCursorPos = src.readInt(); mNewCursorPos = src.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeString(mOldText); dest.writeInt(mOldTextStart); dest.writeString(mNewText); dest.writeInt(mNewTextStart); dest.writeInt(mOldCursorPos); dest.writeInt(mNewCursorPos); mRangeStart = src.readInt(); mRangeEnd = src.readInt(); mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); } @Override Loading @@ -4345,139 +4312,62 @@ public class Editor { @Override public void undo() { if (DEBUG_UNDO) Log.d(TAG, "undo"); // Remove the new text and insert the old. modifyText(mNewTextStart, getNewTextEnd(), mOldText, mOldTextStart, mOldCursorPos); swapText(); } @Override public void redo() { if (DEBUG_UNDO) Log.d(TAG, "redo"); // Remove the old text and insert the new. modifyText(mOldTextStart, getOldTextEnd(), mNewText, mNewTextStart, mNewCursorPos); swapText(); } /** * Attempts to merge this existing operation with a new edit. * @param edit The new edit operation. * @return If the merge succeeded, returns true. Otherwise returns false and leaves this * object unchanged. */ private boolean mergeWith(EditOperation edit) { switch (mType) { case TYPE_INSERT: return mergeInsertWith(edit); case TYPE_DELETE: return mergeDeleteWith(edit); case TYPE_REPLACE: return mergeReplaceWith(edit); default: return false; } } private boolean mergeInsertWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "mergeInsertWith " + edit); // Only merge continuous insertions. if (edit.mType != TYPE_INSERT) { return false; } // Only merge insertions that are contiguous. if (getNewTextEnd() != edit.mNewTextStart) { return false; } mNewText += edit.mNewText; mNewCursorPos = edit.mNewCursorPos; return true; } // TODO: Support forward delete. private boolean mergeDeleteWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "mergeDeleteWith " + edit); // Only merge continuous deletes. if (edit.mType != TYPE_DELETE) { return false; } // Only merge deletions that are contiguous. if (mOldTextStart != edit.getOldTextEnd()) { return false; } mOldTextStart = edit.mOldTextStart; mOldText = edit.mOldText + mOldText; mNewCursorPos = edit.mNewCursorPos; return true; } private boolean mergeReplaceWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "mergeReplaceWith " + edit); // Replacements can merge only with adjacent inserts and adjacent replacements. if (edit.mType == TYPE_DELETE || getNewTextEnd() != edit.mOldTextStart || edit.mOldTextStart != edit.mNewTextStart) { return false; } mOldText += edit.mOldText; mNewText += edit.mNewText; mNewCursorPos = edit.mNewCursorPos; return true; } private int getNewTextEnd() { return mNewTextStart + mNewText.length(); } private int getOldTextEnd() { return mOldTextStart + mOldText.length(); } private void modifyText(int deleteFrom, int deleteTo, CharSequence newText, int newTextInsertAt, int newCursorPos) { private void swapText() { // Both undo and redo involves swapping the contents of the range // in the text view with our local text. Editor editor = getOwnerData(); Editable text = (Editable) editor.mTextView.getText(); // Apply the edit if it is still valid. if (isValidRange(text, deleteFrom, deleteTo) && newTextInsertAt <= text.length() - (deleteTo - deleteFrom)) { if (deleteFrom != deleteTo) { text.delete(deleteFrom, deleteTo); } if (newText.length() != 0) { text.insert(newTextInsertAt, newText); Editable editable = (Editable)editor.mTextView.getText(); CharSequence curText; if (mRangeStart >= mRangeEnd) { curText = null; } else { curText = editable.subSequence(mRangeStart, mRangeEnd); } if (DEBUG_UNDO) { Log.d(TAG, "Swap: range=(" + mRangeStart + "-" + mRangeEnd + "), oldText=" + mOldText); Log.d(TAG, "Swap: curText=" + curText); } // Restore the cursor position. // TODO: Select all the text that was undone. if (newCursorPos <= text.length()) { Selection.setSelection(text, newCursorPos); if (mOldText == null) { editable.delete(mRangeStart, mRangeEnd); mRangeEnd = mRangeStart; } else { editable.replace(mRangeStart, mRangeEnd, mOldText); mRangeEnd = mRangeStart + mOldText.length(); } mOldText = curText; } @Override public String toString() { return "EditOperation: [" + "mType=" + mType + ", " + "mOldText=" + mOldText + ", " + "mOldTextStart=" + mOldTextStart + ", " + "mNewText=" + mNewText + ", " + "mNewTextStart=" + mNewTextStart + ", " + "mOldCursorPos=" + mOldCursorPos + ", " + "mNewCursorPos=" + mNewCursorPos + "]"; } public static final Parcelable.ClassLoaderCreator<EditOperation> CREATOR = new Parcelable.ClassLoaderCreator<EditOperation>() { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mRangeStart); dest.writeInt(mRangeEnd); TextUtils.writeToParcel(mOldText, dest, flags); } public static final Parcelable.ClassLoaderCreator<TextModifyOperation> CREATOR = new Parcelable.ClassLoaderCreator<TextModifyOperation>() { @Override public EditOperation createFromParcel(Parcel in) { return new EditOperation(in, null); public TextModifyOperation createFromParcel(Parcel in) { return new TextModifyOperation(in, null); } @Override public EditOperation createFromParcel(Parcel in, ClassLoader loader) { return new EditOperation(in, loader); public TextModifyOperation createFromParcel(Parcel in, ClassLoader loader) { return new TextModifyOperation(in, loader); } @Override public EditOperation[] newArray(int size) { return new EditOperation[size]; public TextModifyOperation[] newArray(int size) { return new TextModifyOperation[size]; } }; } Loading