Skip to content

Instantly share code, notes, and snippets.

@nevyn
Created January 18, 2012 13:09
Show Gist options
  • Save nevyn/1632940 to your computer and use it in GitHub Desktop.
Save nevyn/1632940 to your computer and use it in GitHub Desktop.
half-way generalized KVO undoer
#import <Cocoa/Cocoa.h>
#import "SPLangNode.h"
@interface SPLangUndoer : NSObject {
NSUndoManager *mgr;
SPLangNode *root;
}
-(id)initWatchingTree:(SPLangNode*)tree withUndo:(NSUndoManager*)undoManager;
@end
#import "SPLangUndoer.h"
@interface SPLangUndoer ()
@property (retain) NSUndoManager *mgr;
@property (retain) SPLangNode *root;
-(void)watchNodeWithSubtree:(SPLangNode*)node;
-(void)unwatchNodeWithSubtree:(SPLangNode*)node;
-(void)watchTranslation:(SPTranslation*)translation;
-(void)unwatchTranslation:(SPTranslation*)translation;
@end
static NSString *UndoKVOContext = @"UndoKVOContext";
@implementation SPLangUndoer
@synthesize mgr, root;
-(id)initWatchingTree:(SPLangNode*)tree withUndo:(NSUndoManager*)undoManager;
{
self.mgr = undoManager;
self.root = tree;
[self watchNodeWithSubtree:tree];
return self;
}
-(void)dealloc;
{
[self unwatchNodeWithSubtree:self.root];
self.mgr = nil;
self.root = nil;
[super dealloc];
}
-(void)watchNodeWithSubtree:(SPLangNode*)node;
{
for (NSString *prop in [[node class] undoableKeyPaths])
[node addObserver:self forKeyPath:prop options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:UndoKVOContext];
for (SPTranslation *translation in [node valueForKey:@"translations"])
[self watchTranslation:translation];
for (SPLangNode *child in [node valueForKey:@"children"])
[self watchNodeWithSubtree:child];
}
-(void)unwatchNodeWithSubtree:(SPLangNode*)node;
{
for (SPLangNode *child in [node valueForKey:@"children"])
[self unwatchNodeWithSubtree:child];
for (SPTranslation *translation in [node valueForKey:@"translations"])
[self unwatchTranslation:translation];
for (NSString *prop in [[node class] undoableKeyPaths])
[node removeObserver:self forKeyPath:prop];
}
-(void)watchTranslation:(SPTranslation*)translation;
{
for (NSString *prop in [[translation class] undoableKeyPaths])
[translation addObserver:self forKeyPath:prop options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:UndoKVOContext];
}
-(void)unwatchTranslation:(SPTranslation*)translation;
{
for (NSString *prop in [[translation class] undoableKeyPaths])
[translation removeObserver:self forKeyPath:prop];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSKeyValueChange kind = [[change objectForKey:NSKeyValueChangeKindKey] intValue];
NSIndexSet *changedIndices = [change objectForKey:NSKeyValueChangeIndexesKey];
NSUInteger anIndex = [changedIndices firstIndex]; // wrong but good enough?
id old = [change objectForKey:NSKeyValueChangeOldKey];
id new = [change objectForKey:NSKeyValueChangeNewKey];
if([keyPath isEqual:@"children"]) {
if(kind == NSKeyValueChangeRemoval || kind == NSKeyValueChangeReplacement || kind == NSKeyValueChangeSetting)
for (SPLangNode *node in old) {
[self unwatchNodeWithSubtree:node];
[[mgr prepareWithInvocationTarget:object] insertObject:node inChildrenAtIndex:anIndex];
}
if(kind == NSKeyValueChangeInsertion || kind == NSKeyValueChangeReplacement || kind == NSKeyValueChangeSetting)
for (SPLangNode *node in new) {
[self watchNodeWithSubtree:node];
[[mgr prepareWithInvocationTarget:object] removeObjectFromChildrenAtIndex:[[object valueForKey:@"children"] indexOfObject:node]];
}
} else if([keyPath isEqual:@"translations"]) {
if(kind == NSKeyValueChangeRemoval || kind == NSKeyValueChangeReplacement || kind == NSKeyValueChangeSetting)
for (SPTranslation *trans in old) {
[self unwatchTranslation:trans];
[[mgr prepareWithInvocationTarget:object] insertObject:trans inTranslationsAtIndex:anIndex];
}
if(kind == NSKeyValueChangeInsertion || kind == NSKeyValueChangeReplacement || kind == NSKeyValueChangeSetting)
for (SPTranslation *trans in new) {
[self watchTranslation:trans];
[[mgr prepareWithInvocationTarget:[object mutableArrayValueForKey:@"translations"]] removeObject:trans];
}
} else {
[[mgr prepareWithInvocationTarget:object] setValue:old forKeyPath:keyPath];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment