![]() |
API 0.9.5
|
00001 /* 00002 * CPUndoManager.j 00003 * Foundation 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2008, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 var CPUndoManagerNormal = 0, 00025 CPUndoManagerUndoing = 1, 00026 CPUndoManagerRedoing = 2; 00027 00028 CPUndoManagerCheckpointNotification = @"CPUndoManagerCheckpointNotification"; 00029 CPUndoManagerDidOpenUndoGroupNotification = @"CPUndoManagerDidOpenUndoGroupNotification"; 00030 CPUndoManagerDidRedoChangeNotification = @"CPUndoManagerDidRedoChangeNotification"; 00031 CPUndoManagerDidUndoChangeNotification = @"CPUndoManagerDidUndoChangeNotification"; 00032 CPUndoManagerWillCloseUndoGroupNotification = @"CPUndoManagerWillCloseUndoGroupNotification"; 00033 CPUndoManagerWillRedoChangeNotification = @"CPUndoManagerWillRedoChangeNotification"; 00034 CPUndoManagerWillUndoChangeNotification = @"CPUndoManagerWillUndoChangeNotification"; 00035 00036 CPUndoCloseGroupingRunLoopOrdering = 350000; 00037 00038 var _CPUndoGroupingPool = [], 00039 _CPUndoGroupingPoolCapacity = 5; 00040 00041 /* @ignore */ 00042 @implementation _CPUndoGrouping : CPObject 00043 { 00044 _CPUndoGrouping _parent; 00045 CPMutableArray _invocations; 00046 CPString _actionName; 00047 } 00048 00049 + (void)_poolUndoGrouping:(_CPUndoGrouping)anUndoGrouping 00050 { 00051 if (!anUndoGrouping || _CPUndoGroupingPool.length >= _CPUndoGroupingPoolCapacity) 00052 return; 00053 00054 _CPUndoGroupingPool.push(anUndoGrouping); 00055 } 00056 00057 + (id)undoGroupingWithParent:(_CPUndoGrouping)anUndoGrouping 00058 { 00059 if (_CPUndoGroupingPool.length) 00060 { 00061 var grouping = _CPUndoGroupingPool.pop(); 00062 00063 grouping._parent = anUndoGrouping; 00064 00065 if (grouping._invocations.length) 00066 grouping._invocations = []; 00067 00068 return grouping; 00069 } 00070 00071 return [[self alloc] initWithParent:anUndoGrouping]; 00072 } 00073 00074 - (id)initWithParent:(_CPUndoGrouping)anUndoGrouping 00075 { 00076 self = [super init]; 00077 00078 if (self) 00079 { 00080 _parent = anUndoGrouping; 00081 _invocations = []; 00082 _actionName = @""; 00083 } 00084 00085 return self; 00086 } 00087 00088 - (_CPUndoGrouping)parent 00089 { 00090 return _parent; 00091 } 00092 00093 - (void)addInvocation:(CPInvocation)anInvocation 00094 { 00095 _invocations.push(anInvocation); 00096 } 00097 00098 - (void)addInvocationsFromArray:(CPArray)invocations 00099 { 00100 [_invocations addObjectsFromArray:invocations]; 00101 } 00102 00103 - (BOOL)removeInvocationsWithTarget:(id)aTarget 00104 { 00105 var index = _invocations.length; 00106 00107 while (index--) 00108 if ([_invocations[index] target] == aTarget) 00109 _invocations.splice(index, 1); 00110 } 00111 00112 - (CPArray)invocations 00113 { 00114 return _invocations; 00115 } 00116 00117 - (void)invoke 00118 { 00119 var index = _invocations.length; 00120 00121 while (index--) 00122 [_invocations[index] invoke]; 00123 } 00124 00125 - (void)setActionName:(CPString)aName 00126 { 00127 _actionName = aName; 00128 } 00129 00130 - (CPString)actionName 00131 { 00132 return _actionName; 00133 } 00134 00135 @end 00136 00137 var _CPUndoGroupingParentKey = @"_CPUndoGroupingParentKey", 00138 _CPUndoGroupingInvocationsKey = @"_CPUndoGroupingInvocationsKey", 00139 _CPUndoGroupingActionNameKey = @"_CPUndoGroupingActionNameKey"; 00140 00141 @implementation _CPUndoGrouping (CPCoder) 00142 00143 - (id)initWithCoder:(CPCoder)aCoder 00144 { 00145 self = [super init]; 00146 00147 if (self) 00148 { 00149 _parent = [aCoder decodeObjectForKey:_CPUndoGroupingParentKey]; 00150 _invocations = [aCoder decodeObjectForKey:_CPUndoGroupingInvocationsKey]; 00151 _actionName = [aCoder decodeObjectForKey:_CPUndoGroupingActionNameKey]; 00152 } 00153 00154 return self; 00155 } 00156 00157 - (void)encodeWithCoder:(CPCoder)aCoder 00158 { 00159 [aCoder encodeObject:_parent forKey:_CPUndoGroupingParentKey]; 00160 [aCoder encodeObject:_invocations forKey:_CPUndoGroupingInvocationsKey]; 00161 [aCoder encodeObject:_actionName forKey:_CPUndoGroupingActionNameKey]; 00162 } 00163 00164 @end 00165 00181 @implementation CPUndoManager : CPObject 00182 { 00183 CPMutableArray _redoStack; 00184 CPMutableArray _undoStack; 00185 00186 BOOL _groupsByEvent; 00187 int _disableCount; 00188 int _levelsOfUndo; 00189 id _currentGrouping; 00190 int _state; 00191 00192 id _preparedTarget; 00193 id _undoManagerProxy; 00194 00195 CPArray _runLoopModes; 00196 BOOL _registeredWithRunLoop; 00197 } 00198 00203 - (id)init 00204 { 00205 self = [super init]; 00206 00207 if (self) 00208 { 00209 _redoStack = []; 00210 _undoStack = []; 00211 00212 _state = CPUndoManagerNormal; 00213 00214 [self setRunLoopModes:[CPDefaultRunLoopMode]]; 00215 [self setGroupsByEvent:YES]; 00216 00217 _undoManagerProxy = [_CPUndoManagerProxy alloc]; 00218 _undoManagerProxy._undoManager = self; 00219 } 00220 00221 return self; 00222 } 00223 00224 - (void)_addUndoInvocation:(CPInvocation)anInvocation 00225 { 00226 if (!_currentGrouping) 00227 // Remember that we create these lazily... 00228 if ([self groupsByEvent]) 00229 [self _beginUndoGroupingForEvent]; 00230 else 00231 [CPException raise:CPInternalInconsistencyException reason:"No undo group is currently open"]; 00232 00233 [_currentGrouping addInvocation:anInvocation]; 00234 00235 if (_state === CPUndoManagerNormal) 00236 [_redoStack removeAllObjects]; 00237 } 00238 00239 // Registering Undo Operations 00247 - (void)registerUndoWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anObject 00248 { 00249 // Don't do anything if we're disabled. 00250 if (_disableCount > 0) 00251 return; 00252 00253 //signature = [target methodSignatureForSelector:selector]; 00254 // FIXME: we need method signatures. 00255 var invocation = [CPInvocation invocationWithMethodSignature:nil]; 00256 00257 [invocation setTarget:aTarget]; 00258 [invocation setSelector:aSelector]; 00259 [invocation setArgument:anObject atIndex:2]; 00260 00261 [self _addUndoInvocation:invocation]; 00262 } 00268 - (id)prepareWithInvocationTarget:(id)aTarget 00269 { 00270 _preparedTarget = aTarget; 00271 00272 return _undoManagerProxy; 00273 } 00274 00275 /* 00276 FIXME This method doesn't seem to do anything right 00277 @ignore 00278 */ 00279 - (CPMethodSignature)_methodSignatureOfPreparedTargetForSelector:(SEL)aSelector 00280 { 00281 if ([_preparedTarget respondsToSelector:aSelector]) 00282 return 1; 00283 00284 return nil;//[_preparedTarget methodSignatureForSelector:selector]; 00285 } 00286 00292 - (void)_forwardInvocationToPreparedTarget:(CPInvocation)anInvocation 00293 { 00294 // Don't do anything if we're disabled. 00295 if (_disableCount > 0) 00296 return; 00297 00298 /* 00299 if (_currentGroup == nil) 00300 [NSException raise:NSInternalInconsistencyException 00301 format:@"forwardInvocation called without first opening an undo group"]; 00302 */ 00303 [anInvocation setTarget:_preparedTarget]; 00304 00305 [self _addUndoInvocation:anInvocation]; 00306 00307 _preparedTarget = nil; 00308 } 00309 00310 // Checking Undo Ability 00314 - (BOOL)canRedo 00315 { 00316 [[CPNotificationCenter defaultCenter] 00317 postNotificationName:CPUndoManagerCheckpointNotification 00318 object:self]; 00319 00320 return [_redoStack count] > 0; 00321 } 00322 00326 - (BOOL)canUndo 00327 { 00328 if (_undoStack.length > 0) 00329 return YES; 00330 00331 return [[_currentGrouping invocations] count] > 0; 00332 } 00333 00334 // Preform Undo and Redo 00338 - (void)undo 00339 { 00340 if ([self groupingLevel] === 1) 00341 [self endUndoGrouping]; 00342 00343 [self undoNestedGroup]; 00344 } 00345 00349 - (void)undoNestedGroup 00350 { 00351 if ([_undoStack count] <= 0) 00352 return; 00353 00354 var defaultCenter = [CPNotificationCenter defaultCenter]; 00355 00356 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification 00357 object:self]; 00358 00359 [defaultCenter postNotificationName:CPUndoManagerWillUndoChangeNotification 00360 object:self]; 00361 00362 var undoGrouping = _undoStack.pop(), 00363 actionName = [undoGrouping actionName]; 00364 00365 _state = CPUndoManagerUndoing; 00366 00367 [self _beginUndoGrouping]; 00368 [undoGrouping invoke]; 00369 [self endUndoGrouping]; 00370 00371 [_CPUndoGrouping _poolUndoGrouping:undoGrouping]; 00372 00373 _state = CPUndoManagerNormal; 00374 00375 [[_redoStack lastObject] setActionName:actionName]; 00376 00377 [defaultCenter postNotificationName:CPUndoManagerDidUndoChangeNotification 00378 object:self]; 00379 } 00380 00384 - (void)redo 00385 { 00386 // Don't do anything if we have no redos. 00387 if ([_redoStack count] <= 0) 00388 return; 00389 00390 /* if (_state == NSUndoManagerUndoing) 00391 [NSException raise:NSInternalInconsistencyException 00392 format:@"redo called while undoing"]; 00393 */ 00394 00395 var defaultCenter = [CPNotificationCenter defaultCenter]; 00396 00397 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification 00398 object:self]; 00399 00400 [defaultCenter postNotificationName:CPUndoManagerWillRedoChangeNotification 00401 object:self]; 00402 00403 var oldUndoGrouping = _currentGrouping, 00404 undoGrouping = _redoStack.pop(), 00405 actionName = [undoGrouping actionName]; 00406 00407 _currentGrouping = nil; 00408 _state = CPUndoManagerRedoing; 00409 00410 [self _beginUndoGrouping]; 00411 [undoGrouping invoke]; 00412 [self endUndoGrouping]; 00413 00414 [_CPUndoGrouping _poolUndoGrouping:undoGrouping]; 00415 00416 _currentGrouping = oldUndoGrouping; 00417 _state = CPUndoManagerNormal; 00418 00419 [[_undoStack lastObject] setActionName:actionName]; 00420 [defaultCenter postNotificationName:CPUndoManagerDidRedoChangeNotification object:self]; 00421 } 00422 00423 // Creating Undo Groups 00427 - (void)beginUndoGrouping 00428 { 00429 // It doesn't matter that the user is creating a group themselves, we are 00430 // pretending to have opened the group at the beginning of the run loop, 00431 // so create an implicit one here. 00432 if (!_currentGrouping && [self groupsByEvent]) 00433 [self _beginUndoGroupingForEvent]; 00434 00435 [[CPNotificationCenter defaultCenter] 00436 postNotificationName:CPUndoManagerCheckpointNotification 00437 object:self]; 00438 00439 [self _beginUndoGrouping]; 00440 } 00441 00442 /* @ignore */ 00443 - (void)_beginUndoGroupingForEvent 00444 { 00445 [self _beginUndoGrouping]; 00446 [self _registerWithRunLoop]; 00447 } 00448 00449 /* @ignore */ 00450 - (void)_beginUndoGrouping 00451 { 00452 _currentGrouping = [_CPUndoGrouping undoGroupingWithParent:_currentGrouping]; 00453 } 00454 00459 - (void)endUndoGrouping 00460 { 00461 if (!_currentGrouping) 00462 [CPException raise:CPInternalInconsistencyException reason:"endUndoGrouping. No undo group is currently open."]; 00463 00464 var defaultCenter = [CPNotificationCenter defaultCenter]; 00465 00466 [defaultCenter postNotificationName:CPUndoManagerCheckpointNotification 00467 object:self]; 00468 00469 var parent = [_currentGrouping parent]; 00470 00471 if (!parent && [_currentGrouping invocations].length > 0) 00472 { 00473 [defaultCenter 00474 postNotificationName:CPUndoManagerWillCloseUndoGroupNotification 00475 object:self]; 00476 00477 // Put this group on the redo stack if we are currently undoing, otherwise 00478 // put it on the undo stack. That way, "undos" become "redos". 00479 var stack = _state === CPUndoManagerUndoing ? _redoStack : _undoStack; 00480 00481 stack.push(_currentGrouping); 00482 00483 if (_levelsOfUndo > 0 && stack.length > _levelsOfUndo) 00484 stack.splice(0, 1); 00485 } 00486 00487 // Nested Undo Grouping 00488 else 00489 { 00490 [parent addInvocationsFromArray:[_currentGrouping invocations]]; 00491 00492 [_CPUndoGrouping _poolUndoGrouping:_currentGrouping]; 00493 } 00494 00495 _currentGrouping = parent; 00496 } 00497 00504 - (void)enableUndoRegistration 00505 { 00506 if (_disableCount <= 0) 00507 [CPException raise:CPInternalInconsistencyException 00508 reason:"enableUndoRegistration. There are no disable messages in effect right now."]; 00509 00510 _disableCount--; 00511 } 00512 00516 - (BOOL)groupsByEvent 00517 { 00518 return _groupsByEvent; 00519 } 00520 00525 - (void)setGroupsByEvent:(BOOL)aFlag 00526 { 00527 aFlag = !!aFlag; 00528 00529 if (_groupsByEvent === aFlag) 00530 return; 00531 00532 _groupsByEvent = aFlag; 00533 00534 if (![self groupsByEvent]) 00535 [self _unregisterWithRunLoop]; 00536 } 00537 00541 - (unsigned)groupingLevel 00542 { 00543 var grouping = _currentGrouping, 00544 level = _currentGrouping ? 1 : 0; 00545 00546 while (grouping = [grouping parent]) 00547 ++level; 00548 00549 return level; 00550 } 00551 00552 // Disabling Undo 00556 - (void)disableUndoRegistration 00557 { 00558 ++_disableCount; 00559 } 00560 00564 - (BOOL)isUndoRegistrationEnabled 00565 { 00566 return _disableCount == 0; 00567 } 00568 00569 // Checking Whether Undo or Redo Is Being Performed 00573 - (BOOL)isUndoing 00574 { 00575 return _state === CPUndoManagerUndoing; 00576 } 00577 00581 - (BOOL)isRedoing 00582 { 00583 return _state === CPUndoManagerRedoing; 00584 } 00585 00586 // Clearing Undo Operations 00590 - (void)removeAllActions 00591 { 00592 // Close off any groupings. 00593 while (_currentGrouping) 00594 [self endUndoGrouping]; 00595 00596 // Won't need this anymore 00597 [self _unregisterWithRunLoop]; 00598 00599 _state = CPUndoManagerNormal; 00600 _redoStack = []; 00601 _undoStack = []; 00602 _disableCount = 0; 00603 } 00604 00609 - (void)removeAllActionsWithTarget:(id)aTarget 00610 { 00611 [_currentGrouping removeInvocationsWithTarget:aTarget]; 00612 00613 var index = _redoStack.length; 00614 00615 while (index--) 00616 { 00617 var grouping = _redoStack[index]; 00618 00619 [grouping removeInvocationsWithTarget:aTarget]; 00620 00621 if (![grouping invocations].length) 00622 _redoStack.splice(index, 1); 00623 } 00624 00625 index = _undoStack.length; 00626 00627 while (index--) 00628 { 00629 var grouping = _undoStack[index]; 00630 00631 [grouping removeInvocationsWithTarget:aTarget]; 00632 00633 if (![grouping invocations].length) 00634 _undoStack.splice(index, 1); 00635 } 00636 } 00637 00638 // Managing the Action Name 00644 - (void)setActionName:(CPString)anActionName 00645 { 00646 if (anActionName !== nil && _currentGrouping) 00647 [_currentGrouping setActionName:anActionName]; 00648 } 00649 00656 - (CPString)redoActionName 00657 { 00658 if (![self canRedo]) 00659 return nil; 00660 00661 return [[_redoStack lastObject] actionName]; 00662 } 00663 00669 - (CPString)redoMenuItemTitle 00670 { 00671 return [self redoMenuTitleForUndoActionName:[self redoActionName]]; 00672 } 00673 00679 - (CPString)redoMenuTitleForUndoActionName:(CPString)anActionName 00680 { 00681 // This handles the empty string ("") case as well. 00682 if (anActionName || anActionName === 0) 00683 00684 // FIXME: The terms @"Redo" and @"Redo %@" should be localized. 00685 // KEYWORDS: Localization 00686 return @"Redo " + anActionName; 00687 00688 return @"Redo"; 00689 } 00690 00697 - (CPString)undoActionName 00698 { 00699 if (![self canUndo]) 00700 return nil; 00701 00702 return [[_undoStack lastObject] actionName]; 00703 } 00704 00710 - (CPString)undoMenuItemTitle 00711 { 00712 return [self undoMenuTitleForUndoActionName:[self undoActionName]]; 00713 } 00714 00720 - (CPString)undoMenuTitleForUndoActionName:(CPString)anActionName 00721 { 00722 // This handles the empty string ("") case as well. 00723 if (anActionName || anActionName === 0) 00724 00725 // FIXME: The terms @"Undo" and @"Undo %@" should be localized. 00726 // KEYWORDS: Localization 00727 return @"Undo " + anActionName; 00728 00729 return @"Undo"; 00730 } 00731 00732 // Working With Run Loops 00737 - (CPArray)runLoopModes 00738 { 00739 return _runLoopModes; 00740 } 00741 00750 - (void)setRunLoopModes:(CPArray)modes 00751 { 00752 _runLoopModes = [modes copy]; 00753 00754 if (_registeredWithRunLoop) 00755 { 00756 [self _unregisterWithRunLoop]; 00757 [self _registerWithRunLoop]; 00758 } 00759 } 00760 00761 - (void)_runLoopEndUndoGrouping 00762 { 00763 [self endUndoGrouping]; 00764 _registeredWithRunLoop = NO; 00765 } 00766 00767 /* @ignore */ 00768 - (void)_registerWithRunLoop 00769 { 00770 if (_registeredWithRunLoop) 00771 return; 00772 00773 _registeredWithRunLoop = YES; 00774 [[CPRunLoop currentRunLoop] 00775 performSelector:@selector(_runLoopEndUndoGrouping) 00776 target:self 00777 argument:nil 00778 order:CPUndoCloseGroupingRunLoopOrdering 00779 modes:_runLoopModes]; 00780 } 00781 00782 /* @ignore */ 00783 - (void)_unregisterWithRunLoop 00784 { 00785 if (!_registeredWithRunLoop) 00786 return; 00787 00788 _registeredWithRunLoop = NO; 00789 [[CPRunLoop currentRunLoop] 00790 cancelPerformSelector:@selector(_runLoopEndUndoGrouping) 00791 target:self 00792 argument:nil]; 00793 } 00794 00795 - (void)observeChangesForKeyPath:(CPString)aKeyPath ofObject:(id)anObject 00796 { 00797 [anObject addObserver:self 00798 forKeyPath:aKeyPath 00799 options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew 00800 context:NULL]; 00801 } 00802 00803 - (void)stopObservingChangesForKeyPath:(CPString)aKeyPath ofObject:(id)anObject 00804 { 00805 [anObject removeObserver:self forKeyPath:aKeyPath]; 00806 } 00807 00808 - (void)observeValueForKeyPath:(CPString)aKeyPath 00809 ofObject:(id)anObject 00810 change:(CPDictionary)aChange 00811 context:(id)aContext 00812 { 00813 // Don't add no-ops to the undo stack. 00814 var before = [aChange valueForKey:CPKeyValueChangeOldKey], 00815 after = [aChange valueForKey:CPKeyValueChangeNewKey]; 00816 if (before === after || (before !== nil && before.isa && (after === nil || after.isa) && [before isEqual:after])) 00817 return; 00818 00819 [[self prepareWithInvocationTarget:anObject] 00820 applyChange:[aChange inverseChangeDictionary] 00821 toKeyPath:aKeyPath]; 00822 } 00823 00824 @end 00825 00826 var CPUndoManagerRedoStackKey = @"CPUndoManagerRedoStackKey", 00827 CPUndoManagerUndoStackKey = @"CPUndoManagerUndoStackKey", 00828 00829 CPUndoManagerLevelsOfUndoKey = @"CPUndoManagerLevelsOfUndoKey", 00830 CPUndoManagerActionNameKey = @"CPUndoManagerActionNameKey", 00831 CPUndoManagerCurrentGroupingKey = @"CPUndoManagerCurrentGroupingKey", 00832 00833 CPUndoManagerRunLoopModesKey = @"CPUndoManagerRunLoopModesKey", 00834 CPUndoManagerGroupsByEventKey = @"CPUndoManagerGroupsByEventKey"; 00835 00836 @implementation CPUndoManager (CPCoding) 00837 00838 - (id)initWithCoder:(CPCoder)aCoder 00839 { 00840 self = [super init]; 00841 00842 if (self) 00843 { 00844 _redoStack = [aCoder decodeObjectForKey:CPUndoManagerRedoStackKey]; 00845 _undoStack = [aCoder decodeObjectForKey:CPUndoManagerUndoStackKey]; 00846 00847 _levelsOfUndo = [aCoder decodeObjectForKey:CPUndoManagerLevelsOfUndoKey]; 00848 _actionName = [aCoder decodeObjectForKey:CPUndoManagerActionNameKey]; 00849 _currentGrouping = [aCoder decodeObjectForKey:CPUndoManagerCurrentGroupingKey]; 00850 00851 _state = CPUndoManagerNormal; 00852 00853 [self setRunLoopModes:[aCoder decodeObjectForKey:CPUndoManagerRunLoopModesKey]]; 00854 [self setGroupsByEvent:[aCoder decodeBoolForKey:CPUndoManagerGroupsByEventKey]]; 00855 } 00856 00857 return self; 00858 } 00859 00860 - (void)encodeWithCoder:(CPCoder)aCoder 00861 { 00862 [aCoder encodeObject:_redoStack forKey:CPUndoManagerRedoStackKey]; 00863 [aCoder encodeObject:_undoStack forKey:CPUndoManagerUndoStackKey]; 00864 00865 [aCoder encodeInt:_levelsOfUndo forKey:CPUndoManagerLevelsOfUndoKey]; 00866 [aCoder encodeObject:_actionName forKey:CPUndoManagerActionNameKey]; 00867 00868 [aCoder encodeObject:_currentGrouping forKey:CPUndoManagerCurrentGroupingKey]; 00869 00870 [aCoder encodeObject:_runLoopModes forKey:CPUndoManagerRunLoopModesKey]; 00871 [aCoder encodeBool:_groupsByEvent forKey:CPUndoManagerGroupsByEventKey]; 00872 } 00873 00874 @end 00875 00876 @implementation _CPUndoManagerProxy : CPProxy 00877 { 00878 CPUndoManager _undoManager; 00879 } 00880 00881 - (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector 00882 { 00883 return [_undoManager _methodSignatureOfPreparedTargetForSelector:aSelector]; 00884 } 00885 00886 - (void)forwardInvocation:(CPInvocation)anInvocation 00887 { 00888 [_undoManager _forwardInvocationToPreparedTarget:anInvocation]; 00889 } 00890 00891 @end