39 var _CPUndoGroupingPool = [],
40 _CPUndoGroupingPoolCapacity = 5;
43 @implementation _CPUndoGrouping :
CPObject 50 + (void)_poolUndoGrouping:(_CPUndoGrouping)anUndoGrouping
52 if (!anUndoGrouping || _CPUndoGroupingPool.length >= _CPUndoGroupingPoolCapacity)
55 _CPUndoGroupingPool.push(anUndoGrouping);
58 + (id)undoGroupingWithParent:(_CPUndoGrouping)anUndoGrouping
60 if (_CPUndoGroupingPool.length)
62 var grouping = _CPUndoGroupingPool.pop();
64 grouping._parent = anUndoGrouping;
66 if (grouping._invocations.length)
67 grouping._invocations = [];
72 return [[
self alloc] initWithParent:anUndoGrouping];
75 - (id)initWithParent:(_CPUndoGrouping)anUndoGrouping
81 _parent = anUndoGrouping;
89 - (_CPUndoGrouping)parent
96 _invocations.push(anInvocation);
99 - (void)addInvocationsFromArray:(CPArray)invocations
101 [_invocations addObjectsFromArray:invocations];
104 - (BOOL)removeInvocationsWithTarget:(
id)aTarget
106 var index = _invocations.length;
109 if ([_invocations[index] target] == aTarget)
110 _invocations.splice(index, 1);
113 - (CPArray)invocations
120 var index = _invocations.length;
123 [_invocations[index] invoke];
126 - (void)setActionName:(
CPString)aName
138 var _CPUndoGroupingParentKey =
@"_CPUndoGroupingParentKey",
139 _CPUndoGroupingInvocationsKey =
@"_CPUndoGroupingInvocationsKey",
140 _CPUndoGroupingActionNameKey =
@"_CPUndoGroupingActionNameKey";
142 @implementation _CPUndoGrouping (CPCoder)
144 - (id)initWithCoder:(
CPCoder)aCoder
150 _parent = [aCoder decodeObjectForKey:_CPUndoGroupingParentKey];
151 _invocations = [aCoder decodeObjectForKey:_CPUndoGroupingInvocationsKey];
152 _actionName = [aCoder decodeObjectForKey:_CPUndoGroupingActionNameKey];
158 - (void)encodeWithCoder:(
CPCoder)aCoder
160 [aCoder encodeObject:_parent forKey:_CPUndoGroupingParentKey];
161 [aCoder encodeObject:_invocations forKey:_CPUndoGroupingInvocationsKey];
162 [aCoder encodeObject:_actionName forKey:_CPUndoGroupingActionNameKey];
193 id _undoManagerProxy;
195 CPArray _runLoopModes;
196 BOOL _registeredWithRunLoop;
218 _undoManagerProxy = [_CPUndoManagerProxy alloc];
219 _undoManagerProxy._undoManager =
self;
227 if (!_currentGrouping)
229 if ([
self groupsByEvent])
230 [
self _beginUndoGroupingForEvent];
234 [_currentGrouping addInvocation:anInvocation];
237 [_redoStack removeAllObjects];
248 - (void)registerUndoWithTarget:(
id)aTarget selector:(
SEL)aSelector object:(
id)anObject
251 if (_disableCount > 0)
258 [invocation setTarget:aTarget];
259 [invocation setSelector:aSelector];
260 [invocation setArgument:anObject atIndex:2];
262 [
self _addUndoInvocation:invocation];
269 - (id)prepareWithInvocationTarget:(
id)aTarget
271 _preparedTarget = aTarget;
273 return _undoManagerProxy;
280 - (CPMethodSignature)_methodSignatureOfPreparedTargetForSelector:(
SEL)aSelector
282 if ([_preparedTarget respondsToSelector:aSelector])
293 - (void)_forwardInvocationToPreparedTarget:(
CPInvocation)anInvocation
296 if (_disableCount > 0)
304 [anInvocation
setTarget:_preparedTarget];
306 [
self _addUndoInvocation:anInvocation];
308 _preparedTarget = nil;
321 return [_redoStack count] > 0;
329 if (_undoStack.length > 0)
332 return [[_currentGrouping invocations] count] > 0;
341 if ([
self groupingLevel] === 1)
350 - (void)undoNestedGroup
352 if ([_undoStack count] <= 0)
357 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification
360 [defaultCenter postNotificationName:CPUndoManagerWillUndoChangeNotification
363 var undoGrouping = _undoStack.pop(),
364 actionName = [undoGrouping actionName];
368 [
self _beginUndoGrouping];
369 [undoGrouping invoke];
372 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
376 [[_redoStack lastObject] setActionName:actionName];
378 [defaultCenter postNotificationName:CPUndoManagerDidUndoChangeNotification
388 if ([_redoStack count] <= 0)
398 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification
401 [defaultCenter postNotificationName:CPUndoManagerWillRedoChangeNotification
404 var oldUndoGrouping = _currentGrouping,
405 undoGrouping = _redoStack.pop(),
406 actionName = [undoGrouping actionName];
408 _currentGrouping = nil;
411 [
self _beginUndoGrouping];
412 [undoGrouping invoke];
415 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
417 _currentGrouping = oldUndoGrouping;
420 [[_undoStack lastObject] setActionName:actionName];
421 [defaultCenter postNotificationName:CPUndoManagerDidRedoChangeNotification object:self];
428 - (void)beginUndoGrouping
433 if (!_currentGrouping && [
self groupsByEvent])
434 [
self _beginUndoGroupingForEvent];
440 [
self _beginUndoGrouping];
444 - (void)_beginUndoGroupingForEvent
446 [
self _beginUndoGrouping];
447 [
self _registerWithRunLoop];
451 - (void)_beginUndoGrouping
453 _currentGrouping = [_CPUndoGrouping undoGroupingWithParent:_currentGrouping];
460 - (void)endUndoGrouping
462 if (!_currentGrouping)
463 [
CPException raise:CPInternalInconsistencyException
reason:"endUndoGrouping. No undo group is currently open."];
467 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification
470 var parent = [_currentGrouping parent];
472 if (!parent && [_currentGrouping invocations].length > 0)
475 postNotificationName:CPUndoManagerWillCloseUndoGroupNotification
482 stack.push(_currentGrouping);
484 if (_levelsOfUndo > 0 && stack.length > _levelsOfUndo)
488 postNotificationName:CPUndoManagerDidCloseUndoGroupNotification
495 [parent addInvocationsFromArray:[_currentGrouping invocations]];
497 [_CPUndoGrouping _poolUndoGrouping:_currentGrouping];
500 _currentGrouping = parent;
509 - (void)enableUndoRegistration
511 if (_disableCount <= 0)
513 reason:"enableUndoRegistration. There are no disable messages in effect right now."];
521 - (BOOL)groupsByEvent
523 return _groupsByEvent;
530 - (void)setGroupsByEvent:(BOOL)aFlag
534 if (_groupsByEvent === aFlag)
537 _groupsByEvent = aFlag;
539 if (![
self groupsByEvent])
540 [
self _unregisterWithRunLoop];
546 - (unsigned)groupingLevel
548 var grouping = _currentGrouping,
549 level = _currentGrouping ? 1 : 0;
551 while (grouping = [grouping parent])
561 - (void)disableUndoRegistration
569 - (BOOL)isUndoRegistrationEnabled
571 return _disableCount == 0;
595 - (void)removeAllActions
598 while (_currentGrouping)
602 [
self _unregisterWithRunLoop];
614 - (void)removeAllActionsWithTarget:(
id)aTarget
616 [_currentGrouping removeInvocationsWithTarget:aTarget];
618 var index = _redoStack.length;
622 var grouping = _redoStack[index];
624 [grouping removeInvocationsWithTarget:aTarget];
626 if (![grouping invocations].length)
627 _redoStack.splice(index, 1);
630 index = _undoStack.length;
634 var grouping = _undoStack[index];
636 [grouping removeInvocationsWithTarget:aTarget];
638 if (![grouping invocations].length)
639 _undoStack.splice(index, 1);
651 if (anActionName !== nil && _currentGrouping)
652 [_currentGrouping setActionName:anActionName];
666 return [[_redoStack lastObject] actionName];
687 if (anActionName || anActionName === 0)
691 return @"Redo " + anActionName;
707 return [[_undoStack lastObject] actionName];
728 if (anActionName || anActionName === 0)
732 return @"Undo " + anActionName;
742 - (CPArray)runLoopModes
744 return _runLoopModes;
755 - (void)setRunLoopModes:(CPArray)modes
757 _runLoopModes = [modes copy];
759 if (_registeredWithRunLoop)
761 [
self _unregisterWithRunLoop];
762 [
self _registerWithRunLoop];
766 - (void)_runLoopEndUndoGrouping
768 [
self endUndoGrouping];
769 _registeredWithRunLoop = NO;
773 - (void)_registerWithRunLoop
775 if (_registeredWithRunLoop)
778 _registeredWithRunLoop = YES;
783 order:CPUndoCloseGroupingRunLoopOrdering
784 modes:_runLoopModes];
788 - (void)_unregisterWithRunLoop
790 if (!_registeredWithRunLoop)
793 _registeredWithRunLoop = NO;
800 - (void)observeChangesForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject
802 [anObject addObserver:self
804 options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew
808 - (void)stopObservingChangesForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject
810 [anObject removeObserver:self forKeyPath:aKeyPath];
814 ofObject:(
id)anObject
819 var before = [aChange
valueForKey:CPKeyValueChangeOldKey],
820 after = [aChange
valueForKey:CPKeyValueChangeNewKey];
821 if (before === after || (before !== nil && before.isa && (after === nil || after.isa) && [before
isEqual:after]))
849 _redoStack = [aCoder decodeObjectForKey:CPUndoManagerRedoStackKey];
850 _undoStack = [aCoder decodeObjectForKey:CPUndoManagerUndoStackKey];
852 _levelsOfUndo = [aCoder decodeObjectForKey:CPUndoManagerLevelsOfUndoKey];
854 _currentGrouping = [aCoder decodeObjectForKey:CPUndoManagerCurrentGroupingKey];
858 [
self setRunLoopModes:[aCoder decodeObjectForKey:CPUndoManagerRunLoopModesKey]];
859 [
self setGroupsByEvent:[aCoder decodeBoolForKey:CPUndoManagerGroupsByEventKey]];
867 [aCoder encodeObject:_redoStack forKey:CPUndoManagerRedoStackKey];
868 [aCoder encodeObject:_undoStack forKey:CPUndoManagerUndoStackKey];
870 [aCoder encodeInt:_levelsOfUndo forKey:CPUndoManagerLevelsOfUndoKey];
873 [aCoder encodeObject:_currentGrouping forKey:CPUndoManagerCurrentGroupingKey];
875 [aCoder encodeObject:_runLoopModes forKey:CPUndoManagerRunLoopModesKey];
876 [aCoder encodeBool:_groupsByEvent forKey:CPUndoManagerGroupsByEventKey];
881 @implementation _CPUndoManagerProxy :
CPProxy 886 - (CPMethodSignature)methodSignatureForSelector:(
SEL)aSelector
888 return [_undoManager _methodSignatureOfPreparedTargetForSelector:aSelector];
893 [_undoManager _forwardInvocationToPreparedTarget:anInvocation];
Used to implement exception handling (creating & raising).
CPUndoManagerWillRedoChangeNotification
CPString redoActionName()
id invocationWithMethodSignature:(CPMethodSignature aMethodSignature)
var CPUndoManagerUndoStackKey
var CPUndoManagerGroupsByEventKey
The main run loop for the application.
var CPUndoManagerRunLoopModesKey
void performSelector:target:argument:order:modes:(SEL aSelector, [target] id aTarget, [argument] id anArgument, [order] int anOrder, [modes] CPArray modes)
CPDictionary inverseChangeDictionary()
CPUndoManagerDidCloseUndoGroupNotification
void postNotificationName:object:(CPString aNotificationName, [object] id anObject)
void raise:reason:(CPString aName, [reason] CPString aReason)
void applyChange:toKeyPath:(CPDictionary aChange, [toKeyPath] CPString aKeyPath)
CPNotificationCenter defaultCenter()
A mutable key-value pair collection.
CPUndoManagerWillCloseUndoGroupNotification
CPRunLoop currentRunLoop()
void setRunLoopModes:(CPArray modes)
An object representation of a message.
void cancelPerformSelector:target:argument:(SEL aSelector, [target] id aTarget, [argument] id anArgument)
void setTarget:(id aTarget)
var CPUndoManagerLevelsOfUndoKey
An immutable string (collection of characters).
var CPUndoManagerRedoStackKey
CPString undoActionName()
CPUndoManagerDidOpenUndoGroupNotification
CPUndoManagerWillUndoChangeNotification
void setGroupsByEvent:(BOOL aFlag)
var CPUndoManagerActionNameKey
CPUndoManagerCheckpointNotification
A general mechanism for user action "undo".
Defines methods for use when archiving & restoring (enc/decoding).
id valueForKey:(CPString aKey)
CPUndoCloseGroupingRunLoopOrdering
CPString redoMenuTitleForUndoActionName:(CPString anActionName)
Sends messages (CPNotification) between objects.
id prepareWithInvocationTarget:(id aTarget)
CPUndoManagerDidRedoChangeNotification
var CPUndoManagerCurrentGroupingKey
CPUndoManagerDidUndoChangeNotification
CPString undoMenuTitleForUndoActionName:(CPString anActionName)