13aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn/*
23aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * Copyright (C) 2013 The Android Open Source Project
33aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *
43aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * Licensed under the Apache License, Version 2.0 (the "License");
53aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * you may not use this file except in compliance with the License.
63aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * You may obtain a copy of the License at
73aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *
83aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *      http://www.apache.org/licenses/LICENSE-2.0
93aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *
103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * Unless required by applicable law or agreed to in writing, software
113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * distributed under the License is distributed on an "AS IS" BASIS,
123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * See the License for the specific language governing permissions and
143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * limitations under the License.
153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn */
163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornpackage android.content;
183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornimport android.os.Parcel;
203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornimport android.os.Parcelable;
213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornimport android.os.ParcelableParcel;
223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornimport android.text.TextUtils;
233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornimport java.util.ArrayList;
253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornimport java.util.HashMap;
263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn/**
283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * Top-level class for managing and interacting with the global undo state for
293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * a document or application.  This class supports both undo and redo and has
303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * helpers for merging undoable operations together as they are performed.
313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *
323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * <p>A single undoable operation is represented by {@link UndoOperation} which
333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * apps implement to define their undo/redo behavior.  The UndoManager keeps
343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * a stack of undo states; each state can have one or more undo operations
353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * inside of it.</p>
363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *
373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * <p>Updates to the stack must be done inside of a {@link #beginUpdate}/{@link #endUpdate()}
383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * pair.  During this time you can add new operations to the stack with
393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * {@link #addOperation}, retrieve and modify existing operations with
403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * {@link #getLastOperation}, control the label shown to the user for this operation
413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * with {@link #setUndoLabel} and {@link #suggestUndoLabel}, etc.</p>
423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn *
433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * <p>Every {link UndoOperation} is associated with an {@link UndoOwner}, which identifies
443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * the data it belongs to.  The owner is used to indicate how operations are dependent
453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * on each other -- operations with the same owner are dependent on others with the
463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * same owner.  For example, you may have a document with multiple embedded objects.  If the
473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * document itself and each embedded object use different owners, then you
483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * can provide undo semantics appropriate to the user's context: while within
493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * an embedded object, only edits to that object are seen and the user can
503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * undo/redo them without needing to impact edits in other objects; while
513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * within the larger document, all edits can be seen and the user must
523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn * undo/redo them as a single stream.</p>
53b811e64cb325c8b9c46a2e8e97ef1aa86ac8664bDianne Hackborn *
54b811e64cb325c8b9c46a2e8e97ef1aa86ac8664bDianne Hackborn * @hide
553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn */
563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackbornpublic class UndoManager {
573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private final HashMap<String, UndoOwner> mOwners = new HashMap<String, UndoOwner>();
583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>();
593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>();
603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private int mUpdateCount;
613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private int mHistorySize = 20;
623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private UndoState mWorking;
633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private int mCommitId = 1;
643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private boolean mInUndo;
653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private boolean mMerged;
663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private int mStateSeq;
683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private int mNextSavedIdx;
693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private UndoOwner[] mStateOwners;
703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Never merge with the last undo state.
733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public static final int MERGE_MODE_NONE = 0;
753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Allow merge with the last undo state only if it contains
783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * operations with the caller's owner.
793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public static final int MERGE_MODE_UNIQUE = 1;
813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Always allow merge with the last undo state, if possible.
843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public static final int MERGE_MODE_ANY = 2;
863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public UndoOwner getOwner(String tag, Object data) {
883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (tag == null) {
893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new NullPointerException("tag can't be null");
903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (data == null) {
923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new NullPointerException("data can't be null");
933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoOwner owner = mOwners.get(tag);
953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owner != null) {
963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (owner.mData != data) {
973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (owner.mData != null) {
983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    throw new IllegalStateException("Owner " + owner + " already exists with data "
993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                            + owner.mData + " but giving different data " + data);
1003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
1013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                owner.mData = data;
1023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
1033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return owner;
1043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        owner = new UndoOwner(tag);
1073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        owner.mManager = this;
1083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        owner.mData = data;
1093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mOwners.put(tag, owner);
1103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return owner;
1113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
1123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    void removeOwner(UndoOwner owner) {
1143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        // XXX need to figure out how to prune.
1153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (false) {
1163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mOwners.remove(owner.mTag);
1173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            owner.mManager = null;
1183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
1203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
1223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Flatten the current undo state into a Parcelable object, which can later be restored
1233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * with {@link #restoreInstanceState(android.os.Parcelable)}.
1243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
1253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public Parcelable saveInstanceState() {
1263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mUpdateCount > 0) {
1273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Can't save state while updating");
1283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        ParcelableParcel pp = new ParcelableParcel(getClass().getClassLoader());
1303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        Parcel p = pp.getParcel();
1313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mStateSeq++;
1323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mStateSeq <= 0) {
1333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mStateSeq = 0;
1343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mNextSavedIdx = 0;
1363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        p.writeInt(mHistorySize);
1373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        p.writeInt(mOwners.size());
1383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        // XXX eventually we need to be smart here about limiting the
1393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        // number of undo states we write to not exceed X bytes.
1403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i = mUndos.size();
1413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while (i > 0) {
1423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            p.writeInt(1);
1433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            i--;
1443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mUndos.get(i).writeToParcel(p);
1453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        i = mRedos.size();
1473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        p.writeInt(i);
1483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while (i > 0) {
1493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            p.writeInt(2);
1503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            i--;
1513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mRedos.get(i).writeToParcel(p);
1523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        p.writeInt(0);
1543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return pp;
1553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
1563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    void saveOwner(UndoOwner owner, Parcel out) {
1583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owner.mStateSeq == mStateSeq) {
1593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            out.writeInt(owner.mSavedIdx);
1603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        } else {
1613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            owner.mStateSeq = mStateSeq;
1623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            owner.mSavedIdx = mNextSavedIdx;
1633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            out.writeInt(owner.mSavedIdx);
1643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            out.writeString(owner.mTag);
1653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mNextSavedIdx++;
1663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
1683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
1703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Restore an undo state previously created with {@link #saveInstanceState()}.  This will
1713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * restore the UndoManager's state to almost exactly what it was at the point it had
1723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * been previously saved; the only information not restored is the data object
1733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * associated with each {@link UndoOwner}, which requires separate calls to
1743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * {@link #getOwner(String, Object)} to re-associate the owner with its data.
1753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
1763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void restoreInstanceState(Parcelable state) {
1773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mUpdateCount > 0) {
1783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Can't save state while updating");
1793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        forgetUndos(null, -1);
1813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        forgetRedos(null, -1);
1823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        ParcelableParcel pp = (ParcelableParcel)state;
1833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        Parcel p = pp.getParcel();
1843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mHistorySize = p.readInt();
1853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mStateOwners = new UndoOwner[p.readInt()];
1863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int stype;
1883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while ((stype=p.readInt()) != 0) {
1893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState ustate = new UndoState(this, p, pp.getClassLoader());
1903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (stype == 1) {
1913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mUndos.add(0, ustate);
1923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            } else {
1933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mRedos.add(0, ustate);
1943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
1953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
1963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
1973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
1983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    UndoOwner restoreOwner(Parcel in) {
1993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int idx = in.readInt();
2003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoOwner owner = mStateOwners[idx];
2013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owner == null) {
2023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            String tag = in.readString();
2033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            owner = new UndoOwner(tag);
2043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mStateOwners[idx] = owner;
2053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mOwners.put(tag, owner);
2063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return owner;
2083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
2093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
2113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Set the maximum number of undo states that will be retained.
2123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
2133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void setHistorySize(int size) {
2143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mHistorySize = size;
2153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mHistorySize >= 0 && countUndos(null) > mHistorySize) {
2163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            forgetUndos(null, countUndos(null) - mHistorySize);
2173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
2193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
2213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the current maximum number of undo states.
2223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
2233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int getHistorySize() {
2243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return mHistorySize;
2253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
2263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
2283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Perform undo of last/top <var>count</var> undo states.  The states impacted
2293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * by this can be limited through <var>owners</var>.
2303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owners Optional set of owners that should be impacted.  If null, all
2313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * undo states will be visible and available for undo.  If non-null, only those
2323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * states that contain one of the owners specified here will be visible.
2333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param count Number of undo states to pop.
2343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @return Returns the number of undo states that were actually popped.
2353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
2363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int undo(UndoOwner[] owners, int count) {
2373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking != null) {
2383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Can't be called during an update");
2393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int num = 0;
2423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i = -1;
2433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mInUndo = true;
2453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoState us = getTopUndo(null);
2473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (us != null) {
2483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            us.makeExecuted();
2493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while (count > 0 && (i=findPrevState(mUndos, owners, i)) >= 0) {
2523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = mUndos.remove(i);
2533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            state.undo();
2543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mRedos.add(state);
2553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            count--;
2563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            num++;
2573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mInUndo = false;
2603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return num;
2623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
2633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
2653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Perform redo of last/top <var>count</var> undo states in the transient redo stack.
2663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * The states impacted by this can be limited through <var>owners</var>.
2673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owners Optional set of owners that should be impacted.  If null, all
2683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * undo states will be visible and available for undo.  If non-null, only those
2693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * states that contain one of the owners specified here will be visible.
2703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param count Number of undo states to pop.
2713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @return Returns the number of undo states that were actually redone.
2723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
2733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int redo(UndoOwner[] owners, int count) {
2743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking != null) {
2753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Can't be called during an update");
2763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int num = 0;
2793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i = -1;
2803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mInUndo = true;
2823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while (count > 0 && (i=findPrevState(mRedos, owners, i)) >= 0) {
2843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = mRedos.remove(i);
2853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            state.redo();
2863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mUndos.add(state);
2873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            count--;
2883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            num++;
2893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
2903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mInUndo = false;
2923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return num;
2943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
2953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
2963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
2973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Returns true if we are currently inside of an undo/redo operation.  This is
2983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * useful for editors to know whether they should be generating new undo state
2993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * when they see edit operations happening.
3003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
3013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public boolean isInUndo() {
3023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return mInUndo;
3033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int forgetUndos(UndoOwner[] owners, int count) {
3063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (count < 0) {
3073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            count = mUndos.size();
3083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int removed = 0;
3113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        for (int i=0; i<mUndos.size() && removed < count; i++) {
3123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = mUndos.get(i);
3133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (count > 0 && matchOwners(state, owners)) {
3143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                state.destroy();
3153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mUndos.remove(i);
3163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                removed++;
3173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
3183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return removed;
3213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int forgetRedos(UndoOwner[] owners, int count) {
3243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (count < 0) {
3253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            count = mRedos.size();
3263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int removed = 0;
3293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        for (int i=0; i<mRedos.size() && removed < count; i++) {
3303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = mRedos.get(i);
3313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (count > 0 && matchOwners(state, owners)) {
3323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                state.destroy();
3333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mRedos.remove(i);
3343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                removed++;
3353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
3363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return removed;
3393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
3423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the number of undo states on the undo stack.
3433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owners If non-null, only those states containing an operation with one of
3443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * the owners supplied here will be counted.
3453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
3463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int countUndos(UndoOwner[] owners) {
3473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owners == null) {
3483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return mUndos.size();
3493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int count=0;
3523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i=0;
3533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while ((i=findNextState(mUndos, owners, i)) >= 0) {
3543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            count++;
3553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            i++;
3563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return count;
3583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
3613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the number of redo states on the undo stack.
3623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owners If non-null, only those states containing an operation with one of
3633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * the owners supplied here will be counted.
3643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
3653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int countRedos(UndoOwner[] owners) {
3663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owners == null) {
3673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return mRedos.size();
3683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int count=0;
3713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i=0;
3723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while ((i=findNextState(mRedos, owners, i)) >= 0) {
3733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            count++;
3743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            i++;
3753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
3763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return count;
3773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
3803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the user-visible label for the top undo state on the stack.
3813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owners If non-null, will select the top-most undo state containing an
3823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * operation with one of the owners supplied here.
3833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
3843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public CharSequence getUndoLabel(UndoOwner[] owners) {
3853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoState state = getTopUndo(owners);
3863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return state != null ? state.getLabel() : null;
3873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
3903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the user-visible label for the top redo state on the stack.
3913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owners If non-null, will select the top-most undo state containing an
3923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * operation with one of the owners supplied here.
3933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
3943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public CharSequence getRedoLabel(UndoOwner[] owners) {
3953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoState state = getTopRedo(owners);
3963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return state != null ? state.getLabel() : null;
3973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
3983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
3993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Start creating a new undo state.  Multiple calls to this function will nest until
4013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * they are all matched by a later call to {@link #endUpdate}.
4023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param label Optional user-visible label for this new undo state.
4033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void beginUpdate(CharSequence label) {
4053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mInUndo) {
4063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Can't being update while performing undo/redo");
4073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
4083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mUpdateCount <= 0) {
4093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            createWorkingState();
4103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mMerged = false;
4113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mUpdateCount = 0;
4123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
4133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mWorking.updateLabel(label);
4153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mUpdateCount++;
4163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private void createWorkingState() {
4193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mWorking = new UndoState(this, mCommitId++);
4203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mCommitId < 0) {
4213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mCommitId = 1;
4223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
4233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Returns true if currently inside of a {@link #beginUpdate}.
4273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public boolean isInUpdate() {
4293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return mUpdateCount > 0;
4303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Forcibly set a new for the new undo state being built within a {@link #beginUpdate}.
4343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Any existing label will be replaced with this one.
4353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void setUndoLabel(CharSequence label) {
4373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking == null) {
4383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Must be called during an update");
4393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
4403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mWorking.setLabel(label);
4413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Set a new for the new undo state being built within a {@link #beginUpdate}, but
4453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * only if there is not a label currently set for it.
4463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void suggestUndoLabel(CharSequence label) {
4483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking == null) {
4493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Must be called during an update");
4503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
4513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mWorking.updateLabel(label);
4523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the number of times {@link #beginUpdate} has been called without a matching
4563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * {@link #endUpdate} call.
4573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int getUpdateNestingLevel() {
4593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return mUpdateCount;
4603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Check whether there is an {@link UndoOperation} in the current {@link #beginUpdate}
4643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * undo state.
4653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owner Optional owner of the operation to look for.  If null, will succeed
4663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * if there is any operation; if non-null, will only succeed if there is an operation
4673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * with the given owner.
4683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @return Returns true if there is a matching operation in the current undo state.
4693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public boolean hasOperation(UndoOwner owner) {
4713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking == null) {
4723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Must be called during an update");
4733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
4743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return mWorking.hasOperation(owner);
4753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the most recent {@link UndoOperation} that was added to the update.
4793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param mergeMode May be either {@link #MERGE_MODE_NONE} or {@link #MERGE_MODE_ANY}.
4803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public UndoOperation<?> getLastOperation(int mergeMode) {
4823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return getLastOperation(null, null, mergeMode);
4833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the most recent {@link UndoOperation} that was added to the update and
4873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * has the given owner.
4883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owner Optional owner of last operation to retrieve.  If null, the last
4893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * operation regardless of owner will be retrieved; if non-null, the last operation
4903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * matching the given owner will be retrieved.
4913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE},
4923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * or {@link #MERGE_MODE_ANY}.
4933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
4943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public UndoOperation<?> getLastOperation(UndoOwner owner, int mergeMode) {
4953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return getLastOperation(null, owner, mergeMode);
4963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
4973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
4983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
4993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Return the most recent {@link UndoOperation} that was added to the update and
5003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * has the given owner.
5013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param clazz Optional class of the last operation to retrieve.  If null, the
5023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * last operation regardless of class will be retrieved; if non-null, the last
5033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * operation whose class is the same as the given class will be retrieved.
5043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owner Optional owner of last operation to retrieve.  If null, the last
5053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * operation regardless of owner will be retrieved; if non-null, the last operation
5063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * matching the given owner will be retrieved.
5073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE},
5083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * or {@link #MERGE_MODE_ANY}.
5093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
5103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public <T extends UndoOperation> T getLastOperation(Class<T> clazz, UndoOwner owner,
5113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            int mergeMode) {
5123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking == null) {
5133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Must be called during an update");
5143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) {
5163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = getTopUndo(null);
5173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoOperation<?> last;
5183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners())
5193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    && state.canMerge() && (last=state.getLastOperation(clazz, owner)) != null) {
5203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (last.allowMerge()) {
5213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    mWorking.destroy();
5223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    mWorking = state;
5233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    mUndos.remove(state);
5243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    mMerged = true;
5253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return (T)last;
5263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
5273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
5283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return mWorking.getLastOperation(clazz, owner);
5313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
5323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
5343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Add a new UndoOperation to the current update.
5353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param op The new operation to add.
5363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE},
5373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * or {@link #MERGE_MODE_ANY}.
5383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
5393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void addOperation(UndoOperation<?> op, int mergeMode) {
5403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking == null) {
5413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Must be called during an update");
5423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoOwner owner = op.getOwner();
5443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owner.mManager != this) {
5453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalArgumentException(
5463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    "Given operation's owner is not in this undo manager.");
5473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) {
5493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = getTopUndo(null);
5503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners())
5513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    && state.canMerge() && state.hasOperation(op.getOwner())) {
5523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mWorking.destroy();
5533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mWorking = state;
5543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mUndos.remove(state);
5553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mMerged = true;
5563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
5573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mWorking.addOperation(op);
5593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
5603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
5623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Finish the creation of an undo state, matching a previous call to
5633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * {@link #beginUpdate}.
5643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
5653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public void endUpdate() {
5663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking == null) {
5673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            throw new IllegalStateException("Must be called during an update");
5683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mUpdateCount--;
5703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mUpdateCount == 0) {
5723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            pushWorkingState();
5733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
5753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    private void pushWorkingState() {
5773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int N = mUndos.size() + 1;
5783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking.hasData()) {
5803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mUndos.add(mWorking);
5813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            forgetRedos(null, -1);
5823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mWorking.commit();
5833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (N >= 2) {
5843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                // The state before this one can no longer be merged, ever.
5853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                // The only way to get back to it is for the user to perform
5863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                // an undo.
5873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mUndos.get(N-2).makeExecuted();
5883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
5893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        } else {
5903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mWorking.destroy();
5913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        mWorking = null;
5933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mHistorySize >= 0 && N > mHistorySize) {
5953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            forgetUndos(null, N - mHistorySize);
5963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
5973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
5983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
5993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
6003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Commit the last finished undo state.  This undo state can no longer be
6013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * modified with further {@link #MERGE_MODE_UNIQUE} or
6023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * {@link #MERGE_MODE_ANY} merge modes.  If called while inside of an update,
6033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * this will push any changes in the current update on to the undo stack
6043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * and result with a fresh undo state, behaving as if {@link #endUpdate()}
6053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * had been called enough to unwind the current update, then the last state
6063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * committed, and {@link #beginUpdate} called to restore the update nesting.
6073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owner The optional owner to determine whether to perform the commit.
6083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * If this is non-null, the commit will only execute if the current top undo
6093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * state contains an operation with the given owner.
6103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @return Returns an integer identifier for the committed undo state, which
6113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * can later be used to try to uncommit the state to perform further edits on it.
6123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
6133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public int commitState(UndoOwner owner) {
6143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking != null && mWorking.hasData()) {
6153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (owner == null || mWorking.hasOperation(owner)) {
6163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mWorking.setCanMerge(false);
6173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                int commitId = mWorking.getCommitId();
6183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                pushWorkingState();
6193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                createWorkingState();
6203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mMerged = true;
6213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return commitId;
6223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
6233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        } else {
6243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = getTopUndo(null);
6253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (state != null && (owner == null || state.hasOperation(owner))) {
6263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                state.setCanMerge(false);
6273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return state.getCommitId();
6283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
6293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return -1;
6313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
6323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    /**
6343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * Attempt to undo a previous call to {@link #commitState}.  This will work
6353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * if the undo state at the top of the stack has the given id, and has not been
6363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * involved in an undo operation.  Otherwise false is returned.
6373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param commitId The identifier for the state to be uncommitted, as returned
6383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * by {@link #commitState}.
6393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @param owner Optional owner that must appear in the committed state.
6403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     * @return Returns true if the uncommit is successful, else false.
6413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn     */
6423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    public boolean uncommitState(int commitId, UndoOwner owner) {
6433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mWorking != null && mWorking.getCommitId() == commitId) {
6443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (owner == null || mWorking.hasOperation(owner)) {
6453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return mWorking.setCanMerge(true);
6463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
6473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        } else {
6483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = getTopUndo(null);
6493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (state != null && (owner == null || state.hasOperation(owner))) {
6503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (state.getCommitId() == commitId) {
6513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return state.setCanMerge(true);
6523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
6533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
6543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return false;
6563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
6573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    UndoState getTopUndo(UndoOwner[] owners) {
6593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mUndos.size() <= 0) {
6603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return null;
6613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i = findPrevState(mUndos, owners, -1);
6633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return i >= 0 ? mUndos.get(i) : null;
6643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
6653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    UndoState getTopRedo(UndoOwner[] owners) {
6673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (mRedos.size() <= 0) {
6683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return null;
6693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int i = findPrevState(mRedos, owners, -1);
6713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return i >= 0 ? mRedos.get(i) : null;
6723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
6733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    boolean matchOwners(UndoState state, UndoOwner[] owners) {
6753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owners == null) {
6763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return true;
6773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        for (int i=0; i<owners.length; i++) {
6793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (state.matchOwner(owners[i])) {
6803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return true;
6813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
6823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return false;
6843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
6853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    int findPrevState(ArrayList<UndoState> states, UndoOwner[] owners, int from) {
6873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        final int N = states.size();
6883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (from == -1) {
6903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            from = N-1;
6913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (from >= N) {
6933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return -1;
6943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owners == null) {
6963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return from;
6973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
6983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
6993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while (from >= 0) {
7003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = states.get(from);
7013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (matchOwners(state, owners)) {
7023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return from;
7033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
7043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            from--;
7053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return -1;
7083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
7093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    int findNextState(ArrayList<UndoState> states, UndoOwner[] owners, int from) {
7113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        final int N = states.size();
7123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (from < 0) {
7143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            from = 0;
7153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (from >= N) {
7173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return -1;
7183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        if (owners == null) {
7203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return from;
7213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        while (from < N) {
7243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoState state = states.get(from);
7253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (matchOwners(state, owners)) {
7263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return from;
7273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
7283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            from++;
7293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        return -1;
7323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
7333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    final static class UndoState {
7353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private final UndoManager mManager;
7363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private final int mCommitId;
7373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private final ArrayList<UndoOperation<?>> mOperations = new ArrayList<UndoOperation<?>>();
7383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private ArrayList<UndoOperation<?>> mRecent;
7393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private CharSequence mLabel;
7403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private boolean mCanMerge = true;
7413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        private boolean mExecuted;
7423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoState(UndoManager manager, int commitId) {
7443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mManager = manager;
7453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mCommitId = commitId;
7463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        UndoState(UndoManager manager, Parcel p, ClassLoader loader) {
7493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mManager = manager;
7503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mCommitId = p.readInt();
7513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mCanMerge = p.readInt() != 0;
7523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mExecuted = p.readInt() != 0;
7533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
7543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = p.readInt();
7553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=0; i<N; i++) {
7563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                UndoOwner owner = mManager.restoreOwner(p);
7573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                UndoOperation op = (UndoOperation)p.readParcelable(loader);
7583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                op.mOwner = owner;
7593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mOperations.add(op);
7603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
7613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void writeToParcel(Parcel p) {
7643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (mRecent != null) {
7653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                throw new IllegalStateException("Can't save state before committing");
7663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
7673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            p.writeInt(mCommitId);
7683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            p.writeInt(mCanMerge ? 1 : 0);
7693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            p.writeInt(mExecuted ? 1 : 0);
7703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            TextUtils.writeToParcel(mLabel, p, 0);
7713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = mOperations.size();
7723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            p.writeInt(N);
7733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=0; i<N; i++) {
7743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                UndoOperation op = mOperations.get(i);
7753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mManager.saveOwner(op.mOwner, p);
7763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                p.writeParcelable(op, 0);
7773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
7783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int getCommitId() {
7813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return mCommitId;
7823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void setLabel(CharSequence label) {
7853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mLabel = label;
7863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void updateLabel(CharSequence label) {
7893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (mLabel != null) {
7903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mLabel = label;
7913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
7923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        CharSequence getLabel() {
7953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return mLabel;
7963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
7973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
7983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        boolean setCanMerge(boolean state) {
7993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            // Don't allow re-enabling of merging if state has been executed.
8003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (state && mExecuted) {
8013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return false;
8023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mCanMerge = state;
8043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return true;
8053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void makeExecuted() {
8083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mExecuted = true;
8093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        boolean canMerge() {
8123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return mCanMerge && !mExecuted;
8133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        int countOperations() {
8163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return mOperations.size();
8173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        boolean hasOperation(UndoOwner owner) {
8203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = mOperations.size();
8213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (owner == null) {
8223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return N != 0;
8233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=0; i<N; i++) {
8253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (mOperations.get(i).getOwner() == owner) {
8263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return true;
8273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
8283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return false;
8303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        boolean hasMultipleOwners() {
8333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = mOperations.size();
8343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (N <= 1) {
8353aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return false;
8363aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8373aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            UndoOwner owner = mOperations.get(0).getOwner();
8383aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=1; i<N; i++) {
8393aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (mOperations.get(i).getOwner() != owner) {
8403aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return true;
8413aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
8423aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8433aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return false;
8443aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8453aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8463aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void addOperation(UndoOperation<?> op) {
8473aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (mOperations.contains(op)) {
8483aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                throw new IllegalStateException("Already holds " + op);
8493aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8503aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mOperations.add(op);
8513aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (mRecent == null) {
8523aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mRecent = new ArrayList<UndoOperation<?>>();
8533aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mRecent.add(op);
8543aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8553aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            op.mOwner.mOpCount++;
8563aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8573aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8583aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        <T extends UndoOperation> T getLastOperation(Class<T> clazz, UndoOwner owner) {
8593aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = mOperations.size();
8603aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            if (clazz == null && owner == null) {
8613aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return N > 0 ? (T)mOperations.get(N-1) : null;
8623aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8633aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            // First look for the top-most operation with the same owner.
8643aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=N-1; i>=0; i--) {
8653aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                UndoOperation<?> op = mOperations.get(i);
8663aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (owner != null && op.getOwner() != owner) {
8673aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    continue;
8683aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
8693aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                // Return this operation if it has the same class that the caller wants.
8703aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                // Note that we don't search deeper for the class, because we don't want
8713aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                // to end up with a different order of operations for the same owner.
8723aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (clazz != null && op.getClass() != clazz) {
8733aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return null;
8743aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
8753aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                return (T)op;
8763aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8773aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8783aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return null;
8793aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8803aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8813aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        boolean matchOwner(UndoOwner owner) {
8823aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=mOperations.size()-1; i>=0; i--) {
8833aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (mOperations.get(i).matchOwner(owner)) {
8843aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return true;
8853aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
8863aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8873aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return false;
8883aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8893aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8903aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        boolean hasData() {
8913aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=mOperations.size()-1; i>=0; i--) {
8923aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (mOperations.get(i).hasData()) {
8933aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    return true;
8943aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
8953aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
8963aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            return false;
8973aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
8983aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
8993aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void commit() {
9003aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = mRecent != null ? mRecent.size() : 0;
9013aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=0; i<N; i++) {
9023aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mRecent.get(i).commit();
9033aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
9043aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            mRecent = null;
9053aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
9063aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
9073aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void undo() {
9083aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=mOperations.size()-1; i>=0; i--) {
9093aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mOperations.get(i).undo();
9103aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
9113aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
9123aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
9133aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void redo() {
9143aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            final int N = mOperations.size();
9153aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=0; i<N; i++) {
9163aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                mOperations.get(i).redo();
9173aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
9183aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
9193aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn
9203aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        void destroy() {
9213aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            for (int i=mOperations.size()-1; i>=0; i--) {
9223aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                UndoOwner owner = mOperations.get(i).mOwner;
9233aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                owner.mOpCount--;
9243aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                if (owner.mOpCount <= 0) {
9253aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    if (owner.mOpCount < 0) {
9263aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                        throw new IllegalStateException("Underflow of op count on owner " + owner
9273aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                                + " in op " + mOperations.get(i));
9283aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    }
9293aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                    mManager.removeOwner(owner);
9303aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn                }
9313aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn            }
9323aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn        }
9333aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn    }
9343aa49b6fece334ace7525d42c1f6d0b7cdc1fbfbDianne Hackborn}
935