Confused by grouping with NSUndoManager

  • I am having trouble getting NSUndoManager to behave the way I want it
    to.  I want to group some actions together so that when the user
    selects Undo, all those actions are undone at the same time.
    However, it isn't as easy as just wrapping the set of actions in
    calls to beginUndoGrouping and endUndoGrouping, since the set of
    actions is not fixed.  What I want to do is analogous to how most
    text editors work.  (My app has nothing to do with editing text.
    This is just a convenient analogy to use.)

    Undo works on word boundaries in most text editors.  For example, if
    I type "foo bar" then Undo, the word "bar" will be removed.  Undoing
    again will remove the entire word "foo."  However, editors can also
    undo partial words.  If I type "foo ba" then Undo, the partial word
    "ba" will be removed.  My thinking was that the code for that would
    work something like this:

    User types "f" - beginUndoGrouping is called to start a new group.
    prepareWithInvocationTarget is called with a selector to remove the
    letter
    User types "o" - prepareWithInvocationTarget is called with a
    selector to remove the letter
    User types "o" - prepareWithInvocationTarget is called with a
    selector to remove the letter
    User types a space - prepareWithInvocationTarget is called with a
    selector to remove the letter.  This is a word boundary, so
    endUndoGrouping is called to group the entire word together.

    User types "b" - there is no open group, so beginUndoGrouping is
    called to start a new group.  prepareWithInvocationTarget is called
    with a selector to remove the letter
    User types "a" - prepareWithInvocationTarget is called with the
    selector to remove a letter
    User selects Undo - here is where the trouble starts.

    Apparently because the group wasn't closed, I am getting this error:

    NSUndoManager 0x3ef3b0 is in invalid state, undo was called with too
    many nested undo groups

    The documentation (which I just updated to be sure) has this
    description for the NSUndoManager  undo method, "Closes the top-level
    undo group if necessary and invokes undoNestedGroup."  The Discussion
    section for the method says "This method also invokes endUndoGrouping
    if the nesting level is 1."

    The setGroupsByEvent: method says this, "The default is YES. If you
    turn automatic grouping off, you must close groups explicitly before
    invoking either undo or undoNestedGroup."  I am not changing the
    default setting and I have verified that it is set to YES.

    The documentation seems to be saying that what I did in the pseudo-
    code above should work.  I can open a group, do some things, then the
    undo method should close the group for me.  This does not appear to
    be what is happening.

    I have tried several ways to work around this.  I registered for
    NSUndoManagerWillUndoChangeNotification, hoping to close the group
    just before the undo method was called.  Unfortunately, the exception
    happens before the notification is posted and I still get the error.

    I derived my own class from NSUndoManager and overrode the undo and
    undoNestedGroup methods so that they closed any open groups by
    calling endUndoGrouping.  This got rid of the error I was getting,
    but I ran into another problem.  My document no longer accurately
    kept track of its dirty state.  Even after undoing all the actions
    that were done, the document still said it needed to be saved.

    If the NSUndoManager subclass is the right way to go, then I will try
    to fix the dirty flag problem.  However, it doesn't seem like I
    should have to do this, since the documentation explicitly says that
    it is already doing it.

    Either I am misunderstanding the documentation or missing something
    else.

    Any ideas?

    Thanks
previous month september 2007 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Go to today