Annotation of Examples/AppKit/Draw/undo.subproj/ChangeManager.m, revision 1.1

1.1     ! root        1: #import "change.h"
        !             2: 
        !             3: /*
        !             4:  * Please refer to external reference pages for complete
        !             5:  * documentation on using the ChangeManager class.
        !             6:  */
        !             7: 
        !             8: /* 
        !             9:  * N_LEVEL_UNDO sets the maximum number of changes that the ChangeManager
        !            10:  * will keep track of. Set this to 1 to get single level undo behaviour. 
        !            11:  * Set it to a really big number if you want to offer nearly infinite undo.
        !            12:  * Be careful if you do this because unless you explicity reset the 
        !            13:  * ChangeManager from time to time, like whenever you save a document, the
        !            14:  * ChangeManager will never forget changes and will eventually chew up
        !            15:  * enourmous amounts of swapfile.
        !            16:  */
        !            17: #define        N_LEVEL_UNDO    10
        !            18: 
        !            19: /* Localization */
        !            20: 
        !            21: #define UNDO_OPERATION NXLocalStringFromTable("Operations", "Undo", NULL, "The operation of undoing the last thing the user did.")
        !            22: #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.")
        !            23: #define REDO_OPERATION NXLocalStringFromTable("Operations", "Redo", NULL, "The operation of redoing the last thing the user undid.")
        !            24: #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.")
        !            25: 
        !            26: @interface ChangeManager(PrivateMethods)
        !            27: 
        !            28: - updateMenuCell:menuCell with:(const char *)menuText;
        !            29: 
        !            30: @end
        !            31: 
        !            32: @implementation ChangeManager
        !            33: /* Methods called directly by your code */
        !            34: 
        !            35: - init
        !            36: {
        !            37:     [super init];
        !            38: 
        !            39:     _changeList = [[List alloc] initCount:N_LEVEL_UNDO];
        !            40:     _numberOfDoneChanges = 0;
        !            41:     _numberOfUndoneChanges = 0;
        !            42:     _numberOfDoneChangesAtLastClean = 0;
        !            43:     _someChangesForgotten = NO;
        !            44:     _lastChange = nil;
        !            45:     _nextChange = nil;
        !            46:     _changeInProgress = nil;
        !            47:     _changesDisabled = 0;
        !            48:     
        !            49:     return self;
        !            50: }
        !            51: 
        !            52: - free
        !            53: {
        !            54:     [self reset:self];
        !            55:     [_changeList free];
        !            56:     return [super free];
        !            57: }
        !            58: 
        !            59: - (BOOL)canUndo
        !            60: {
        !            61:     if (_lastChange == nil) {
        !            62:         return NO;
        !            63:     } else {
        !            64:        NX_ASSERT(![_lastChange changeInProgress], "Fault in Undo system: Code 1");
        !            65:        NX_ASSERT(![_lastChange disabled], "Fault in Undo system: Code 2");
        !            66:        NX_ASSERT([_lastChange hasBeenDone], "Fault in Undo system: Code 3");
        !            67:         return YES;
        !            68:     }
        !            69: }
        !            70: 
        !            71: - (BOOL)canRedo
        !            72: {
        !            73:     if (_nextChange == nil) {
        !            74:         return NO;
        !            75:     } else {
        !            76:        NX_ASSERT(![_nextChange changeInProgress], "Fault in Undo system: Code 4");
        !            77:        NX_ASSERT(![_nextChange disabled], "Fault in Undo system: Code 5");
        !            78:        NX_ASSERT(![_nextChange hasBeenDone], "Fault in Undo system: Code 6");
        !            79:         return YES;
        !            80:     }
        !            81: }
        !            82: 
        !            83: - (BOOL)isDirty
        !            84: {
        !            85:     return ((_numberOfDoneChanges != _numberOfDoneChangesAtLastClean) 
        !            86:                || _someChangesForgotten);
        !            87: }
        !            88: 
        !            89: - dirty:sender
        !            90: {
        !            91:     _someChangesForgotten = YES;
        !            92:     return self;
        !            93: }
        !            94: 
        !            95: - clean:sender
        !            96: {
        !            97:     _someChangesForgotten = NO;
        !            98:     _numberOfDoneChangesAtLastClean = _numberOfDoneChanges;
        !            99:     return self;
        !           100: }
        !           101: 
        !           102: - reset:sender
        !           103: {
        !           104:     while(_lastChange = [_changeList removeLastObject]) {
        !           105:        [_lastChange free];
        !           106:     }
        !           107:     _numberOfDoneChanges = 0;
        !           108:     _numberOfUndoneChanges = 0;
        !           109:     _numberOfDoneChangesAtLastClean = 0;
        !           110:     _someChangesForgotten = NO;
        !           111:     _lastChange = nil;
        !           112:     _nextChange = nil;
        !           113:     _changeInProgress = nil;
        !           114:     _changesDisabled = 0;
        !           115: 
        !           116:     return self;
        !           117: }
        !           118: 
        !           119: - disableChanges:sender
        !           120: /*
        !           121:  * disableChanges: and enableChanges: work as a team, incrementing and
        !           122:  * decrementing the _changesDisabled count. We use a count instead of
        !           123:  * a BOOL so that nested disables will work correctly -- the outermost
        !           124:  * disable and enable pair are the only ones that do anything.
        !           125:  */
        !           126: {
        !           127:     _changesDisabled++;
        !           128:     return self;
        !           129: }
        !           130: 
        !           131: - enableChanges:sender
        !           132: /*
        !           133:  * We're forgiving if we get an enableChanges: that doesn't match up
        !           134:  * with any previous disableChanges: call.
        !           135:  */
        !           136: {
        !           137:     if (_changesDisabled > 0)
        !           138:         _changesDisabled--;
        !           139:     return self;
        !           140: }
        !           141: 
        !           142: - undoOrRedoChange:sender
        !           143: {
        !           144:     if ([self canUndo]) {
        !           145:         [self undoChange:sender];
        !           146:     } else {
        !           147:        if ([self canRedo]) {
        !           148:            [self redoChange:sender];
        !           149:        }
        !           150:     }
        !           151:     return self;
        !           152: }
        !           153: 
        !           154: - undoChange:sender
        !           155: {
        !           156:     if ([self canUndo]) {
        !           157:        [_lastChange finishChange];
        !           158:        [self disableChanges:self];
        !           159:            [_lastChange undoChange];
        !           160:        [self enableChanges:self];
        !           161:        _nextChange = _lastChange;
        !           162:        _lastChange = nil;
        !           163:        _numberOfDoneChanges--;
        !           164:        _numberOfUndoneChanges++;
        !           165:        if (_numberOfDoneChanges > 0) {
        !           166:            _lastChange = [_changeList objectAt:(_numberOfDoneChanges - 1)];
        !           167:        }
        !           168:        [self changeWasUndone];
        !           169:     }
        !           170:     
        !           171:     return self;
        !           172: }
        !           173: 
        !           174: - redoChange:sender
        !           175: {
        !           176:     if ([self canRedo]) {
        !           177:        [self disableChanges:self];
        !           178:            [_nextChange redoChange];
        !           179:        [self enableChanges:self];
        !           180:        _lastChange = _nextChange;
        !           181:        _nextChange = nil;
        !           182:        _numberOfDoneChanges++;
        !           183:        _numberOfUndoneChanges--;
        !           184:        if (_numberOfUndoneChanges > 0) {
        !           185:            _nextChange = [_changeList objectAt:_numberOfDoneChanges];
        !           186:        }
        !           187:        [self changeWasRedone];
        !           188:     }
        !           189:     
        !           190:     return self;
        !           191: }
        !           192: 
        !           193: - (BOOL)validateCommand:menuCell
        !           194: /*
        !           195:  * See the Draw code for a good example of how validateCommand:
        !           196:  * can be used to keep the application's menu items up to date.
        !           197:  */
        !           198: {
        !           199:     SEL action;
        !           200:     BOOL canUndo, canRedo, enableMenuItem = YES;
        !           201:     char menuText[256];
        !           202: 
        !           203:     action = [menuCell action];
        !           204:     
        !           205:     if (action == @selector(undoOrRedoChange:)) {
        !           206:         enableMenuItem = NO;
        !           207:        canUndo = [self canUndo];
        !           208:        if (canUndo) {
        !           209:            sprintf(menuText, UNDO_SOMETHING_OPERATION, [_lastChange changeName]);
        !           210:            enableMenuItem = YES;
        !           211:        } else {
        !           212:            canRedo = [self canRedo];
        !           213:            if (canRedo) {
        !           214:                sprintf(menuText, REDO_SOMETHING_OPERATION, [_nextChange changeName]);
        !           215:                enableMenuItem = YES;
        !           216:            } else {
        !           217:                sprintf(menuText, UNDO_OPERATION);
        !           218:            }
        !           219:        }
        !           220:        [self updateMenuCell:menuCell with:menuText];
        !           221:     }
        !           222: 
        !           223:     if (action == @selector(undoChange:)) {
        !           224:        canUndo = [self canUndo];
        !           225:        if (!canUndo) {
        !           226:            sprintf(menuText, UNDO_OPERATION);
        !           227:        } else {
        !           228:            sprintf(menuText, UNDO_SOMETHING_OPERATION, [_lastChange changeName]);
        !           229:        }
        !           230:        [self updateMenuCell:menuCell with:menuText];
        !           231:        enableMenuItem = canUndo;
        !           232:     }
        !           233: 
        !           234:     if (action == @selector(redoChange:)) {
        !           235:        canRedo = [self canRedo];
        !           236:        if (!canRedo) {
        !           237:            sprintf(menuText, REDO_OPERATION);
        !           238:        } else {
        !           239:            sprintf(menuText, REDO_SOMETHING_OPERATION, [_nextChange changeName]);
        !           240:        }
        !           241:        [self updateMenuCell:menuCell with:menuText];
        !           242:        enableMenuItem = canRedo;
        !           243:     }
        !           244: 
        !           245:     return enableMenuItem;
        !           246: }
        !           247: 
        !           248: /* Methods called by Change           */
        !           249: /* DO NOT call these methods directly */
        !           250: 
        !           251: - changeInProgress:change
        !           252: /*
        !           253:  * The changeInProgress: and changeComplete: methods are the most
        !           254:  * complicated part of the undo framework. Their behaviour is documented 
        !           255:  * in the external reference sheets.
        !           256:  */
        !           257: {
        !           258:     if (_changesDisabled > 0) {
        !           259:        [change disable];
        !           260:        return nil;
        !           261:     } 
        !           262:     
        !           263:     if (_changeInProgress != nil) {
        !           264:        if ([_changeInProgress incorporateChange:change]) {
        !           265:            /* 
        !           266:             * The _changeInProgress will keep a pointer to this
        !           267:             * change and make use of it, but we have no further
        !           268:             * responsibility for it.
        !           269:             */
        !           270:            [change saveBeforeChange];
        !           271:            return self;
        !           272:        } else {
        !           273:            /* 
        !           274:             * The _changeInProgress has no more interest in this
        !           275:             * change than we do, so we'll just disable it.
        !           276:             */
        !           277:            [change disable];
        !           278:            return nil;
        !           279:        }
        !           280:     } 
        !           281:     
        !           282:     if (_lastChange != nil) {
        !           283:        if ([_lastChange subsumeChange:change]) {
        !           284:            /* 
        !           285:             * The _lastChange has subsumed this change and 
        !           286:             * may either make use of it or free it, but we
        !           287:             * have no further responsibility for it.
        !           288:             */
        !           289:            [change disable];
        !           290:            return nil;
        !           291:        } else {
        !           292:            /* 
        !           293:             * The _lastChange was not able to subsume this change, 
        !           294:             * so we give the _lastChange a chance to finish and then
        !           295:             * welcome this change as the new _changeInProgress.
        !           296:             */
        !           297:            [_lastChange finishChange];
        !           298:         }
        !           299:     }
        !           300: 
        !           301:     /* 
        !           302:      * This will be a new, independent change.
        !           303:      */
        !           304:     [change saveBeforeChange];
        !           305:     if (![change disabled])
        !           306:         _changeInProgress = change;
        !           307:     return self;
        !           308: }
        !           309: 
        !           310: - changeComplete:change
        !           311: /*
        !           312:  * The changeInProgress: and changeComplete: methods are the most
        !           313:  * complicated part of the undo framework. Their behaviour is documented 
        !           314:  * in the external reference sheets.
        !           315:  */
        !           316: {
        !           317:     int i;
        !           318:     
        !           319:     NX_ASSERT(![change changeInProgress], "Fault in Undo system: Code 7");
        !           320:     NX_ASSERT(![change disabled], "Fault in Undo system: Code 8");
        !           321:     NX_ASSERT([change hasBeenDone], "Fault in Undo system: Code 9");
        !           322:     if (change != _changeInProgress) {
        !           323:        /* 
        !           324:         * "Toto, I don't think we're in Kansas anymore."
        !           325:         *                              - Dorthy
        !           326:         * Actually, we come here whenever a change is 
        !           327:         * incorportated or subsumed by another change 
        !           328:         * and later executes its endChange method.
        !           329:         */
        !           330:         [change saveAfterChange];
        !           331:        return nil;
        !           332:     }
        !           333:     
        !           334:     if (_numberOfUndoneChanges > 0) {
        !           335:        NX_ASSERT(_numberOfDoneChanges != N_LEVEL_UNDO, "Fault in Undo system: Code 10");
        !           336:        /* Remove and free all undone changes */
        !           337:        for (i = (_numberOfDoneChanges + _numberOfUndoneChanges); i > _numberOfDoneChanges; i--) {
        !           338:            [[_changeList removeObjectAt:(i - 1)] free];
        !           339:        }
        !           340:        _nextChange = nil;
        !           341:        _numberOfUndoneChanges = 0;
        !           342:        if (_numberOfDoneChanges < _numberOfDoneChangesAtLastClean)
        !           343:            _someChangesForgotten = YES;
        !           344:     }
        !           345:     if (_numberOfDoneChanges == N_LEVEL_UNDO) {
        !           346:        NX_ASSERT(_numberOfUndoneChanges == 0, "Fault in Undo system: Code 11");
        !           347:        NX_ASSERT(_nextChange == nil, "Fault in Undo system: Code 12");
        !           348:        /* 
        !           349:            * The [_changeList removeObjectAt:0] call is order N.
        !           350:            * This will be slow if N_LEVEL_UNDO is large.
        !           351:            * Ideally the _changeList should be implemented as
        !           352:            * a circular queue, or List should do removeObjectAt:
        !           353:            * in a fixed time. In many applications (including
        !           354:            * Draw) doing the redisplay associated with the undo 
        !           355:            * will take MUCH longer than even an order N call to 
        !           356:            * removeObjectAt:, so it's not too important that 
        !           357:            * this be changed.
        !           358:            */
        !           359:        [[_changeList removeObjectAt:0] free];
        !           360:        _numberOfDoneChanges--;
        !           361:        _someChangesForgotten = YES;
        !           362:     }
        !           363:     [_changeList addObject:change];
        !           364:     _numberOfDoneChanges++;
        !           365: 
        !           366:     _lastChange = change;
        !           367:     _changeInProgress = nil;
        !           368: 
        !           369:     [change saveAfterChange];
        !           370:     [self changeWasDone];
        !           371: 
        !           372:     return self;
        !           373: }
        !           374: 
        !           375: /* Methods called by ChangeManager    */
        !           376: /* DO NOT call these methods directly */
        !           377: 
        !           378: - changeWasDone
        !           379: /*
        !           380:  * To be overridden 
        !           381:  */
        !           382: {
        !           383:     return self;
        !           384: }
        !           385: 
        !           386: - changeWasUndone
        !           387: /*
        !           388:  * To be overridden 
        !           389:  */
        !           390: {
        !           391:     return self;
        !           392: }
        !           393: 
        !           394: - changeWasRedone
        !           395: /*
        !           396:  * To be overridden 
        !           397:  */
        !           398: {
        !           399:     return self;
        !           400: }
        !           401: 
        !           402: /* Private Methods    */
        !           403: 
        !           404: - updateMenuCell:menuCell with:(const char *)menuText
        !           405: {
        !           406:     id editMenu, cv;
        !           407: 
        !           408:     if (strcmp([menuCell title], menuText)) {
        !           409:        cv = [menuCell controlView];
        !           410:        editMenu = [cv window];
        !           411: #ifdef SIZE_MENU_TO_FIT
        !           412:        [editMenu disableDisplay];
        !           413:            [menuCell setTitle:menuText];
        !           414:            [editMenu sizeToFit];
        !           415:        [editMenu reenableDisplay];
        !           416:        [editMenu display];
        !           417: #else
        !           418:        if (![editMenu isDisplayEnabled]) [editMenu reenableDisplay];
        !           419:        [menuCell setTitle:menuText];
        !           420: #endif
        !           421:     }
        !           422:     return self;
        !           423: }
        !           424: 
        !           425: @end

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.