On 06/12/2009, at 10:57 PM, Mike Abdullah wrote:

> But why are you opening a group without registering an undo action? Why not 
> just wait until the first action actually needs to be registered?

I tried it but it doesn't work. I forget the details now as this was my first 
approach and that was a long time ago, but basically the undo manager didn't 
tolerate having groups deferred until a task was actually received. It would 
crash, but without the code, I couldn't say why.

My second approach was to wait until a mouse drag was received, then IF the 
mouse drag handler knew it was going to cause an undoable data model change, it 
would call its delegate to open a group. That worked but was very complicated - 
not every mouse drag necessarily led to an undoable change, so a lot of special 
cases were needed. It was truly ugly. (Bear in mind these mouse drag handlers 
are all individual tools in a drawing app, but some tools cause undoable 
changes, and others don't, e.g. zoom tool).

My third approach was to count the tasks received after opening a group and 
then if that value was 0 at the end of the drag, invoke -undo to remove the 
empty group. That works but I ran into a further issue that if an exception is 
thrown during the drag then cleaning up the open group could not be done (the 
group level stayed stuck at 1 and the group stayed open, leading to the undo 
manager ceasing to work). Without the code, I couldn't say why.

My fourth approach is to write my own bloody undo manager and have done with 
it. Result so far - bliss.

It's really nice and simple if you can just open a group on mouse down, and 
close it again on mouse up. If this didn't result in a bogus Undo task, it 
would work great, as my home grown implementation shows - there's no need to 
worry whether you need the group or not, because if it ends up empty it's never 
added to the Undo stack and is discarded. I believe NSUndoManager should do 
that also.


> I'm not even sure it is a bug, since the undo manager is designed to work in 
> terms of groups, not individual actions.

Internally it works in groups, but a group needs to have at least one action 
otherwise it does nothing. However, an empty group still appears as an undoable 
task in the menu, which is useless. It just causes the user to think that your 
app is broken. What purpose does an empty group serve? I suppose some apps 
might not be submitting any actual actions to the undo manager, but just 
relying on undo notifications to perform undo by some other means internally, 
which might explain why this behaviour persists - but if that is the case the 
docs are silent on that point (and a better design would be to allow that 
behaviour to be disabled).

> So again, why not just wait till the end of the drag and record a single 
> action?

Because an individual property has no way to know whether it's the last in a 
series invoked by a drag. How do you detect 'last'? You can't, because it might 
not have happened yet. If I have a basic property of an object in my data 
model, it can submit its old value to the undo manager - very simple. But it 
doesn't know who called it or whether or not it will be called again. However 
the Undo Manager does know something about a sequence of identical properties 
submitted to it because it is aware of the event cycle and the open group that 
it's adding actions to. It can accept the first (representing the initial or 
original state) and drop the remainder within that event cycle or group. 
Detecting the first is easy (and for undo, is the one that counts), detecting 
last is impossible.

It might be possible to capture the state at the start of a drag but bear in 
mind that a drag could change any number of properties of any number of 
objects. In order to capture the state in advance it would have to know what 
the drag was actually going to change. The object that handles the drag has no 
such knowledge - the data model has that information. The mouse drag is 
something that happens at the controller level (the events having been passed 
to it from a view) but the actual changes to the data model occur at the model 
level, and it is the model that knows what properties to change and thus 
submits their old values to the undo manager. In hindsight it might have been 
better to have the controller observe the changes via KVO and handle the 
coalescing and undo submission there, since it can discriminate between the 
first and last in a sequence of mouse events, but that would be a huge rewrite 
of my app at this stage. Besides, I've always thought of Undo as a model-level 
feature - it's the changes to the data model that you're actually undoing, so 
its unclear if moving the responsibility for Undo into the controller is even 
theoretically the right thing to do.

--Graham

_______________________________________________

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