summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Cook <jamescook@google.com>2015-02-19 18:36:18 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-02-19 18:36:19 +0000
commitc20f54aff64a5b29003968249906c9443208a845 (patch)
tree08d9c1a04b09d5f7e3a8be3de2cdb8cf78aa576b
parent0a7269bbafb91e86f57963a30edcc4c8aa49af44 (diff)
parent9201e797833f35b9afb219f88c10d3b6fda02a4e (diff)
downloadframeworks_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.txt2
-rw-r--r--api/system-current.txt2
-rw-r--r--core/java/android/widget/Editor.java67
-rw-r--r--core/java/android/widget/TextView.java90
-rw-r--r--core/res/res/values/ids.xml2
-rw-r--r--core/res/res/values/public.xml5
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>