00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPObject.j"
00024 @import "CPInvocation.j"
00025 @import "CPProxy.j"
00026
00027
00028 var CPUndoManagerNormal = 0,
00029 CPUndoManagerUndoing = 1,
00030 CPUndoManagerRedoing = 2;
00031
00032 CPUndoManagerCheckpointNotification = @"CPUndoManagerCheckpointNotification";
00033 CPUndoManagerDidOpenUndoGroupNotification = @"CPUndoManagerDidOpenUndoGroupNotification";
00034 CPUndoManagerDidRedoChangeNotification = @"CPUndoManagerDidRedoChangeNotification";
00035 CPUndoManagerDidUndoChangeNotification = @"CPUndoManagerDidUndoChangeNotification";
00036 CPUndoManagerWillCloseUndoGroupNotification = @"CPUndoManagerWillCloseUndoGroupNotification";
00037 CPUndoManagerWillRedoChangeNotification = @"CPUndoManagerWillRedoChangeNotification";
00038 CPUndoManagerWillUndoChangeNotification = @"CPUndoManagerWillUndoChangeNotification";
00039
00040 CPUndoCloseGroupingRunLoopOrdering = 350000;
00041
00042 var _CPUndoGroupingPool = [],
00043 _CPUndoGroupingPoolCapacity = 5;
00044
00045
00046 @implementation _CPUndoGrouping : CPObject
00047 {
00048 _CPUndoGrouping _parent;
00049 CPMutableArray _invocations;
00050 }
00051
00052 + (void)_poolUndoGrouping:(_CPUndoGrouping)anUndoGrouping
00053 {
00054 if (!anUndoGrouping || _CPUndoGroupingPool.length >= _CPUndoGroupingPoolCapacity)
00055 return;
00056
00057 _CPUndoGroupingPool.push(anUndoGrouping);
00058 }
00059
00060 + (id)undoGroupingWithParent:(_CPUndoGrouping)anUndoGrouping
00061 {
00062 if (_CPUndoGroupingPool.length)
00063 {
00064 var grouping = _CPUndoGroupingPool.pop();
00065
00066 grouping._parent = anUndoGrouping;
00067
00068 if (grouping._invocations.length)
00069 grouping._invocations = [];
00070
00071 return grouping;
00072 }
00073
00074 return [[self alloc] initWithParent:anUndoGrouping];
00075 }
00076
00077 - (id)initWithParent:(_CPUndoGrouping)anUndoGrouping
00078 {
00079 self = [super init];
00080
00081 if (self)
00082 {
00083 _parent = anUndoGrouping;
00084 _invocations = [];
00085 }
00086
00087 return self;
00088 }
00089
00090 - (_CPUndoGrouping)parent
00091 {
00092 return _parent;
00093 }
00094
00095 - (void)addInvocation:(CPInvocation)anInvocation
00096 {
00097 _invocations.push(anInvocation);
00098 }
00099
00100 - (void)addInvocationsFromArray:(CPArray)invocations
00101 {
00102 [_invocations addObjectsFromArray:invocations];
00103 }
00104
00105 - (BOOL)removeInvocationsWithTarget:(id)aTarget
00106 {
00107 var index = _invocations.length;
00108
00109 while (index--)
00110 if ([_invocations[index] target] == aTarget)
00111 _invocations.splice(index, 1);
00112 }
00113
00114 - (CPArray)invocations
00115 {
00116 return _invocations;
00117 }
00118
00119 - (void)invoke
00120 {
00121 var index = _invocations.length;
00122
00123 while (index--)
00124 [_invocations[index] invoke];
00125 }
00126
00127 @end
00128
00129 var _CPUndoGroupingParentKey = @"_CPUndoGroupingParentKey",
00130 _CPUndoGroupingInvocationsKey = @"_CPUndoGroupingInvocationsKey";
00131
00132 @implementation _CPUndoGrouping (CPCoder)
00133
00134 - (id)initWithCoder:(CPCoder)aCoder
00135 {
00136 self = [super init];
00137
00138 if (self)
00139 {
00140 _parent = [aCoder decodeObjectForKey:_CPUndoGroupingParentKey];
00141 _invocations = [aCoder decodeObjectForKey:_CPUndoGroupingInvocationsKey];
00142 }
00143
00144 return self;
00145 }
00146
00147 - (void)encodeWithCoder:(CPCoder)aCoder
00148 {
00149 [aCoder encodeObject:_parent forKey:_CPUndoGroupingParentKey];
00150 [aCoder encodeObject:_invocations forKey:_CPUndoGroupingInvocationsKey];
00151 }
00152
00153 @end
00154
00170 @implementation CPUndoManager : CPObject
00171 {
00172 CPMutableArray _redoStack;
00173 CPMutableArray _undoStack;
00174
00175 BOOL _groupsByEvent;
00176 int _disableCount;
00177 int _levelsOfUndo;
00178 id _currentGrouping;
00179 int _state;
00180 CPString _actionName;
00181
00182 id _preparedTarget;
00183 id _undoManagerProxy;
00184
00185 CPArray _runLoopModes;
00186 BOOL _registeredWithRunLoop;
00187 }
00188
00193 - (id)init
00194 {
00195 self = [super init];
00196
00197 if (self)
00198 {
00199 _redoStack = [];
00200 _undoStack = [];
00201
00202 _state = CPUndoManagerNormal;
00203
00204 [self setRunLoopModes:[CPDefaultRunLoopMode]];
00205 [self setGroupsByEvent:YES];
00206
00207 _undoManagerProxy = [_CPUndoManagerProxy alloc];
00208 _undoManagerProxy._undoManager = self;
00209 }
00210
00211 return self;
00212 }
00213
00214 - (void)_addUndoInvocation:(CPInvocation)anInvocation
00215 {
00216 if (!_currentGrouping)
00217
00218 if ([self groupsByEvent])
00219 [self _beginUndoGroupingForEvent];
00220 else
00221 [CPException raise:CPInternalInconsistencyException reason:"No undo group is currently open"];
00222
00223 [_currentGrouping addInvocation:anInvocation];
00224
00225 if (_state === CPUndoManagerNormal)
00226 [_redoStack removeAllObjects];
00227 }
00228
00229
00237 - (void)registerUndoWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anObject
00238 {
00239
00240 if (_disableCount > 0)
00241 return;
00242
00243
00244
00245 var invocation = [CPInvocation invocationWithMethodSignature:nil];
00246
00247 [invocation setTarget:aTarget];
00248 [invocation setSelector:aSelector];
00249 [invocation setArgument:anObject atIndex:2];
00250
00251 [self _addUndoInvocation:invocation];
00252 }
00258 - (id)prepareWithInvocationTarget:(id)aTarget
00259 {
00260 _preparedTarget = aTarget;
00261
00262 return _undoManagerProxy;
00263 }
00264
00265
00266
00267
00268
00269 - (CPMethodSignature)_methodSignatureOfPreparedTargetForSelector:(SEL)aSelector
00270 {
00271 if ([_preparedTarget respondsToSelector:aSelector])
00272 return 1;
00273
00274 return nil;
00275 }
00276
00282 - (void)_forwardInvocationToPreparedTarget:(CPInvocation)anInvocation
00283 {
00284
00285 if (_disableCount > 0)
00286 return;
00287
00288
00289
00290
00291
00292
00293 [anInvocation setTarget:_preparedTarget];
00294
00295 [self _addUndoInvocation:anInvocation];
00296
00297 _preparedTarget = nil;
00298 }
00299
00300
00304 - (BOOL)canRedo
00305 {
00306 [[CPNotificationCenter defaultCenter]
00307 postNotificationName:CPUndoManagerCheckpointNotification
00308 object:self];
00309
00310 return [_redoStack count] > 0;
00311 }
00312
00316 - (BOOL)canUndo
00317 {
00318 if (_undoStack.length > 0)
00319 return YES;
00320
00321 return [_currentGrouping actions].length > 0;
00322 }
00323
00324
00328 - (void)undo
00329 {
00330 if ([self groupingLevel] === 1)
00331 [self endUndoGrouping];
00332
00333 [self undoNestedGroup];
00334 }
00335
00339 - (void)undoNestedGroup
00340 {
00341 if ([_undoStack count] <= 0)
00342 return;
00343
00344 var defaultCenter = [CPNotificationCenter defaultCenter];
00345
00346 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification
00347 object:self];
00348
00349 [defaultCenter postNotificationName:CPUndoManagerWillUndoChangeNotification
00350 object:self];
00351
00352 var undoGrouping = _undoStack.pop();
00353
00354 _state = CPUndoManagerUndoing;
00355
00356 [self _beginUndoGrouping];
00357 [undoGrouping invoke];
00358 [self endUndoGrouping];
00359
00360 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
00361
00362 _state = CPUndoManagerNormal;
00363
00364 [defaultCenter postNotificationName:CPUndoManagerDidUndoChangeNotification
00365 object:self];
00366 }
00367
00371 - (void)redo
00372 {
00373
00374 if ([_redoStack count] <= 0)
00375 return;
00376
00377
00378
00379
00380
00381
00382 var defaultCenter = [CPNotificationCenter defaultCenter];
00383
00384 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification
00385 object:self];
00386
00387 [defaultCenter postNotificationName:CPUndoManagerWillRedoChangeNotification
00388 object:self];
00389
00390 var oldUndoGrouping = _currentGrouping,
00391 undoGrouping = _redoStack.pop();
00392
00393 _currentGrouping = nil;
00394 _state = CPUndoManagerRedoing;
00395
00396 [self _beginUndoGrouping];
00397 [undoGrouping invoke];
00398 [self endUndoGrouping];
00399
00400 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
00401
00402 _currentGrouping = oldUndoGrouping;
00403 _state = CPUndoManagerNormal;
00404
00405 [defaultCenter postNotificationName:CPUndoManagerDidRedoChangeNotification object:self];
00406 }
00407
00408
00412 - (void)beginUndoGrouping
00413 {
00414
00415
00416
00417 if (!_currentGrouping && [self groupsByEvent])
00418 [self _beginUndoGroupingForEvent];
00419
00420 [[CPNotificationCenter defaultCenter]
00421 postNotificationName:CPUndoManagerCheckpointNotification
00422 object:self];
00423
00424 [self _beginUndoGrouping];
00425 }
00426
00427
00428 - (void)_beginUndoGroupingForEvent
00429 {
00430 [self _beginUndoGrouping];
00431 [self _registerWithRunLoop];
00432 }
00433
00434
00435 - (void)_beginUndoGrouping
00436 {
00437 _currentGrouping = [_CPUndoGrouping undoGroupingWithParent:_currentGrouping];
00438 }
00439
00444 - (void)endUndoGrouping
00445 {
00446 if (!_currentGrouping)
00447 [CPException raise:CPInternalInconsistencyException reason:"endUndoGrouping. No undo group is currently open."];
00448
00449 var defaultCenter = [CPNotificationCenter defaultCenter];
00450
00451 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification
00452 object:self];
00453
00454 var parent = [_currentGrouping parent];
00455
00456 if (!parent && [_currentGrouping invocations].length > 0)
00457 {
00458 [defaultCenter
00459 postNotificationName:CPUndoManagerWillCloseUndoGroupNotification
00460 object:self];
00461
00462
00463
00464 var stack = _state === CPUndoManagerUndoing ? _redoStack : _undoStack;
00465
00466 stack.push(_currentGrouping);
00467
00468 if (_levelsOfUndo > 0 && stack.length > _levelsOfUndo)
00469 stack.splice(0, 1);
00470 }
00471
00472
00473 else
00474 {
00475 [parent addInvocationsFromArray:[_currentGrouping invocations]];
00476
00477 [_CPUndoGrouping _poolUndoGrouping:_currentGrouping];
00478 }
00479
00480 _currentGrouping = parent;
00481 }
00482
00489 - (void)enableUndoRegistration
00490 {
00491 if (_disableCount <= 0)
00492 [CPException raise:CPInternalInconsistencyException
00493 reason:"enableUndoRegistration. There are no disable messages in effect right now."];
00494
00495 _disableCount--;
00496 }
00497
00501 - (BOOL)groupsByEvent
00502 {
00503 return _groupsByEvent;
00504 }
00505
00510 - (void)setGroupsByEvent:(BOOL)aFlag
00511 {
00512 aFlag = !!aFlag;
00513
00514 if (_groupsByEvent === aFlag)
00515 return;
00516
00517 _groupsByEvent = aFlag;
00518
00519 if (![self groupsByEvent])
00520 [self _unregisterWithRunLoop];
00521 }
00522
00526 - (unsigned)groupingLevel
00527 {
00528 var grouping = _currentGrouping,
00529 level = _currentGrouping != nil;
00530
00531 while (grouping = [grouping parent])
00532 ++level;
00533
00534 return level;
00535 }
00536
00537
00541 - (void)disableUndoRegistration
00542 {
00543 ++_disableCount;
00544 }
00545
00549 - (BOOL)isUndoRegistrationEnabled
00550 {
00551 return _disableCount == 0;
00552 }
00553
00554
00558 - (BOOL)isUndoing
00559 {
00560 return _state === CPUndoManagerUndoing;
00561 }
00562
00566 - (BOOL)isRedoing
00567 {
00568 return _state === CPUndoManagerRedoing;
00569 }
00570
00571
00575 - (void)removeAllActions
00576 {
00577 _redoStack = [];
00578 _undoStack = [];
00579 _disableCount = 0;
00580 }
00581
00586 - (void)removeAllActionsWithTarget:(id)aTarget
00587 {
00588 [_currentGrouping removeInvocationsWithTarget:aTarget];
00589
00590 var index = _redoStack.length;
00591
00592 while (index--)
00593 {
00594 var grouping = _redoStack[index];
00595
00596 [grouping removeInvocationsWithTarget:aTarget];
00597
00598 if (![grouping invocations].length)
00599 _redoStack.splice(index, 1);
00600 }
00601
00602 index = _undoStack.length;
00603
00604 while (index--)
00605 {
00606 var grouping = _undoStack[index];
00607
00608 [grouping removeInvocationsWithTarget:aTarget];
00609
00610 if (![grouping invocations].length)
00611 _undoStack.splice(index, 1);
00612 }
00613 }
00614
00615
00621 - (void)setActionName:(CPString)anActionName
00622 {
00623 _actionName = anActionName;
00624 }
00625
00632 - (CPString)redoActionName
00633 {
00634 return [self canRedo] ? _actionName : nil;
00635 }
00636
00643 - (CPString)undoActionName
00644 {
00645 return [self canUndo] ? _actionName : nil;
00646 }
00647
00648
00653 - (CPArray)runLoopModes
00654 {
00655 return _runLoopModes;
00656 }
00657
00666 - (void)setRunLoopModes:(CPArray)modes
00667 {
00668 _runLoopModes = [modes copy];
00669
00670 if (_registeredWithRunLoop)
00671 {
00672 [self _unregisterWithRunLoop];
00673 [self _registerWithRunLoop];
00674 }
00675 }
00676
00677 - (void)_runLoopEndUndoGrouping
00678 {
00679 [self endUndoGrouping];
00680 _registeredWithRunLoop = NO;
00681 }
00682
00683
00684 - (void)_registerWithRunLoop
00685 {
00686 if (_registeredWithRunLoop)
00687 return;
00688
00689 _registeredWithRunLoop = YES;
00690 [[CPRunLoop currentRunLoop]
00691 performSelector:@selector(_runLoopEndUndoGrouping)
00692 target:self
00693 argument:nil
00694 order:CPUndoCloseGroupingRunLoopOrdering
00695 modes:_runLoopModes];
00696 }
00697
00698
00699 - (void)_unregisterWithRunLoop
00700 {
00701 if (!_registeredWithRunLoop)
00702 return;
00703
00704 _registeredWithRunLoop = NO;
00705 [[CPRunLoop currentRunLoop]
00706 cancelPerformSelector:@selector(_runLoopEndUndoGrouping)
00707 target:self
00708 argument:nil];
00709 }
00710
00711 - (void)observeChangesForKeyPath:(CPString)aKeyPath ofObject:(id)anObject
00712 {
00713 [anObject addObserver:self
00714 forKeyPath:aKeyPath
00715 options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew
00716 context:NULL];
00717 }
00718
00719 - (void)stopObservingChangesForKeyPath:(CPString)aKeyPath ofObject:(id)anObject
00720 {
00721 [anObject removeObserver:self forKeyPath:aKeyPath];
00722 }
00723
00724 - (void)observeValueForKeyPath:(CPString)aKeyPath
00725 ofObject:(id)anObject
00726 change:(CPDictionary)aChange
00727 context:(id)aContext
00728 {
00729 [[self prepareWithInvocationTarget:anObject]
00730 applyChange:[aChange inverseChangeDictionary]
00731 toKeyPath:aKeyPath];
00732 }
00733
00734 @end
00735
00736 var CPUndoManagerRedoStackKey = @"CPUndoManagerRedoStackKey",
00737 CPUndoManagerUndoStackKey = @"CPUndoManagerUndoStackKey";
00738
00739 CPUndoManagerLevelsOfUndoKey = @"CPUndoManagerLevelsOfUndoKey";
00740 CPUndoManagerActionNameKey = @"CPUndoManagerActionNameKey";
00741 CPUndoManagerCurrentGroupingKey = @"CPUndoManagerCurrentGroupingKey";
00742
00743 CPUndoManagerRunLoopModesKey = @"CPUndoManagerRunLoopModesKey";
00744 CPUndoManagerGroupsByEventKey = @"CPUndoManagerGroupsByEventKey";
00745
00746 @implementation CPUndoManager (CPCoding)
00747
00748 - (id)initWithCoder:(CPCoder)aCoder
00749 {
00750 self = [super init];
00751
00752 if (self)
00753 {
00754 _redoStack = [aCoder decodeObjectForKey:CPUndoManagerRedoStackKey];
00755 _undoStack = [aCoder decodeObjectForKey:CPUndoManagerUndoStackKey];
00756
00757 _levelsOfUndo = [aCoder decodeObjectForKey:CPUndoManagerLevelsOfUndoKey];
00758 _actionName = [aCoder decodeObjectForKey:CPUndoManagerActionNameKey];
00759 _currentGrouping = [aCoder decodeObjectForKey:CPUndoManagerCurrentGroupingKey];
00760
00761 _state = CPUndoManagerNormal;
00762
00763 [self setRunLoopModes:[aCoder decodeObjectForKey:CPUndoManagerRunLoopModesKey]];
00764 [self setGroupsByEvent:[aCoder decodeBoolForKey:CPUndoManagerGroupsByEventKey]];
00765 }
00766
00767 return self;
00768 }
00769
00770 - (void)encodeWithCoder:(CPCoder)aCoder
00771 {
00772 [aCoder encodeObject:_redoStack forKey:CPUndoManagerRedoStackKey];
00773 [aCoder encodeObject:_undoStack forKey:CPUndoManagerUndoStackKey];
00774
00775 [aCoder encodeInt:_levelsOfUndo forKey:CPUndoManagerLevelsOfUndoKey];
00776 [aCoder encodeObject:_actionName forKey:CPUndoManagerActionNameKey];
00777
00778 [aCoder encodeObject:_currentGrouping forKey:CPUndoManagerCurrentGroupingKey];
00779
00780 [aCoder encodeObject:_runLoopModes forKey:CPUndoManagerRunLoopModesKey];
00781 [aCoder encodeBool:_groupsByEvent forKey:CPUndoManagerGroupsByEventKey];
00782 }
00783
00784 @end
00785
00786 @implementation _CPUndoManagerProxy : CPProxy
00787 {
00788 CPUndoManager _undoManager;
00789 }
00790
00791 - (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
00792 {
00793 return [_undoManager _methodSignatureOfPreparedTargetForSelector:aSelector];
00794 }
00795
00796 - (void)forwardInvocation:(CPInvocation)anInvocation
00797 {
00798 [_undoManager _forwardInvocationToPreparedTarget:anInvocation];
00799 }
00800
00801 @end