Annotation of Examples/AppKit/Draw/undo.subproj/ChangeManager.m, revision 1.1.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.