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
00026
00027 var CPUndoManagerNormal = 0,
00028 CPUndoManagerUndoing = 1,
00029 CPUndoManagerRedoing = 2;
00030
00031 CPUndoManagerCheckpointNotification = @"CPUndoManagerCheckpointNotification";
00032 CPUndoManagerDidOpenUndoGroupNotification = @"CPUndoManagerDidOpenUndoGroupNotification";
00033 CPUndoManagerDidRedoChangeNotification = @"CPUndoManagerDidRedoChangeNotification";
00034 CPUndoManagerDidUndoChangeNotification = @"CPUndoManagerDidUndoChangeNotification";
00035 CPUndoManagerWillCloseUndoGroupNotification = @"CPUndoManagerWillCloseUndoGroupNotification";
00036 CPUndoManagerWillRedoChangeNotification = @"CPUndoManagerWillRedoChangeNotification";
00037 CPUndoManagerWillUndoChangeNotification = @"CPUndoManagerWillUndoChangeNotification";
00038
00039 CPUndoCloseGroupingRunLoopOrdering = 350000;
00040
00041 var _CPUndoGroupingPool = [],
00042 _CPUndoGroupingPoolCapacity = 5;
00043
00044
00045 @implementation _CPUndoGrouping : CPObject
00046 {
00047 _CPUndoGrouping _parent;
00048 CPMutableArray _invocations;
00049 }
00050
00051 + (void)_poolUndoGrouping:(_CPUndoGrouping)anUndoGrouping
00052 {
00053 if (!anUndoGrouping || _CPUndoGroupingPool.length >= _CPUndoGroupingPoolCapacity)
00054 return;
00055
00056 _CPUndoGroupingPool.push(anUndoGrouping);
00057 }
00058
00059 + (id)undoGroupingWithParent:(_CPUndoGrouping)anUndoGrouping
00060 {
00061 if (_CPUndoGroupingPool.length)
00062 {
00063 var grouping = _CPUndoGroupingPool.pop();
00064
00065 grouping._parent = anUndoGrouping;
00066
00067 if (grouping._invocations.length)
00068 grouping._invocations = [];
00069
00070 return grouping;
00071 }
00072
00073 return [[self alloc] initWithParent:anUndoGrouping];
00074 }
00075
00076 - (id)initWithParent:(_CPUndoGrouping)anUndoGrouping
00077 {
00078 self = [super init];
00079
00080 if (self)
00081 {
00082 _parent = anUndoGrouping;
00083 _invocations = [];
00084 }
00085
00086 return self;
00087 }
00088
00089 - (_CPUndoGrouping)parent
00090 {
00091 return _parent;
00092 }
00093
00094 - (void)addInvocation:(CPInvocation)anInvocation
00095 {
00096 _invocations.push(anInvocation);
00097 }
00098
00099 - (void)addInvocationsFromArray:(CPArray)invocations
00100 {
00101 [_invocations addObjectsFromArray:invocations];
00102 }
00103
00104 - (BOOL)removeInvocationsWithTarget:(id)aTarget
00105 {
00106 var index = _invocations.length;
00107
00108 while (index--)
00109 if ([_invocations[index] target] == aTarget)
00110 _invocations.splice(index, 1);
00111 }
00112
00113 - (CPArray)invocations
00114 {
00115 return _invocations;
00116 }
00117
00118 - (void)invoke
00119 {
00120 var index = _invocations.length;
00121
00122 while (index--)
00123 [_invocations[index] invoke];
00124 }
00125
00126 @end
00127
00128 var _CPUndoGroupingParentKey = @"_CPUndoGroupingParentKey",
00129 _CPUndoGroupingInvocationsKey = @"_CPUndoGroupingInvocationsKey";
00130
00131 @implementation _CPUndoGrouping (CPCoder)
00132
00133 - (id)initWithCoder:(CPCoder)aCoder
00134 {
00135 self = [super init];
00136
00137 if (self)
00138 {
00139 _parent = [aCoder decodeObjectForKey:_CPUndoGroupingParentKey];
00140 _invocations = [aCoder decodeObjectForKey:_CPUndoGroupingInvocationsKey];
00141 }
00142
00143 return self;
00144 }
00145
00146 - (void)encodeWithCoder:(CPCoder)aCoder
00147 {
00148 [aCoder encodeObject:_parent forKey:_CPUndoGroupingParentKey];
00149 [aCoder encodeObject:_invocations forKey:_CPUndoGroupingInvocationsKey];
00150 }
00151
00152 @end
00153
00157 @implementation CPUndoManager : CPObject
00158 {
00159 CPMutableArray _redoStack;
00160 CPMutableArray _undoStack;
00161
00162 BOOL _groupsByEvent;
00163 int _disableCount;
00164 int _levelsOfUndo;
00165 id _currentGrouping;
00166 int _state;
00167 CPString _actionName;
00168 id _preparedTarget;
00169
00170 CPArray _runLoopModes;
00171 BOOL _registeredWithRunLoop;
00172 }
00173
00178 - (id)init
00179 {
00180 self = [super init];
00181
00182 if (self)
00183 {
00184 _redoStack = [];
00185 _undoStack = [];
00186
00187 _state = CPUndoManagerNormal;
00188
00189 [self setRunLoopModes:[CPDefaultRunLoopMode]];
00190 [self setGroupsByEvent:YES];
00191 _performRegistered = NO;
00192 }
00193
00194 return self;
00195 }
00196
00197
00205 - (void)registerUndoWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anObject
00206 {
00207 if (!_currentGrouping)
00208 [CPException raise:CPInternalInconsistencyException reason:"No undo group is currently open"];
00209
00210 if (_disableCount > 0)
00211 return;
00212
00213
00214
00215 var invocation = [CPInvocation invocationWithMethodSignature:nil];
00216
00217 [invocation setTarget:aTarget];
00218 [invocation setSelector:aSelector];
00219 [invocation setArgument:anObject atIndex:2];
00220
00221 [_currentGrouping addInvocation:invocation];
00222
00223 if (_state == CPUndoManagerNormal)
00224 [_redoStack removeAllObjects];
00225 }
00231 - (id)prepareWithInvocationTarget:(id)aTarget
00232 {
00233 _preparedTarget = aTarget;
00234
00235 return self;
00236 }
00237
00238
00239
00240
00241
00242 -(CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
00243 {
00244 if ([_preparedTarget respondsToSelector:aSelector])
00245 return 1;
00246
00247 return nil;
00248 }
00249
00255 - (void)forwardInvocation:(CPInvocation)anInvocation
00256 {
00257 if (_disableCount > 0)
00258 return;
00259
00260
00261
00262
00263
00264
00265
00266
00267 [anInvocation setTarget:_preparedTarget];
00268 [_currentGrouping addInvocation:anInvocation];
00269
00270 if (_state == CPUndoManagerNormal)
00271 [_redoStack removeAllObjects];
00272
00273 _preparedTarget = nil;
00274 }
00275
00276
00280 - (BOOL)canRedo
00281 {
00282 return _redoStack.length > 0;
00283 }
00284
00288 - (BOOL)canUndo
00289 {
00290 if (_undoStack.length > 0)
00291 return YES;
00292
00293 return [_currentGrouping actions].length > 0;
00294 }
00295
00296
00300 - (void)undo
00301 {
00302 if ([self groupingLevel] == 1)
00303 [self endUndoGrouping];
00304
00305 [self undoNestedGroup];
00306 }
00307
00311 - (void)undoNestedGroup
00312 {
00313 if (_undoStack.length == 0)
00314 return;
00315
00316 var defaultCenter = [CPNotificationCenter defaultCenter];
00317
00318
00319
00320 [defaultCenter postNotificationName:CPUndoManagerWillUndoChangeNotification object:self];
00321
00322 var undoGrouping = _undoStack.pop();
00323
00324 _state = CPUndoManagerUndoing;
00325
00326 [self beginUndoGrouping];
00327 [undoGrouping invoke];
00328 [self endUndoGrouping];
00329
00330 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
00331
00332 _state = CPUndoManagerNormal;
00333
00334 [defaultCenter postNotificationName:CPUndoManagerDidUndoChangeNotification object:self];
00335 }
00336
00340 - (void)redo
00341 {
00342
00343 if (_redoStack.length == 0)
00344 return;
00345
00346
00347
00348
00349
00350
00351
00352
00353 var defaultCenter = [CPNotificationCenter defaultCenter];
00354
00355 [defaultCenter postNotificationName:CPUndoManagerWillRedoChangeNotification object:self];
00356
00357 var oldUndoGrouping = _currentGrouping,
00358 undoGrouping = _redoStack.pop();
00359
00360 _currentGrouping = nil;
00361 _state = CPUndoManagerRedoing;
00362
00363 [self beginUndoGrouping];
00364 [undoGrouping invoke];
00365 [self endUndoGrouping];
00366
00367 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
00368
00369 _currentGrouping = oldUndoGrouping;
00370 _state = CPUndoManagerNormal;
00371
00372 [defaultCenter postNotificationName:CPUndoManagerDidRedoChangeNotification object:self];
00373 }
00374
00375
00379 - (void)beginUndoGrouping
00380 {
00381 _currentGrouping = [_CPUndoGrouping undoGroupingWithParent:_currentGrouping];
00382 }
00383
00388 - (void)endUndoGrouping
00389 {
00390 if (!_currentGrouping)
00391 [CPException raise:CPInternalInconsistencyException reason:"endUndoGrouping. No undo group is currently open."];
00392
00393 var parent = [_currentGrouping parent];
00394
00395 if (!parent && [_currentGrouping invocations].length > 0)
00396 {
00397 [[CPNotificationCenter defaultCenter]
00398 postNotificationName:CPUndoManagerWillCloseUndoGroupNotification
00399 object:self];
00400
00401
00402
00403 var stack = _state == CPUndoManagerUndoing ? _redoStack : _undoStack;
00404
00405 stack.push(_currentGrouping);
00406
00407 if (_levelsOfUndo > 0 && stack.length > _levelsOfUndo)
00408 stack.splice(0, 1);
00409 }
00410
00411
00412 else
00413 {
00414 [parent addInvocationsFromArray:[_currentGrouping invocations]];
00415
00416 [_CPUndoGrouping _poolUndoGrouping:_currentGrouping];
00417 }
00418
00419 _currentGrouping = parent;
00420 }
00421
00428 - (void)enableUndoRegistration
00429 {
00430 if (_disableCount <= 0)
00431 [CPException raise:CPInternalInconsistencyException
00432 reason:"enableUndoRegistration. There are no disable messages in effect right now."];
00433
00434 _disableCount--;
00435 }
00436
00440 - (BOOL)groupsByEvent
00441 {
00442 return _groupsByEvent;
00443 }
00444
00449 - (void)setGroupsByEvent:(BOOL)aFlag
00450 {
00451 if (_groupsByEvent == aFlag)
00452 return;
00453
00454 _groupsByEvent = aFlag;
00455
00456 if (_groupsByEvent)
00457 {
00458 [self _registerWithRunLoop];
00459
00460
00461
00462 if (!_currentGrouping)
00463 [self beginUndoGrouping];
00464 }
00465 else
00466 [self _unregisterWithRunLoop];
00467 }
00468
00472 - (unsigned)groupingLevel
00473 {
00474 var grouping = _currentGrouping,
00475 level = _currentGrouping != nil;
00476
00477 while (grouping = [grouping parent])
00478 ++level;
00479
00480 return level;
00481 }
00482
00483
00487 - (void)disableUndoRegistration
00488 {
00489 ++_disableCount;
00490 }
00491
00495 - (BOOL)isUndoRegistrationEnabled
00496 {
00497 return _disableCount == 0;
00498 }
00499
00500
00504 - (BOOL)isUndoing
00505 {
00506 return _state == CPUndoManagerUndoing;
00507 }
00508
00512 - (BOOL)isRedoing
00513 {
00514 return _state == CPUndoManagerRedoing;
00515 }
00516
00517
00521 - (void)removeAllActions
00522 {
00523 _redoStack = [];
00524 _undoStack = [];
00525 _disableCount = 0;
00526 }
00527
00532 - (void)removeAllActionsWithTarget:(id)aTarget
00533 {
00534 [_currentGrouping removeInvocationsWithTarget:aTarget];
00535
00536 var index = _redoStack.length;
00537
00538 while (index--)
00539 {
00540 var grouping = _redoStack[index];
00541
00542 [grouping removeInvocationsWithTarget:aTarget];
00543
00544 if (![grouping invocations].length)
00545 _redoStack.splice(index, 1);
00546 }
00547
00548 index = _undoStack.length;
00549
00550 while (index--)
00551 {
00552 var grouping = _undoStack[index];
00553
00554 [grouping removeInvocationsWithTarget:aTarget];
00555
00556 if (![grouping invocations].length)
00557 _undoStack.splice(index, 1);
00558 }
00559 }
00560
00561
00567 - (void)setActionName:(CPString)anActionName
00568 {
00569 _actionName = anActionName;
00570 }
00571
00578 - (CPString)redoActionName
00579 {
00580 return [self canRedo] ? _actionName : nil;
00581 }
00582
00589 - (CPString)undoActionName
00590 {
00591 return [self canUndo] ? _actionName : nil;
00592 }
00593
00594
00599 - (CPArray)runLoopModes
00600 {
00601 return _runLoopModes;
00602 }
00603
00612 - (void)setRunLoopModes:(CPArray)modes
00613 {
00614 _runLoopModes = modes;
00615
00616 [self _unregisterWithRunLoop];
00617
00618 if (_groupsByEvent)
00619 [self _registerWithRunLoop];
00620 }
00621
00622
00623 - (void)beginUndoGroupingForEvent
00624 {
00625 if (!_groupsByEvent)
00626 return;
00627
00628 if (_currentGrouping != nil)
00629 [self endUndoGrouping];
00630
00631 [self beginUndoGrouping];
00632
00633 [[CPRunLoop currentRunLoop] performSelector:@selector(beginUndoGroupingForEvent)
00634 target:self argument:nil order:CPUndoCloseGroupingRunLoopOrdering modes:_runLoopModes];
00635 }
00636
00637
00638 - (void)_registerWithRunLoop
00639 {
00640 if (_registeredWithRunLoop)
00641 return;
00642
00643 _registeredWithRunLoop = YES;
00644 [[CPRunLoop currentRunLoop] performSelector:@selector(beginUndoGroupingForEvent)
00645 target:self argument:nil order:CPUndoCloseGroupingRunLoopOrdering modes:_runLoopModes];
00646 }
00647
00648
00649 - (void)_unregisterWithRunLoop
00650 {
00651 if (!_registeredWithRunLoop)
00652 return;
00653
00654 _registeredWithRunLoop = NO;
00655 [[CPRunLoop currentRunLoop] cancelPerformSelector:@selector(beginUndoGroupingForEvent) target:self argument:nil];
00656 }
00657
00658 @end
00659
00660 var CPUndoManagerRedoStackKey = @"CPUndoManagerRedoStackKey",
00661 CPUndoManagerUndoStackKey = @"CPUndoManagerUndoStackKey";
00662
00663 CPUndoManagerLevelsOfUndoKey = @"CPUndoManagerLevelsOfUndoKey";
00664 CPUndoManagerActionNameKey = @"CPUndoManagerActionNameKey";
00665 CPUndoManagerCurrentGroupingKey = @"CPUndoManagerCurrentGroupingKey";
00666
00667 CPUndoManagerRunLoopModesKey = @"CPUndoManagerRunLoopModesKey";
00668 CPUndoManagerGroupsByEventKey = @"CPUndoManagerGroupsByEventKey";
00669
00670 @implementation CPUndoManager (CPCoding)
00671
00672 - (id)initWithCoder:(CPCoder)aCoder
00673 {
00674 self = [super init];
00675
00676 if (self)
00677 {
00678 _redoStack = [aCoder decodeObjectForKey:CPUndoManagerRedoStackKey];
00679 _undoStack = [aCoder decodeObjectForKey:CPUndoManagerUndoStackKey];
00680
00681 _levelsOfUndo = [aCoder decodeObjectForKey:CPUndoManagerLevelsOfUndoKey];
00682 _actionName = [aCoder decodeObjectForKey:CPUndoManagerActionNameKey];
00683 _currentGrouping = [aCoder decodeObjectForKey:CPUndoManagerCurrentGroupingKey];
00684
00685 _state = CPUndoManagerNormal;
00686
00687 [self setRunLoopModes:[aCoder decodeObjectForKey:CPUndoManagerRunLoopModesKey]];
00688 [self setGroupsByEvent:[aCoder decodeBoolForKey:CPUndoManagerGroupsByEventKey]];
00689 }
00690
00691 return self;
00692 }
00693
00694 - (void)encodeWithCoder:(CPCoder)aCoder
00695 {
00696 [aCoder encodeObject:_redoStack forKey:CPUndoManagerRedoStackKey];
00697 [aCoder encodeObject:_undoStack forKey:CPUndoManagerUndoStackKey];
00698
00699 [aCoder encodeInt:_levelsOfUndo forKey:CPUndoManagerLevelsOfUndoKey];
00700 [aCoder encodeObject:_actionName forKey:CPUndoManagerActionNameKey];
00701
00702 [aCoder encodeObject:_currentGrouping forKey:CPUndoManagerCurrentGroupingKey];
00703
00704 [aCoder encodeObject:_runLoopModes forKey:CPUndoManagerRunLoopModesKey];
00705 [aCoder encodeBool:_groupsByEvent forKey:CPUndoManagerGroupsByEventKey];
00706 }
00707
00708 @end