|
|
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
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.