This is quite straightforward - it's covered by Aaron Hillegasse in his excellent book. I use this technique and it works beautifully. I've also wrapped up the common functionality into a class that "undoable observables" can derive from, and once I had that, each class that wants undo just needs to do 2 things:

1. publish a list of those properties that it wants to be undoable (this is just a list of strings). 2. Return a user-readable string for the undo action name for each undoable property. The undo manager sets this as the undo action name - this is not required to make this actually work but you probably don't want the Undo menu just to say "Undo", but instead tell you what it will undo.

So, when an object wants to start having its properties be undoable, the controller uses the list of properties it publishes and starts observing each one. Every change to any property ends up calling - observeValueForKeyPath:ofObject:change:context: which converts the change to an undo task using the undo manager's usual - prepareWithInvocationTarget: method. The cunning thing is that the target is the controller, and the method invoked is some private method you declare. On invoking Undo, this private method then reroutes the undo change back to the original object and property- setting method. It completely unifies undo for all KVO-compliant properties for any number of subsidiary objects - in my case I have many different ones, all with many different properties. Undo "just works" for all of them, once I provide the list of properties to the controller.

Here's part of the code in the controller. The "cunning private method" is ultra-simple:

- (void) changeKeyPath:(NSString*) keypath ofObject:(id) object toValue:(id) value
{
        if([value isEqual:[NSNull null]])
                value = nil;
        
        [object setValue:value forKeyPath:keypath];
}


- (void) observeValueForKeyPath:(NSString*) keypath ofObject:(id) object change:(NSDictionary*) change context:(void*) context
{
        #pragma unused(context)
        
NSKeyValueChange ch = [[change objectForKey:NSKeyValueChangeKindKey] intValue];
        BOOL             wasChanged = NO;
        
        if ( ch == NSKeyValueChangeSetting )
        {
if(![[change objectForKey:NSKeyValueChangeOldKey] isEqual:[change objectForKey:NSKeyValueChangeNewKey]])
                {
[[[self undoManager] prepareWithInvocationTarget:self] changeKeyPath:keypath
                                                                                
ofObject:object
                                                                                
toValue:[change objectForKey:NSKeyValueChangeOldKey]];
                        wasChanged = YES;
                }
        }
else if ( ch == NSKeyValueChangeInsertion || ch == NSKeyValueChangeRemoval )
        {
// Cocoa has a bug (at least on 10.4) where array insertion/deletion changes don't properly record the old array.
                // GCObserveableObject gives us a workaround
                                
                NSArray* old = [object oldArrayValueForKeyPath:keypath];
                
[[[self undoManager] prepareWithInvocationTarget:self] changeKeyPath:keypath
                                                                        
ofObject:object
                                                                        
toValue:old];   
                                                                                
                                                
                wasChanged = YES;
        }
        
if ( wasChanged && !([[self undoManager] isUndoing] || [[self undoManager] isRedoing]))
        {
if([object respondsToSelector:@selector(actionNameForKeyPath:changeKind:)]) [[self undoManager] setActionName:[object actionNameForKeyPath:keypath changeKind:ch]];
                else
[[self undoManager] setActionName:[GCObservableObject actionNameForKeyPath:keypath objClass:[object class]]];
        }
}




On 14 May 2008, at 1:10 am, Mike Abdullah wrote:

In which case you need to set up KVO of the properties in a controller object of some kind and use that to register the undos. Of course, Core Data does this all automatically :)

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]

Reply via email to