FileDocCategorySizeDatePackage
UndoManager.javaAPI DocAndroid 5.1 API31630Thu Mar 12 22:22:10 GMT 2015android.content

UndoManager

public class UndoManager extends Object
Top-level class for managing and interacting with the global undo state for a document or application. This class supports both undo and redo and has helpers for merging undoable operations together as they are performed.

A single undoable operation is represented by {@link UndoOperation} which apps implement to define their undo/redo behavior. The UndoManager keeps a stack of undo states; each state can have one or more undo operations inside of it.

Updates to the stack must be done inside of a {@link #beginUpdate}/{@link #endUpdate()} pair. During this time you can add new operations to the stack with {@link #addOperation}, retrieve and modify existing operations with {@link #getLastOperation}, control the label shown to the user for this operation with {@link #setUndoLabel} and {@link #suggestUndoLabel}, etc.

Every {link UndoOperation} is associated with an {@link UndoOwner}, which identifies the data it belongs to. The owner is used to indicate how operations are dependent on each other -- operations with the same owner are dependent on others with the same owner. For example, you may have a document with multiple embedded objects. If the document itself and each embedded object use different owners, then you can provide undo semantics appropriate to the user's context: while within an embedded object, only edits to that object are seen and the user can undo/redo them without needing to impact edits in other objects; while within the larger document, all edits can be seen and the user must undo/redo them as a single stream.

hide

Fields Summary
private final HashMap
mOwners
private final ArrayList
mUndos
private final ArrayList
mRedos
private int
mUpdateCount
private int
mHistorySize
private UndoState
mWorking
private int
mCommitId
private boolean
mInUndo
private boolean
mMerged
private int
mStateSeq
private int
mNextSavedIdx
private UndoOwner[]
mStateOwners
public static final int
MERGE_MODE_NONE
Never merge with the last undo state.
public static final int
MERGE_MODE_UNIQUE
Allow merge with the last undo state only if it contains operations with the caller's owner.
public static final int
MERGE_MODE_ANY
Always allow merge with the last undo state, if possible.
Constructors Summary
Methods Summary
public voidaddOperation(UndoOperation op, int mergeMode)
Add a new UndoOperation to the current update.

param
op The new operation to add.
param
mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, or {@link #MERGE_MODE_ANY}.

        if (mWorking == null) {
            throw new IllegalStateException("Must be called during an update");
        }
        UndoOwner owner = op.getOwner();
        if (owner.mManager != this) {
            throw new IllegalArgumentException(
                    "Given operation's owner is not in this undo manager.");
        }
        if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) {
            UndoState state = getTopUndo(null);
            if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners())
                    && state.canMerge() && state.hasOperation(op.getOwner())) {
                mWorking.destroy();
                mWorking = state;
                mUndos.remove(state);
                mMerged = true;
            }
        }
        mWorking.addOperation(op);
    
