diff options
author | James Cook <jamescook@google.com> | 2015-02-27 20:44:44 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-02-27 20:44:45 +0000 |
commit | 7af589995f8036bc9e622ec54fc29492152f926f (patch) | |
tree | b0a9d88ddcc2eda2df8be867b3b5fdf73d518e4e /core | |
parent | 07a65f444a88a8acf727700f5e23c3a537466e6a (diff) | |
parent | f59152cf00520d1bd36949b44faca2e1fcf6d28f (diff) | |
download | frameworks_base-7af589995f8036bc9e622ec54fc29492152f926f.zip frameworks_base-7af589995f8036bc9e622ec54fc29492152f926f.tar.gz frameworks_base-7af589995f8036bc9e622ec54fc29492152f926f.tar.bz2 |
Merge "Reland: Add basic support for Ctrl-Z to editable TextViews"
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/content/UndoManager.java | 6 | ||||
-rw-r--r-- | core/java/android/content/UndoOwner.java | 11 | ||||
-rw-r--r-- | core/java/android/widget/Editor.java | 67 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 83 | ||||
-rw-r--r-- | core/res/res/values/ids.xml | 2 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 6 |
6 files changed, 140 insertions, 35 deletions
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java index e3bc238..559b01c 100644 --- a/core/java/android/content/UndoManager.java +++ b/core/java/android/content/UndoManager.java @@ -105,8 +105,7 @@ public class UndoManager { return owner; } - owner = new UndoOwner(tag); - owner.mManager = this; + owner = new UndoOwner(tag, this); owner.mData = data; mOwners.put(tag, owner); return owner; @@ -116,7 +115,6 @@ public class UndoManager { // XXX need to figure out how to prune. if (false) { mOwners.remove(owner.mTag); - owner.mManager = null; } } @@ -202,7 +200,7 @@ public class UndoManager { UndoOwner owner = mStateOwners[idx]; if (owner == null) { String tag = in.readString(); - owner = new UndoOwner(tag); + owner = new UndoOwner(tag, this); mStateOwners[idx] = owner; mOwners.put(tag, owner); } diff --git a/core/java/android/content/UndoOwner.java b/core/java/android/content/UndoOwner.java index d0cdc95..9106588 100644 --- a/core/java/android/content/UndoOwner.java +++ b/core/java/android/content/UndoOwner.java @@ -23,8 +23,8 @@ package android.content; */ public class UndoOwner { final String mTag; + final UndoManager mManager; - UndoManager mManager; Object mData; int mOpCount; @@ -32,8 +32,15 @@ public class UndoOwner { int mStateSeq; int mSavedIdx; - UndoOwner(String tag) { + UndoOwner(String tag, UndoManager manager) { + if (tag == null) { + throw new NullPointerException("tag can't be null"); + } + if (manager == null) { + throw new NullPointerException("manager can't be null"); + } mTag = tag; + mManager = manager; } /** diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 4752594..8601d2b 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -50,6 +50,7 @@ import android.graphics.drawable.Drawable; import android.inputmethodservice.ExtractEditText; import android.os.Bundle; import android.os.Handler; +import android.os.ParcelableParcel; import android.os.SystemClock; import android.provider.Settings; import android.text.DynamicLayout; @@ -118,15 +119,18 @@ import java.util.HashMap; */ public class Editor { private static final String TAG = "Editor"; - static final boolean DEBUG_UNDO = false; + private static final boolean DEBUG_UNDO = false; static final int BLINK = 500; private static final float[] TEMP_POSITION = new float[2]; private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; + // Tag used when the Editor maintains its own separate UndoManager. + private static final String UNDO_OWNER_TAG = "Editor"; - UndoManager mUndoManager; - UndoOwner mUndoOwner; - InputFilter mUndoInputFilter; + // Each Editor manages its own undo stack. + private final UndoManager mUndoManager = new UndoManager(); + private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); + final InputFilter mUndoInputFilter = new UndoInputFilter(this); // Cursor Controllers. InsertionPointCursorController mInsertionPointCursorController; @@ -222,6 +226,39 @@ public class Editor { Editor(TextView textView) { mTextView = textView; + // Synchronize the filter list, which places the undo input filter at the end. + mTextView.setFilters(mTextView.getFilters()); + } + + ParcelableParcel saveInstanceState() { + // For now there is only undo state. + return (ParcelableParcel) mUndoManager.saveInstanceState(); + } + + void restoreInstanceState(ParcelableParcel state) { + mUndoManager.restoreInstanceState(state); + // Re-associate this object as the owner of undo state. + mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); + } + + boolean canUndo() { + UndoOwner[] owners = { mUndoOwner }; + return mUndoManager.countUndos(owners) > 0; + } + + boolean canRedo() { + UndoOwner[] owners = { mUndoOwner }; + return mUndoManager.countRedos(owners) > 0; + } + + void undo() { + UndoOwner[] owners = { mUndoOwner }; + mUndoManager.undo(owners, 1); // Undo 1 action. + } + + void redo() { + UndoOwner[] owners = { mUndoOwner }; + mUndoManager.redo(owners, 1); // Redo 1 action. } void onAttachedToWindow() { @@ -1706,7 +1743,7 @@ public class Editor { /** * Called by the framework in response to a text auto-correction (such as fixing a typo using a - * a dictionnary) from the current input method, provided by it calling + * a dictionary) from the current input method, provided by it calling * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default * implementation flashes the background of the corrected word to provide feedback to the user. * @@ -4161,8 +4198,12 @@ public class Editor { int mChangedStart, mChangedEnd, mChangedDelta; } + /** + * 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). + */ public static class UndoInputFilter implements InputFilter { - final Editor mEditor; + private final Editor mEditor; public UndoInputFilter(Editor editor) { mEditor = editor; @@ -4192,6 +4233,8 @@ public class Editor { // 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); @@ -4245,7 +4288,10 @@ public class Editor { } } - public static class TextModifyOperation extends UndoOperation<TextView> { + /** + * An operation to undo a single "edit" to a text view. + */ + public static class TextModifyOperation extends UndoOperation<Editor> { int mRangeStart, mRangeEnd; CharSequence mOldText; @@ -4277,8 +4323,8 @@ public class Editor { private void swapText() { // Both undo and redo involves swapping the contents of the range // in the text view with our local text. - TextView tv = getOwnerData(); - Editable editable = (Editable)tv.getText(); + Editor editor = getOwnerData(); + Editable editable = (Editable)editor.mTextView.getText(); CharSequence curText; if (mRangeStart >= mRangeEnd) { curText = null; @@ -4309,14 +4355,17 @@ public class Editor { public static final Parcelable.ClassLoaderCreator<TextModifyOperation> CREATOR = new Parcelable.ClassLoaderCreator<TextModifyOperation>() { + @Override public TextModifyOperation createFromParcel(Parcel in) { return new TextModifyOperation(in, null); } + @Override public TextModifyOperation createFromParcel(Parcel in, ClassLoader loader) { return new TextModifyOperation(in, loader); } + @Override public TextModifyOperation[] newArray(int size) { return new TextModifyOperation[size]; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 27603f5..2d0a9cb 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -47,6 +47,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.ParcelableParcel; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -1612,7 +1613,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ public final UndoManager getUndoManager() { - return mEditor == null ? null : mEditor.mUndoManager; + // TODO: Consider supporting a global undo manager. + throw new UnsupportedOperationException("not implemented"); } /** @@ -1630,22 +1632,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ public final void setUndoManager(UndoManager undoManager, String tag) { - if (undoManager != null) { - createEditorIfNeeded(); - mEditor.mUndoManager = undoManager; - mEditor.mUndoOwner = undoManager.getOwner(tag, this); - mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor); - if (!(mText instanceof Editable)) { - setText(mText, BufferType.EDITABLE); - } - - setFilters((Editable) mText, mFilters); - } else if (mEditor != null) { - // XXX need to destroy all associated state. - mEditor.mUndoManager = null; - mEditor.mUndoOwner = null; - mEditor.mUndoInputFilter = null; - } + // TODO: Consider supporting a global undo manager. An implementation will need to: + // * createEditorIfNeeded() + // * Promote to BufferType.EDITABLE if needed. + // * Update the UndoManager and UndoOwner. + // Likewise it will need to be able to restore the default UndoManager. + throw new UnsupportedOperationException("not implemented"); } /** @@ -3899,6 +3891,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ss.error = getError(); + if (mEditor != null) { + ss.editorState = mEditor.saveInstanceState(); + } return ss; } @@ -3968,6 +3963,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } }); } + + if (ss.editorState != null) { + createEditorIfNeeded(); + mEditor.restoreInstanceState(ss.editorState); + } } /** @@ -8383,6 +8383,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return onTextContextMenuItem(ID_SELECT_ALL); } break; + case KeyEvent.KEYCODE_Z: + if (canUndo()) { + return onTextContextMenuItem(ID_UNDO); + } + break; case KeyEvent.KEYCODE_X: if (canCut()) { return onTextContextMenuItem(ID_CUT); @@ -8402,11 +8407,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { // Handle Ctrl-Shift shortcuts. switch (keyCode) { + case KeyEvent.KEYCODE_Z: + if (canRedo()) { + return onTextContextMenuItem(ID_REDO); + } + break; case KeyEvent.KEYCODE_V: if (canPaste()) { return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); } - break; } } return super.onKeyShortcut(keyCode, event); @@ -8784,6 +8793,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } static final int ID_SELECT_ALL = android.R.id.selectAll; + static final int ID_UNDO = android.R.id.undo; + static final int ID_REDO = android.R.id.redo; static final int ID_CUT = android.R.id.cut; static final int ID_COPY = android.R.id.copy; static final int ID_PASTE = android.R.id.paste; @@ -8815,6 +8826,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener selectAllText(); return true; + case ID_UNDO: + if (mEditor != null) { + mEditor.undo(); + } + return true; // Returns true even if nothing was undone. + + case ID_REDO: + if (mEditor != null) { + mEditor.redo(); + } + return true; // Returns true even if nothing was undone. + case ID_PASTE: paste(min, max, true /* withFormatting */); return true; @@ -8948,7 +8971,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ protected void stopSelectionActionMode() { - mEditor.stopSelectionActionMode(); + if (mEditor != null) { + mEditor.stopSelectionActionMode(); + } + } + + boolean canUndo() { + return mEditor != null && mEditor.canUndo(); + } + + boolean canRedo() { + return mEditor != null && mEditor.canRedo(); } boolean canCut() { @@ -9325,6 +9358,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener CharSequence text; boolean frozenWithFocus; CharSequence error; + ParcelableParcel editorState; // Optional state from Editor. SavedState(Parcelable superState) { super(superState); @@ -9344,6 +9378,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener out.writeInt(1); TextUtils.writeToParcel(error, out, flags); } + + if (editorState == null) { + out.writeInt(0); + } else { + out.writeInt(1); + editorState.writeToParcel(out, flags); + } } @Override @@ -9379,6 +9420,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (in.readInt() != 0) { error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } + + if (in.readInt() != 0) { + editorState = ParcelableParcel.CREATOR.createFromParcel(in); + } } } diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 6e2f534..d657bad 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -90,4 +90,6 @@ <item type="id" name="statusBarBackground" /> <item type="id" name="navigationBarBackground" /> <item type="id" name="pasteAsPlainText" /> + <item type="id" name="undo" /> + <item type="id" name="redo" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2869091..37f8232 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2645,5 +2645,9 @@ <!-- Context menu ID for the "Paste as plain text" menu item to to copy the current contents of the clipboard into the text view without formatting. --> <public type="id" name="pasteAsPlainText" /> - + <!-- Context menu ID for the "Undo" menu item to undo the last text edit operation. --> + <public type="id" name="undo" /> + <!-- Context menu ID for the "Redo" menu item to redo the last text edit operation. --> + <public type="id" name="redo" /> + </resources> |