home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / undo.subproj / ChangeManager.m < prev    next >
Encoding:
Text File  |  1992-02-10  |  10.7 KB  |  426 lines

  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.     _someChangesForgotte@    bNO;
  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 canU@    c) {
  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:@    d 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@    e 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
  426.