public voidbeginUpdate(java.lang.CharSequence label)
Start creating a new undo state. Multiple calls to this function will nest until they are all matched by a later call to {@link #endUpdate}.

param
label Optional user-visible label for this new undo state.

        if (mInUndo) {
            throw new IllegalStateException("Can't being update while performing undo/redo");
        }
        if (mUpdateCount <= 0) {
            createWorkingState();
            mMerged = false;
            mUpdateCount = 0;
        }

        mWorking.updateLabel(label);
        mUpdateCount++;
    
public intcommitState(UndoOwner owner)
Commit the last finished undo state. This undo state can no longer be modified with further {@link #MERGE_MODE_UNIQUE} or {@link #MERGE_MODE_ANY} merge modes. If called while inside of an update, this will push any changes in the current update on to the undo stack and result with a fresh undo state, behaving as if {@link #endUpdate()} had been called enough to unwind the current update, then the last state committed, and {@link #beginUpdate} called to restore the update nesting.

param
owner The optional owner to determine whether to perform the commit. If this is non-null, the commit will only execute if the current top undo state contains an operation with the given owner.
return
Returns an integer identifier for the committed undo state, which can later be used to try to uncommit the state to perform further edits on it.

        if (mWorking != null && mWorking.hasData()) {
            if (owner == null || mWorking.hasOperation(owner)) {
                mWorking.setCanMerge(false);
                int commitId = mWorking.getCommitId();
                pushWorkingState();
                createWorkingState();
                mMerged = true;
                return commitId;
            }
        } else {
            UndoState state = getTopUndo(null);
            if (state != null && (owner == null || state.hasOperation(owner))) {
                state.setCanMerge(false);
                return state.getCommitId();
            }
        }
        return -1;
    
public intcountRedos(UndoOwner[] owners)
Return the number of redo states on the undo stack.

param
owners If non-null, only those states containing an operation with one of the owners supplied here will be counted.

        if (owners == null) {
            return mRedos.size();
        }

        int count=0;
        int i=0;
        while ((i=findNextState(mRedos, owners, i)) >= 0) {
            count++;
            i++;
        }
        return count;
    
public intcountUndos(UndoOwner[] owners)
Return the number of undo states on the undo stack.

param
owners If non-null, only those states containing an operation with one of the owners supplied here will be counted.

        if (owners == null) {
            return mUndos.size();
        }

        int count=0;
        int i=0;
        while ((i=findNextState(mUndos, owners, i)) >= 0) {
            count++;
            i++;
        }
        return count;
    
private voidcreateWorkingState()

        mWorking = new UndoState(this, mCommitId++);
        if (mCommitId < 0) {
            mCommitId = 1;
        }
    
public voidendUpdate()
Finish the creation of an undo state, matching a previous call to {@link #beginUpdate}.

        if (mWorking == null) {
            throw new IllegalStateException("Must be called during an update");
        }
        mUpdateCount--;

        if (mUpdateCount == 0) {
            pushWorkingState();
        }
    
intfindNextState(java.util.ArrayList states, UndoOwner[] owners, int from)

        final int N = states.size();

        if (from < 0) {
            from = 0;
        }
        if (from >= N) {
            return -1;
        }
        if (owners == null) {
            return from;
        }

        while (from < N) {
            UndoState state = states.get(from);
            if (matchOwners(state, owners)) {
                return from;
            }
            from++;
        }

        return -1;
    
intfindPrevState(java.util.ArrayList states, UndoOwner[] owners, int from)

        final int N = states.size();

        if (from == -1) {
            from = N-1;
        }
        if (from >= N) {
            return -1;
        }
        if (owners == null) {
            return from;
        }

        while (from >= 0) {
            UndoState state = states.get(from);
            if (matchOwners(state, owners)) {
                return from;
            }
            from--;
        }

        return -1;
    
public intforgetRedos(UndoOwner[] owners, int count)

        if (count < 0) {
            count = mRedos.size();
        }

        int removed = 0;
        for (int i=0; i<mRedos.size() && removed < count; i++) {
            UndoState state = mRedos.get(i);
            if (count > 0 && matchOwners(state, owners)) {
                state.destroy();
                mRedos.remove(i);
                removed++;
            }
        }

        return removed;
    
public intforgetUndos(UndoOwner[] owners, int count)

        if (count < 0) {
            count = mUndos.size();
        }

        int removed = 0;
        for (int i=0; i<mUndos.size() && removed < count; i++) {
            UndoState state = mUndos.get(i);
            if (count > 0 && matchOwners(state, owners)) {
                state.destroy();
                mUndos.remove(i);
                removed++;
            }
        }

        return removed;
    
public intgetHistorySize()
Return the current maximum number of undo states.

        return mHistorySize;
    
public UndoOperationgetLastOperation(int mergeMode)
Return the most recent {@link UndoOperation} that was added to the update.

param
mergeMode May be either {@link #MERGE_MODE_NONE} or {@link #MERGE_MODE_ANY}.

        return getLastOperation(null, null, mergeMode);
    
public UndoOperationgetLastOperation(UndoOwner owner, int mergeMode)
Return the most recent {@link UndoOperation} that was added to the update and has the given owner.

param
owner Optional owner of last operation to retrieve. If null, the last operation regardless of owner will be retrieved; if non-null, the last operation matching the given owner will be retrieved.
param
mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, or {@link #MERGE_MODE_ANY}.

        return getLastOperation(null, owner, mergeMode);
    
public TgetLastOperation(java.lang.Class clazz, UndoOwner owner, int mergeMode)
Return the most recent {@link UndoOperation} that was added to the update and has the given owner.

param
clazz Optional class of the last operation to retrieve. If null, the last operation regardless of class will be retrieved; if non-null, the last operation whose class is the same as the given class will be retrieved.
param
owner Optional owner of last operation to retrieve. If null, the last operation regardless of owner will be retrieved; if non-null, the last operation matching the given owner will be retrieved.
param
mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, or {@link #MERGE_MODE_ANY}.

        if (mWorking == null) {
            throw new IllegalStateException("Must be called during an update");
        }
        if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) {
            UndoState state = getTopUndo(null);
            UndoOperation<?> last;
            if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners())
                    && state.canMerge() && (last=state.getLastOperation(clazz, owner)) != null) {
                if (last.allowMerge()) {
                    mWorking.destroy();
                    mWorking = state;
                    mUndos.remove(state);
                    mMerged = true;
                    return (T)last;
                }
            }
        }

        return mWorking.getLastOperation(clazz, owner);
    
public UndoOwnergetOwner(java.lang.String tag, java.lang.Object data)


          
        if (tag == null) {
            throw new NullPointerException("tag can't be null");
        }
        if (data == null) {
            throw new NullPointerException("data can't be null");
        }
        UndoOwner owner = mOwners.get(tag);
        if (owner != null) {
            if (owner.mData != data) {
                if (owner.mData != null) {
                    throw new IllegalStateException("Owner " + owner + " already exists with data "
                            + owner.mData + " but giving different data " + data);
                }
                owner.mData = data;
            }
            return owner;
        }

        owner = new UndoOwner(tag);
        owner.mManager = this;
        owner.mData = data;
        mOwners.put(tag, owner);
        return owner;
    
public java.lang.CharSequencegetRedoLabel(UndoOwner[] owners)
Return the user-visible label for the top redo state on the stack.

param
owners If non-null, will select the top-most undo state containing an operation with one of the owners supplied here.

        UndoState state = getTopRedo(owners);
        return state != null ? state.getLabel() : null;
    
android.content.UndoManager$UndoStategetTopRedo(UndoOwner[] owners)

        if (mRedos.size() <= 0) {
            return null;
        }
        int i = findPrevState(mRedos, owners, -1);
        return i >= 0 ? mRedos.get(i) : null;
    
android.content.UndoManager$UndoStategetTopUndo(UndoOwner[] owners)

        if (mUndos.size() <= 0) {
            return null;
        }
        int i = findPrevState(mUndos, owners, -1);
        return i >= 0 ? mUndos.get(i) : null;
    
public java.lang.CharSequencegetUndoLabel(UndoOwner[] owners)
Return the user-visible label for the top undo state on the stack.

param
owners If non-null, will select the top-most undo state containing an operation with one of the owners supplied here.

        UndoState state = getTopUndo(owners);
        return state != null ? state.getLabel() : null;
    
public intgetUpdateNestingLevel()
Return the number of times {@link #beginUpdate} has been called without a matching {@link #endUpdate} call.

        return mUpdateCount;
    
public booleanhasOperation(UndoOwner owner)
Check whether there is an {@link UndoOperation} in the current {@link #beginUpdate} undo state.

param
owner Optional owner of the operation to look for. If null, will succeed if there is any operation; if non-null, will only succeed if there is an operation with the given owner.
return
Returns true if there is a matching operation in the current undo state.

        if (mWorking == null) {
            throw new IllegalStateException("Must be called during an update");
        }
        return mWorking.hasOperation(owner);
    
public booleanisInUndo()
Returns true if we are currently inside of an undo/redo operation. This is useful for editors to know whether they should be generating new undo state when they see edit operations happening.

        return mInUndo;
    
public booleanisInUpdate()
Returns true if currently inside of a {@link #beginUpdate}.

        return mUpdateCount > 0;
    
booleanmatchOwners(android.content.UndoManager$UndoState state, UndoOwner[] owners)

        if (owners == null) {
            return true;
        }
        for (int i=0; i<owners.length; i++) {
            if (state.matchOwner(owners[i])) {
                return true;
            }
        }
        return false;
    
private voidpushWorkingState()

        int N = mUndos.size() + 1;

        if (mWorking.hasData()) {
            mUndos.add(mWorking);
            forgetRedos(null, -1);
            mWorking.commit();
            if (N >= 2) {
                // The state before this one can no longer be merged, ever.
                // The only way to get back to it is for the user to perform
                // an undo.
                mUndos.get(N-2).makeExecuted();
            }
        } else {
            mWorking.destroy();
        }
        mWorking = null;

        if (mHistorySize >= 0 && N > mHistorySize) {
            forgetUndos(null, N - mHistorySize);
        }
    
public intredo(UndoOwner[] owners, int count)
Perform redo of last/top count undo states in the transient redo stack. The states impacted by this can be limited through owners.

param
owners Optional set of owners that should be impacted. If null, all undo states will be visible and available for undo. If non-null, only those states that contain one of the owners specified here will be visible.
param
count Number of undo states to pop.
return
Returns the number of undo states that were actually redone.

        if (mWorking != null) {
            throw new IllegalStateException("Can't be called during an update");
        }

        int num = 0;
        int i = -1;

        mInUndo = true;

        while (count > 0 && (i=findPrevState(mRedos, owners, i)) >= 0) {
            UndoState state = mRedos.remove(i);
            state.redo();
            mUndos.add(state);
            count--;
            num++;
        }

        mInUndo = false;

        return num;
    
voidremoveOwner(UndoOwner owner)

        // XXX need to figure out how to prune.
        if (false) {
            mOwners.remove(owner.mTag);
            owner.mManager = null;
        }
    
public voidrestoreInstanceState(android.os.Parcelable state)
Restore an undo state previously created with {@link #saveInstanceState()}. This will restore the UndoManager's state to almost exactly what it was at the point it had been previously saved; the only information not restored is the data object associated with each {@link UndoOwner}, which requires separate calls to {@link #getOwner(String, Object)} to re-associate the owner with its data.

        if (mUpdateCount > 0) {
            throw new IllegalStateException("Can't save state while updating");
        }
        forgetUndos(null, -1);
        forgetRedos(null, -1);
        ParcelableParcel pp = (ParcelableParcel)state;
        Parcel p = pp.getParcel();
        mHistorySize = p.readInt();
        mStateOwners = new UndoOwner[p.readInt()];

        int stype;
        while ((stype=p.readInt()) != 0) {
            UndoState ustate = new UndoState(this, p, pp.getClassLoader());
            if (stype == 1) {
                mUndos.add(0, ustate);
            } else {
                mRedos.add(0, ustate);
            }
        }
    
UndoOwnerrestoreOwner(android.os.Parcel in)

        int idx = in.readInt();
        UndoOwner owner = mStateOwners[idx];
        if (owner == null) {
            String tag = in.readString();
            owner = new UndoOwner(tag);
            mStateOwners[idx] = owner;
            mOwners.put(tag, owner);
        }
        return owner;
    
public android.os.ParcelablesaveInstanceState()
Flatten the current undo state into a Parcelable object, which can later be restored with {@link #restoreInstanceState(android.os.Parcelable)}.

        if (mUpdateCount > 0) {
            throw new IllegalStateException("Can't save state while updating");
        }
        ParcelableParcel pp = new ParcelableParcel(getClass().getClassLoader());
        Parcel p = pp.getParcel();
        mStateSeq++;
        if (mStateSeq <= 0) {
            mStateSeq = 0;
        }
        mNextSavedIdx = 0;
        p.writeInt(mHistorySize);
        p.writeInt(mOwners.size());
        // XXX eventually we need to be smart here about limiting the
        // number of undo states we write to not exceed X bytes.
        int i = mUndos.size();
        while (i > 0) {
            p.writeInt(1);
            i--;
            mUndos.get(i).writeToParcel(p);
        }
        i = mRedos.size();
        p.writeInt(i);
        while (i > 0) {
            p.writeInt(2);
            i--;
            mRedos.get(i).writeToParcel(p);
        }
        p.writeInt(0);
        return pp;
    
voidsaveOwner(UndoOwner owner, android.os.Parcel out)

        if (owner.mStateSeq == mStateSeq) {
            out.writeInt(owner.mSavedIdx);
        } else {
            owner.mStateSeq = mStateSeq;
            owner.mSavedIdx = mNextSavedIdx;
            out.writeInt(owner.mSavedIdx);
            out.writeString(owner.mTag);
            mNextSavedIdx++;
        }
    
public voidsetHistorySize(int size)
Set the maximum number of undo states that will be retained.

        mHistorySize = size;
        if (mHistorySize >= 0 && countUndos(null) > mHistorySize) {
            forgetUndos(null, countUndos(null) - mHistorySize);
        }
    
public voidsetUndoLabel(java.lang.CharSequence label)
Forcibly set a new for the new undo state being built within a {@link #beginUpdate}. Any existing label will be replaced with this one.

        if (mWorking == null) {
            throw new IllegalStateException("Must be called during an update");
        }
        mWorking.setLabel(label);
    
public voidsuggestUndoLabel(java.lang.CharSequence label)
Set a new for the new undo state being built within a {@link #beginUpdate}, but only if there is not a label currently set for it.

        if (mWorking == null) {
            throw new IllegalStateException("Must be called during an update");
        }
        mWorking.updateLabel(label);
    
public booleanuncommitState(int commitId, UndoOwner owner)
Attempt to undo a previous call to {@link #commitState}. This will work if the undo state at the top of the stack has the given id, and has not been involved in an undo operation. Otherwise false is returned.

param
commitId The identifier for the state to be uncommitted, as returned by {@link #commitState}.
param
owner Optional owner that must appear in the committed state.
return
Returns true if the uncommit is successful, else false.

        if (mWorking != null && mWorking.getCommitId() == commitId) {
            if (owner == null || mWorking.hasOperation(owner)) {
                return mWorking.setCanMerge(true);
            }
        } else {
            UndoState state = getTopUndo(null);
            if (state != null && (owner == null || state.hasOperation(owner))) {
                if (state.getCommitId() == commitId) {
                    return state.setCanMerge(true);
                }
            }
        }
        return false;
    
public intundo(UndoOwner[] owners, int count)
Perform undo of last/top count undo states. The states impacted by this can be limited through owners.

param
owners Optional set of owners that should be impacted. If null, all undo states will be visible and available for undo. If non-null, only those states that contain one of the owners specified here will be visible.
param
count Number of undo states to pop.
return
Returns the number of undo states that were actually popped.

        if (mWorking != null) {
            throw new IllegalStateException("Can't be called during an update");
        }

        int num = 0;
        int i = -1;

        mInUndo = true;

        UndoState us = getTopUndo(null);
        if (us != null) {
            us.makeExecuted();
        }

        while (count > 0 && (i=findPrevState(mUndos, owners, i)) >= 0) {
            UndoState state = mUndos.remove(i);
            state.undo();
            mRedos.add(state);
            count--;
            num++;
        }

        mInUndo = false;

        return num;