On Feb 26, 2008, at 16:06, Brian Williams wrote:

OK Quincey I'll bite, how did you do it?
I am interested in how to more efficiently integrate the undo menu stuff into my CD project.


Well, you asked for it ...

Basically I co-opted KVC's keyPath mechanism.

The easiest case is setting a single property (for example, a text field bound to a NSObjectController or File's Owner with keyPath "firstName", though it works for in-place edits of table view columns bound to a NSArrayController too). In IB, I just write the keyPath in the binding as "ui.firstName" instead.

To detect this, I override [NSObject setValue:forKeyPath:]. (Regrettably, I was forced to subclass NSObject instead of adding a category, since all that KVC/KVO stuff is already added via categories.) The override decodes the keyPath component by component, stripping out any "ui" component, and resolves the target component by component too, until it can eventually call the standard [NSObject setValue:forKey:]. After seeing a "ui" component, it also starts building an "undo string" with an @-sign, the target class name and the rest of the components ("@Employee.firstName" for example). When the real setValue is called, that undo string is also shipped off to an undo controller.

For standard controls and views, there can only be one of these undo strings per run-loop-bracketed undo group. But you might also way to use the same mechanism from action methods, and they can be coded to change multiple "ui.something" properties. (To-many property array deletions can be multiple changes too, but I'll get to that a bit later.) So the undo controller combines any strings it receives with the string it already has. For example, if it gets "@Employee.firstName" more than once, it changes its string to "@Employee.firstName.+s" to mark the plural. Or if it gets "@Employee.firstName" and "@Employee.lastName", it changes its string to "@Employee.+s" to mark multiple attributes.

The undo controller registers to receive NSUndoManager's NSUndoManagerWillCloseUndoGroupNotification. When it receives that notification for the top level group, it uses a .strings table to "localize" its weird undo string into things like "Employee First Name" (from "@Employee.firstName") or into "Employee First Names" (from "@Employee.firstName.+s") or into "Employee Changes" (from "@Employee.+s"), etc, and sends it to the NSUndoManager. Then it clears its own string for the next run loop iteration.

For insertions and deletions in a to-many (array) property (typically but not necessarily arriving via a NSArrayController), I had to override [NSObject mutableArrayValueForKeyPath:keyPath]. The override resolves the keyPath like the setValue:forKeyPath override, but of course it can't use setValue:forKey. Instead, it creates and returns a NSArray subclass that holds and monitors the standard proxy object that mutableArrayValueForKeyPath:keyPath normally returns. When it sees an insertion, it sends a string like "@[EMAIL PROTECTED]" to the undo controller. Deletions produce strings like "@[EMAIL PROTECTED]". These string are subject combining like I already described, eventually leading to localized strings like "Delete Employee" (from "@[EMAIL PROTECTED]") or "Delete Employees" (from "@[EMAIL PROTECTED]").

To set this up in IB, the model keyPath of the NSArrayController's binding to File's Owner needs to have a "ui" in it at a suitable place.

The final little piece is to add a -(id) ui {return self;} property to the NSObject subclass, so that "ui" components in key paths are ignored in all the other keyPath methods that are not overridden.

All of this is just to get the undo menu string. The actual undo data itself is handled entirely separately.

That's about it. It isn't trivial, although it's not a lot of code.

I have a theory that this methodology can be adapted to core data objects, too, implemented as a category on NSManagedObject plus an override of mutableSetValueForKeyPath:keyPath. But I'm not sure yet if this is feasible.
_______________________________________________

Cocoa-dev mailing list ([email protected])

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