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
00159 @implementation CPUndoManager : CPObject
00160 {
00161 CPMutableArray _redoStack;
00162 CPMutableArray _undoStack;
00163
00164 BOOL _groupsByEvent;
00165 int _disableCount;
00166 int _levelsOfUndo;
00167 id _currentGrouping;
00168 int _state;
00169 CPString _actionName;
00170 id _preparedTarget;
00171
00172 CPArray _runLoopModes;
00173 BOOL _registeredWithRunLoop;
00174 }
00175
00180 - (id)init
00181 {
00182 self = [super init];
00183
00184 if (self)
00185 {
00186 _redoStack = [];
00187 _undoStack = [];
00188
00189 _state = CPUndoManagerNormal;
00190
00191 [self setRunLoopModes:[CPDefaultRunLoopMode]];
00192 [self setGroupsByEvent:YES];
00193 _performRegistered = NO;
00194 }
00195
00196 return self;
00197 }
00198
00199
00207 - (void)registerUndoWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anObject
00208 {
00209 if (!_currentGrouping)
00210 [CPException raise:CPInternalInconsistencyException reason:"No undo group is currently open"];
00211
00212 if (_disableCount > 0)
00213 return;
00214
00215
00216
00217 var invocation = [CPInvocation invocationWithMethodSignature:nil];
00218
00219 [invocation setTarget:aTarget];
00220 [invocation setSelector:aSelector];
00221 [invocation setArgument:anObject atIndex:2];
00222
00223 [_currentGrouping addInvocation:invocation];
00224
00225 if (_state == CPUndoManagerNormal)
00226 [_redoStack removeAllObjects];
00227 }
00233 - (id)prepareWithInvocationTarget:(id)aTarget
00234 {
00235 _preparedTarget = aTarget;
00236
00237 return self;
00238 }
00239
00240
00241
00242
00243
00244 -(CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
00245 {
00246 if ([_preparedTarget respondsToSelector:aSelector])
00247 return 1;
00248
00249 return nil;
00250 }
00251
00257 - (void)forwardInvocation:(CPInvocation)anInvocation
00258 {
00259 if (_disableCount > 0)
00260 return;
00261
00262
00263
00264
00265
00266
00267
00268
00269 [anInvocation setTarget:_preparedTarget];
00270 [_currentGrouping addInvocation:anInvocation];
00271
00272 if (_state == CPUndoManagerNormal)
00273 [_redoStack removeAllObjects];
00274
00275 _preparedTarget = nil;
00276 }
00277
00278
00282 - (BOOL)canRedo
00283 {
00284 return _redoStack.length > 0;
00285 }
00286
00290 - (BOOL)canUndo
00291 {
00292 if (_undoStack.length > 0)
00293 return YES;
00294
00295 return [_currentGrouping actions].length > 0;
00296 }
00297
00298
00302 - (void)undo
00303 {
00304 if ([self groupingLevel] == 1)
00305 [self endUndoGrouping];
00306
00307 [self undoNestedGroup];
00308 }
00309
00313 - (void)undoNestedGroup
00314 {
00315 if (_undoStack.length == 0)
00316 return;
00317
00318 var defaultCenter = [CPNotificationCenter defaultCenter];
00319
00320
00321
00322 [defaultCenter postNotificationName:CPUndoManagerWillUndoChangeNotification object:self];
00323
00324 var undoGrouping = _undoStack.pop();
00325
00326 _state = CPUndoManagerUndoing;
00327
00328 [self beginUndoGrouping];
00329 [undoGrouping invoke];
00330 [self endUndoGrouping];
00331
00332 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
00333
00334 _state = CPUndoManagerNormal;
00335
00336 [defaultCenter postNotificationName:CPUndoManagerDidUndoChangeNotification object:self];
00337 }
00338
00342 - (void)redo
00343 {
00344
00345 if (_redoStack.length == 0)
00346 return;
00347
00348
00349
00350
00351
00352
00353
00354
00355 var defaultCenter = [CPNotificationCenter defaultCenter];
00356
00357 [defaultCenter postNotificationName:CPUndoManagerWillRedoChangeNotification object:self];
00358
00359 var oldUndoGrouping = _currentGrouping,
00360 undoGrouping = _redoStack.pop();
00361
00362 _currentGrouping = nil;
00363 _state = CPUndoManagerRedoing;
00364
00365 [self beginUndoGrouping];
00366 [undoGrouping invoke];
00367 [self endUndoGrouping];
00368
00369 [_CPUndoGrouping _poolUndoGrouping:undoGrouping];
00370
00371 _currentGrouping = oldUndoGrouping;
00372 _state = CPUndoManagerNormal;
00373
00374 [defaultCenter postNotificationName:CPUndoManagerDidRedoChangeNotification object:self];
00375 }
00376
00377
00381 - (void)beginUndoGrouping
00382 {
00383 _currentGrouping = [_CPUndoGrouping undoGroupingWithParent:_currentGrouping];
00384 }
00385
00390 - (void)endUndoGrouping
00391 {
00392 if (!_currentGrouping)
00393 [CPException raise:CPInternalInconsistencyException reason:"endUndoGrouping. No undo group is currently open."];
00394
00395 var parent = [_currentGrouping parent];
00396
00397 if (!parent && [_currentGrouping invocations].length > 0)
00398 {
00399 [[CPNotificationCenter defaultCenter]
00400 postNotificationName:CPUndoManagerWillCloseUndoGroupNotification
00401 object:self];
00402
00403
00404
00405 var stack = _state == CPUndoManagerUndoing ? _redoStack : _undoStack;
00406
00407 stack.push(_currentGrouping);
00408
00409 if (_levelsOfUndo > 0 && stack.length > _levelsOfUndo)
00410 stack.splice(0, 1);
00411 }
00412
00413
00414 else
00415 {
00416 [parent addInvocationsFromArray:[_currentGrouping invocations]];
00417
00418 [_CPUndoGrouping _poolUndoGrouping:_currentGrouping];
00419 }
00420
00421 _currentGrouping = parent;
00422 }
00423
00430 - (void)enableUndoRegistration
00431 {
00432 if (_disableCount <= 0)
00433 [CPException raise:CPInternalInconsistencyException
00434 reason:"enableUndoRegistration. There are no disable messages in effect right now."];
00435
00436 _disableCount--;
00437 }
00438
00442 - (BOOL)groupsByEvent
00443 {
00444 return _groupsByEvent;
00445 }
00446
00451 - (void)setGroupsByEvent:(BOOL)aFlag
00452 {
00453 if (_groupsByEvent == aFlag)
00454 return;
00455
00456 _groupsByEvent = aFlag;
00457
00458 if (_groupsByEvent)
00459 {
00460 [self _registerWithRunLoop];
00461
00462
00463
00464 if (!_currentGrouping)
00465 [self beginUndoGrouping];
00466 }
00467 else
00468 [self _unregisterWithRunLoop];
00469 }
00470
00474 - (unsigned)groupingLevel
00475 {
00476 var grouping = _currentGrouping,
00477 level = _currentGrouping != nil;
00478
00479 while (grouping = [grouping parent])
00480 ++level;
00481
00482 return level;
00483 }
00484
00485
00489 - (void)disableUndoRegistration
00490 {
00491 ++_disableCount;
00492 }
00493
00497 - (BOOL)isUndoRegistrationEnabled
00498 {
00499 return _disableCount == 0;
00500 }
00501
00502
00506 - (BOOL)isUndoing
00507 {
00508 return _state == CPUndoManagerUndoing;
00509 }
00510
00514 - (BOOL)isRedoing
00515 {
00516 return _state == CPUndoManagerRedoing;
00517 }
00518
00519
00523 - (void)removeAllActions
00524 {
00525 _redoStack = [];
00526 _undoStack = [];
00527 _disableCount = 0;
00528 }
00529
00534 - (void)removeAllActionsWithTarget:(id)aTarget
00535 {
00536 [_currentGrouping removeInvocationsWithTarget:aTarget];
00537
00538 var index = _redoStack.length;
00539
00540 while (index--)
00541 {
00542 var grouping = _redoStack[index];
00543
00544 [grouping removeInvocationsWithTarget:aTarget];
00545
00546 if (![grouping invocations].length)
00547 _redoStack.splice(index, 1);
00548 }
00549
00550 index = _undoStack.length;
00551
00552 while (index--)
00553 {
00554 var grouping = _undoStack[index];
00555
00556 [grouping removeInvocationsWithTarget:aTarget];
00557
00558 if (![grouping invocations].length)
00559 _undoStack.splice(index, 1);
00560 }
00561 }
00562
00563
00569 - (void)setActionName:(CPString)anActionName
00570 {
00571 _actionName = anActionName;
00572 }
00573
00580 - (CPString)redoActionName
00581 {
00582 return [self canRedo] ? _actionName : nil;
00583 }
00584
00591 - (CPString)undoActionName
00592 {
00593 return [self canUndo] ? _actionName : nil;
00594 }
00595
00596
00601 - (CPArray)runLoopModes
00602 {
00603 return _runLoopModes;
00604 }
00605
00614 - (void)setRunLoopModes:(CPArray)modes
00615 {
00616 _runLoopModes = modes;
00617
00618 [self _unregisterWithRunLoop];
00619
00620 if (_groupsByEvent)
00621 [self _registerWithRunLoop];
00622 }
00623
00624
00625 - (void)beginUndoGroupingForEvent
00626 {
00627 if (!_groupsByEvent)
00628 return;
00629
00630 if (_currentGrouping != nil)
00631 [self endUndoGrouping];
00632
00633 [self beginUndoGrouping];
00634
00635 [[CPRunLoop currentRunLoop] performSelector:@selector(beginUndoGroupingForEvent)
00636 target:self argument:nil order:CPUndoCloseGroupingRunLoopOrdering modes:_runLoopModes];
00637 }
00638
00639
00640 - (void)_registerWithRunLoop
00641 {
00642 if (_registeredWithRunLoop)
00643 return;
00644
00645 _registeredWithRunLoop = YES;
00646 [[CPRunLoop currentRunLoop] performSelector:@selector(beginUndoGroupingForEvent)
00647 target:self argument:nil order:CPUndoCloseGroupingRunLoopOrdering modes:_runLoopModes];
00648 }
00649
00650
00651 - (void)_unregisterWithRunLoop
00652 {
00653 if (!_registeredWithRunLoop)
00654 return;
00655
00656 _registeredWithRunLoop = NO;
00657 [[CPRunLoop currentRunLoop] cancelPerformSelector:@selector(beginUndoGroupingForEvent) target:self argument:nil];
00658 }
00659
00660 @end
00661
00662 var CPUndoManagerRedoStackKey = @"CPUndoManagerRedoStackKey",
00663 CPUndoManagerUndoStackKey = @"CPUndoManagerUndoStackKey";
00664
00665 CPUndoManagerLevelsOfUndoKey = @"CPUndoManagerLevelsOfUndoKey";
00666 CPUndoManagerActionNameKey = @"CPUndoManagerActionNameKey";
00667 CPUndoManagerCurrentGroupingKey = @"CPUndoManagerCurrentGroupingKey";
00668
00669 CPUndoManagerRunLoopModesKey = @"CPUndoManagerRunLoopModesKey";
00670 CPUndoManagerGroupsByEventKey = @"CPUndoManagerGroupsByEventKey";
00671
00672 @implementation CPUndoManager (CPCoding)
00673
00674 - (id)initWithCoder:(CPCoder)aCoder
00675 {
00676 self = [super init];
00677
00678 if (self)
00679 {
00680 _redoStack = [aCoder decodeObjectForKey:CPUndoManagerRedoStackKey];
00681 _undoStack = [aCoder decodeObjectForKey:CPUndoManagerUndoStackKey];
00682
00683 _levelsOfUndo = [aCoder decodeObjectForKey:CPUndoManagerLevelsOfUndoKey];
00684 _actionName = [aCoder decodeObjectForKey:CPUndoManagerActionNameKey];
00685 _currentGrouping = [aCoder decodeObjectForKey:CPUndoManagerCurrentGroupingKey];
00686
00687 _state = CPUndoManagerNormal;
00688
00689 [self setRunLoopModes:[aCoder decodeObjectForKey:CPUndoManagerRunLoopModesKey]];
00690 [self setGroupsByEvent:[aCoder decodeBoolForKey:CPUndoManagerGroupsByEventKey]];
00691 }
00692
00693 return self;
00694 }
00695
00696 - (void)encodeWithCoder:(CPCoder)aCoder
00697 {
00698 [aCoder encodeObject:_redoStack forKey:CPUndoManagerRedoStackKey];
00699 [aCoder encodeObject:_undoStack forKey:CPUndoManagerUndoStackKey];
00700
00701 [aCoder encodeInt:_levelsOfUndo forKey:CPUndoManagerLevelsOfUndoKey];
00702 [aCoder encodeObject:_actionName forKey:CPUndoManagerActionNameKey];
00703
00704 [aCoder encodeObject:_currentGrouping forKey:CPUndoManagerCurrentGroupingKey];
00705
00706 [aCoder encodeObject:_runLoopModes forKey:CPUndoManagerRunLoopModesKey];
00707 [aCoder encodeBool:_groupsByEvent forKey:CPUndoManagerGroupsByEventKey];
00708 }
00709
00710 @end