home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Draw / undo.subproj / ChangeManager.m < prev    next >
Text File  |  1996-08-12  |  10KB  |  393 lines

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