This is ChangeManager.m in view mode; [Download] [Up]
#import "change.h" /* * Please refer to external reference pages for complete * documentation on using the ChangeManager class. */ /* * N_LEVEL_UNDO sets the maximum number of changes that the ChangeManager * will keep track of. Set this to 1 to get single level undo behaviour. * Set it to a really big number if you want to offer nearly infinite undo. * Be careful if you do this because unless you explicity reset the * ChangeManager from time to time, like whenever you save a document, the * ChangeManager will never forget changes and will eventually chew up * enourmous amounts of swapfile. */ #define N_LEVEL_UNDO 10 /* Localization */ #define UNDO_OPERATION NXLocalStringFromTable("Operations", "Undo", NULL, "The operation of undoing the last thing the user did.") #define UNDO_SOMETHING_OPERATION NXLocalStringFromTable("Operations", "Undo %s", NULL, "The operation of undoing the last %s operation the user did--all the entries in the Operations and TextOperations .strings files are the %s of this or Redo.") #define REDO_OPERATION NXLocalStringFromTable("Operations", "Redo", NULL, "The operation of redoing the last thing the user undid.") #define REDO_SOMETHING_OPERATION NXLocalStringFromTable("Operations", "Redo %s", NULL, "The operation of redoing the last %s operation the user undid--all the entries in the Operations and TextOperations .strings files are the %s of either this or Undo.") @interface ChangeManager(PrivateMethods) - updateMenuCell:menuCell with:(const char *)menuText; @end @implementation ChangeManager /* Methods called directly by your code */ - init { [super init]; _changeList = [[List alloc] initCount:N_LEVEL_UNDO]; _numberOfDoneChanges = 0; _numberOfUndoneChanges = 0; _numberOfDoneChangesAtLastClean = 0; _someChangesForgotten = NO; _lastChange = nil; _nextChange = nil; _changeInProgress = nil; _changesDisabled = 0; return self; } - free { [self reset:self]; [_changeList free]; return [super free]; } - (BOOL)canUndo { if (_lastChange == nil) { return NO; } else { NX_ASSERT(![_lastChange changeInProgress], "Fault in Undo system: Code 1"); NX_ASSERT(![_lastChange disabled], "Fault in Undo system: Code 2"); NX_ASSERT([_lastChange hasBeenDone], "Fault in Undo system: Code 3"); return YES; } } - (BOOL)canRedo { if (_nextChange == nil) { return NO; } else { NX_ASSERT(![_nextChange changeInProgress], "Fault in Undo system: Code 4"); NX_ASSERT(![_nextChange disabled], "Fault in Undo system: Code 5"); NX_ASSERT(![_nextChange hasBeenDone], "Fault in Undo system: Code 6"); return YES; } } - (BOOL)isDirty { return ((_numberOfDoneChanges != _numberOfDoneChangesAtLastClean) || _someChangesForgotten); } - dirty:sender { _someChangesForgotten = YES; return self; } - clean:sender { _someChangesForgotten = NO; _numberOfDoneChangesAtLastClean = _numberOfDoneChanges; return self; } - reset:sender { while(_lastChange = [_changeList removeLastObject]) { [_lastChange free]; } _numberOfDoneChanges = 0; _numberOfUndoneChanges = 0; _numberOfDoneChangesAtLastClean = 0; _someChangesForgotten = NO; _lastChange = nil; _nextChange = nil; _changeInProgress = nil; _changesDisabled = 0; return self; } - disableChanges:sender /* * disableChanges: and enableChanges: work as a team, incrementing and * decrementing the _changesDisabled count. We use a count instead of * a BOOL so that nested disables will work correctly -- the outermost * disable and enable pair are the only ones that do anything. */ { _changesDisabled++; return self; } - enableChanges:sender /* * We're forgiving if we get an enableChanges: that doesn't match up * with any previous disableChanges: call. */ { if (_changesDisabled > 0) _changesDisabled--; return self; } - undoOrRedoChange:sender { if ([self canUndo]) { [self undoChange:sender]; } else { if ([self canRedo]) { [self redoChange:sender]; } } return self; } - undoChange:sender { if ([self canUndo]) { [_lastChange finishChange]; [self disableChanges:self]; [_lastChange undoChange]; [self enableChanges:self]; _nextChange = _lastChange; _lastChange = nil; _numberOfDoneChanges--; _numberOfUndoneChanges++; if (_numberOfDoneChanges > 0) { _lastChange = [_changeList objectAt:(_numberOfDoneChanges - 1)]; } [self changeWasUndone]; } return self; } - redoChange:sender { if ([self canRedo]) { [self disableChanges:self]; [_nextChange redoChange]; [self enableChanges:self]; _lastChange = _nextChange; _nextChange = nil; _numberOfDoneChanges++; _numberOfUndoneChanges--; if (_numberOfUndoneChanges > 0) { _nextChange = [_changeList objectAt:_numberOfDoneChanges]; } [self changeWasRedone]; } return self; } - (BOOL)validateCommand:menuCell /* * See the Draw code for a good example of how validateCommand: * can be used to keep the application's menu items up to date. */ { SEL action; BOOL canUndo, canRedo, enableMenuItem = YES; char menuText[256]; action = [menuCell action]; if (action == @selector(undoOrRedoChange:)) { enableMenuItem = NO; canUndo = [self canUndo]; if (canUndo) { sprintf(menuText, UNDO_SOMETHING_OPERATION, [_lastChange changeName]); enableMenuItem = YES; } else { canRedo = [self canRedo]; if (canRedo) { sprintf(menuText, REDO_SOMETHING_OPERATION, [_nextChange changeName]); enableMenuItem = YES; } else { sprintf(menuText, UNDO_OPERATION); } } [self updateMenuCell:menuCell with:menuText]; enableMenuItem = NO; } if (action == @selector(undoChange:)) { canUndo = [self canUndo]; if (!canUndo) { sprintf(menuText, UNDO_OPERATION); } else { sprintf(menuText, UNDO_SOMETHING_OPERATION, [_lastChange changeName]); } [self updateMenuCell:menuCell with:menuText]; enableMenuItem = NO; } if (action == @selector(redoChange:)) { canRedo = [self canRedo]; if (!canRedo) { sprintf(menuText, REDO_OPERATION); } else { sprintf(menuText, REDO_SOMETHING_OPERATION, [_nextChange changeName]); } [self updateMenuCell:menuCell with:menuText]; enableMenuItem = NO; } return enableMenuItem; } /* Methods called by Change */ /* DO NOT call these methods directly */ - changeInProgress:change /* * The changeInProgress: and changeComplete: methods are the most * complicated part of the undo framework. Their behaviour is documented * in the external reference sheets. */ { if (_changesDisabled > 0) { [change disable]; return nil; } if (_changeInProgress != nil) { if ([_changeInProgress incorporateChange:change]) { /* * The _changeInProgress will keep a pointer to this * change and make use of it, but we have no further * responsibility for it. */ [change saveBeforeChange]; return self; } else { /* * The _changeInProgress has no more interest in this * change than we do, so we'll just disable it. */ [change disable]; return nil; } } if (_lastChange != nil) { if ([_lastChange subsumeChange:change]) { /* * The _lastChange has subsumed this change and * may either make use of it or free it, but we * have no further responsibility for it. */ [change disable]; return nil; } else { /* * The _lastChange was not able to subsume this change, * so we give the _lastChange a chance to finish and then * welcome this change as the new _changeInProgress. */ [_lastChange finishChange]; } } /* * This will be a new, independent change. */ [change saveBeforeChange]; if (![change disabled]) _changeInProgress = change; return self; } - changeComplete:change /* * The changeInProgress: and changeComplete: methods are the most * complicated part of the undo framework. Their behaviour is documented * in the external reference sheets. */ { int i; NX_ASSERT(![change changeInProgress], "Fault in Undo system: Code 7"); NX_ASSERT(![change disabled], "Fault in Undo system: Code 8"); NX_ASSERT([change hasBeenDone], "Fault in Undo system: Code 9"); if (change != _changeInProgress) { /* * "Toto, I don't think we're in Kansas anymore." * - Dorthy * Actually, we come here whenever a change is * incorportated or subsumed by another change * and later executes its endChange method. */ [change saveAfterChange]; return nil; } if (_numberOfUndoneChanges > 0) { NX_ASSERT(_numberOfDoneChanges != N_LEVEL_UNDO, "Fault in Undo system: Code 10"); /* Remove and free all undone changes */ for (i = (_numberOfDoneChanges + _numberOfUndoneChanges); i > _numberOfDoneChanges; i--) { [[_changeList removeObjectAt:(i - 1)] free]; } _nextChange = nil; _numberOfUndoneChanges = 0; if (_numberOfDoneChanges < _numberOfDoneChangesAtLastClean) _someChangesForgotten = YES; } if (_numberOfDoneChanges == N_LEVEL_UNDO) { NX_ASSERT(_numberOfUndoneChanges == 0, "Fault in Undo system: Code 11"); NX_ASSERT(_nextChange == nil, "Fault in Undo system: Code 12"); /* * The [_changeList removeObjectAt:0] call is order N. * This will be slow if N_LEVEL_UNDO is large. * Ideally the _changeList should be implemented as * a circular queue, or List should do removeObjectAt: * in a fixed time. In many applications (including * Draw) doing the redisplay associated with the undo * will take MUCH longer than even an order N call to * removeObjectAt:, so it's not too important that * this be changed. */ [[_changeList removeObjectAt:0] free]; _numberOfDoneChanges--; _someChangesForgotten = YES; } [_changeList addObject:change]; _numberOfDoneChanges++; _lastChange = change; _changeInProgress = nil; [change saveAfterChange]; [self changeWasDone]; return self; } /* Methods called by ChangeManager */ /* DO NOT call these methods directly */ - changeWasDone /* * To be overridden */ { return self; } - changeWasUndone /* * To be overridden */ { return self; } - changeWasRedone /* * To be overridden */ { return self; } /* Private Methods */ - updateMenuCell:menuCell with:(const char *)menuText { id editMenu, cv; if (strcmp([menuCell title], menuText)) { cv = [menuCell controlView]; editMenu = [cv window]; #ifdef SIZE_MENU_TO_FIT [editMenu disableDisplay]; [menuCell setTitle:menuText]; [editMenu sizeToFit]; [editMenu reenableDisplay]; [editMenu display]; #else if (![editMenu isDisplayEnabled]) [editMenu reenableDisplay]; [menuCell setTitle:menuText]; #endif } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.