Re: Best way to hook into the run loop?
Re: Best way to hook into the run loop?
- Subject: Re: Best way to hook into the run loop?
- From: Graham Cox <email@hidden>
- Date: Sun, 6 Dec 2009 23:36:56 +1100
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@hidden)
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:
This email sent to email@hidden