diff options
author | James Cook <jamescook@google.com> | 2015-02-19 18:36:18 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-02-19 18:36:19 +0000 |
commit | c20f54aff64a5b29003968249906c9443208a845 (patch) | |
tree | 08d9c1a04b09d5f7e3a8be3de2cdb8cf78aa576b | |
parent | 0a7269bbafb91e86f57963a30edcc4c8aa49af44 (diff) | |
parent | 9201e797833f35b9afb219f88c10d3b6fda02a4e (diff) | |
download | frameworks_base-c20f54aff64a5b29003968249906c9443208a845.zip frameworks_base-c20f54aff64a5b29003968249906c9443208a845.tar.gz frameworks_base-c20f54aff64a5b29003968249906c9443208a845.tar.bz2 |
Merge "Add basic support for Ctrl-Z to editable TextViews"
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/widget/Editor.java | 67 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 90 | ||||
-rw-r--r-- | core/res/res/values/ids.xml | 2 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 5 |
6 files changed, 139 insertions, 29 deletions
diff --git a/api/current.txt b/api/current.txt index 7d8b892..cd74fe5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1711,6 +1711,7 @@ package android { field public static final int paste = 16908322; // 0x1020022 field public static final int primary = 16908300; // 0x102000c field public static final int progress = 16908301; // 0x102000d + field public static final int redo = 16908338; // 0x1020032 field public static final int secondaryProgress = 16908303; // 0x102000f field public static final int selectAll = 16908319; // 0x102001f field public static final int selectTextMode = 16908333; // 0x102002d @@ -1727,6 +1728,7 @@ package android { field public static final int text2 = 16908309; // 0x1020015 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 + field public static final int undo = 16908337; // 0x1020031 field public static final int widget_frame = 16908312; // 0x1020018 } diff --git a/api/system-current.txt b/api/system-current.txt index 5e24e51..b016a96 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1787,6 +1787,7 @@ package android { field public static final int paste = 16908322; // 0x1020022 field public static final int primary = 16908300; // 0x102000c field public static final int progress = 16908301; // 0x102000d + field public static final int redo = 16908338; // 0x1020032 field public static final int secondaryProgress = 16908303; // 0x102000f field public static final int selectAll = 16908319; // 0x102001f field public static final int selectTextMode = 16908333; // 0x102002d @@ -1803,6 +1804,7 @@ package android { field public static final int text2 = 16908309; // 0x1020015 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 + field public static final int undo = 16908337; // 0x1020031 field public static final int widget_frame = 16908312; // 0x1020018 } 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 f33ef75..9297731 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); + } } /** @@ -8375,14 +8375,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { - final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK; - if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) { + if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { + // Handle Ctrl-only shortcuts. switch (keyCode) { case KeyEvent.KEYCODE_A: if (canSelectText()) { 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); @@ -8399,6 +8404,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } break; } + } 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; + } } return super.onKeyShortcut(keyCode, event); } @@ -8775,6 +8789,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; @@ -8805,6 +8821,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); return true; @@ -8934,7 +8962,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() { @@ -9304,6 +9342,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); @@ -9323,6 +9362,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 @@ -9358,6 +9404,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 bd24f3e..1f4d37c 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -89,4 +89,6 @@ <item type="id" name="parentMatrix" /> <item type="id" name="statusBarBackground" /> <item type="id" name="navigationBarBackground" /> + <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 cfff420..8814138 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2634,4 +2634,9 @@ <public type="style" name="Theme.Material.DayNight.Panel" /> <public type="style" name="Theme.Material.Light.LightStatusBar" /> + <!-- 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> |