ftp.nice.ch/pub/next/graphics/vector/Wood.0.72.s.tar.gz#/Wood/Sources/UndoManager.m

This is UndoManager.m in view mode; [Download] [Up]

{\rtf0\ansi{\fonttbl\f0\fmodern Courier;\f1\fswiss Helvetica;\f2\fmodern Ohlfs;}
\paperw18140
\paperh13720
\margl120
\margr20
{\colortbl;\red0\green0\blue255;\red255\green0\blue0;\red0\green0\blue0;}
\pard\tx540\tx1080\tx1620\tx2160\tx2700\tx3240\tx3780\tx4320\tx4860\tx5400\tx5940\tx6480\tx7020\tx7560\tx8100\tx8640\tx9180\tx9720\tx10260\tx10800\tx11340\f0\b0\i0\ulnone\fs24\fc0\cf0 #import "UndoManager.h"\
\

\gray108\fc1\cf1 /******************************************************************************\

\b UndoManager\

\b0 \
	The UndoManager object is used to handle undo and redo on a document basis in an application. Objects in the application register changes to themselves by sending an "undo" message to an undoManager instance from within an undoable method.For Example:\
	\
	- setRadius:(float)value\
	\{\

\b 		// Give undoManager the target for this event, then send undo message.\

\b0 		[[undoManager setUndoTarget:self] setRadius:radius];\
	\

\b 		// Set the new value and return\

\b0 		radius = value; return self;\
	\}\
	\
	The UndoManager keeps a group (or list) of records for each "undoable" change. Each record contains a pointer to an object, an action for that object to perform and the arguments for that action. Undo is achieved by executing the target/action information in these undo records.\
	\
	To add several records to an undo group, enclose the registration messages between UndoManager's -beginUndoRecordGrouping and -endUndoRecordGrouping methods. Undo messages that are not surrounded by these methods are added to the undoList in a group by themselves.\
\
	Also since sometimes changes should be ignored (ie. all of the discrete movements of a shape in a drag loop should not be undoable, just the final overall move), the messages -disableUndoRegistration and -reenableUndoRegistration can be sent to control which events are accepted into an undo record. For example, you might call [undoManager disableUndoRegistration] on a mouseDown: to prevent methods that register themselves with the undoManager to register each mouse drag, then you would call [undoManager reenableUndoRegistration] on mouseUp: with another call that would undo the drag loop (ie, [undoManager moveSelectedObjectsTo:oldPoint]).\
\
	The UndoManager allows the User to set the number of levels of undo to maintain (-setLevelsOfUndo:). When the number of records exceeds this amount, the excess is removed from the end of the list.\
	\
	The UndoManager can be instructed to free target or argument data when records are removed from the undoList (whether they fall off the end of the list or are executed during an undo command). This is achieved by the -freeUndoTarget and -freeUndoArgs methods. Additionally the -copyUndoArgs method will copy any pointers that are registered with the next message (it is then assumed that the UndoManager will free them).\
	\
	To trigger an Undo record, simply call -undo. This places the UndoManager into a "redo" state. All messages that are registered during an Undo are kept in a "redo" list. Thus the undo manager provides for redo as well. Redo can be triggered with the -redo message. The redo list is emptied each time a new record is added to the undo list (because, redo should only work if there were previous undos).\
\
	Clients of the undoManager can be notified when an undo is executed by adding themselves to the UndoManager's delegate list(-addUndoDelegate:). The only notification messages are -undoManagerWillUndo: and -undoManagerDidUndo: (called before and after an undo or redo). This notification is useful to do perform functions that all undo events may require, like calling -display.\
\
Written by: Jeff Martin (jmartin@bozell.com)\
You may freely copy, distribute and reuse the code in this example.  \
Don't even talk to me about warranties.\
******************************************************************************/\

\gray0\fc0\cf0 \

\gray300\fc2\cf2 #ifdef hppa\

\gray0\fc0\cf0 #define TARGET(UndoRec)	(*(id *)(UndoRec->args+UndoRec->argSize-4))\
#define SELECTOR(UndoRec)	(*(SEL *)(UndoRec->args+UndoRec->argSize-8))\

\gray300\fc2\cf2 #else\

\gray0\fc0\cf0 #define TARGET(UndoRec)	(*(id *)UndoRec->args)\
#define SELECTOR(UndoRec)	(*(SEL *)(UndoRec->args+4))\

\gray300\fc2\cf2 #endif\

\gray0\fc0\cf0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\fc0\cf0 #ifdef sparc\
# undef BITS(type)\
#endif
\pard\tx540\tx1080\tx1620\tx2160\tx2700\tx3240\tx3780\tx4320\tx4860\tx5400\tx5940\tx6480\tx7020\tx7560\tx8100\tx8640\tx9180\tx9720\tx10260\tx10800\tx11340\f0\fc0\cf0 \
\
#define BIT(x) (((unsigned int)1)<<(x))\
#define BITS(from, to) ((((BIT(to) - 1)<<1) + 1) & !(BIT(from) - 1))\
\
id undoManager = NULL;\
\

\b @implementation UndoManager\

\b0 \
- 
\b init
\b0 \
\{\
	[super init];\
	undoList = [[Storage allocFromZone:[self zone]] initCount:0\
		elementSize: sizeof(RecordGroup) description:@encode(RecordGroup)];\
	redoList = [[Storage allocFromZone:[self zone]] initCount:0\
		elementSize: sizeof(RecordGroup) description:@encode(RecordGroup)];\
	delegateList = [[List allocFromZone:[self zone]] init];\
	disabled = NO; undoing = NO; redoing = NO;\
	levelsOfUndo = 42;\
	target = NULL; freeArgsMask = copyArgsMask = 0;\
	undoName = NULL;\
	redoName = NULL;\
	return self;\
\}\
\
\

\gray108\fc1\cf1 /******************************************************************************\
	- beginUndoRecordGrouping, - endUndoRecordGrouping\
	\
	These methods allow the client of the UndoManager to place several undo records in one undo "event". Undo messages that are received while not surrounded by begin/end undoRecordGrouping are placed into the undo list individually.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b beginUndoRecordGrouping
\b0 \
\{\
	RecordGroup recordGroup, *ptr;\
	\
	if((!recordGrouping++) && (!disabled)) \{\
\

\gray300\fc2\cf2 		// Get the appropriate list\

\gray0\fc0\cf0 		id list = undoing? redoList :undoList;\
		\

\gray300\fc2\cf2 		// Add new recordGroup to the list if there is not an empty one there\
		
\gray0\fc0\cf0 ptr = (RecordGroup *)[list elementAt:0];
\gray300\fc2\cf2 \

\gray0\fc0\cf0 		if(!ptr || [ptr->recordList count]) \{\
			recordGroup.recordList = [[Storage allocFromZone:[self zone]] initCount:0 \
				elementSize: sizeof(UndoRecord) description:@encode(UndoRecord)];\
			if(undoName)\{\
				recordGroup.undoName = undoName;\
				undoName = NULL;\
			\} else\
				recordGroup.undoName = NULL;\
			if(redoName)\{\
				recordGroup.redoName = redoName;\
				redoName = NULL;\
			\} else\
				recordGroup.redoName = NULL;\
			[list insertElement:(void *)&recordGroup at:0];\
				\

\gray300\fc2\cf2 			// If list has more elements than levelsOfUndo, remove one from end\

\gray0\fc0\cf0 			if([list count] > levelsOfUndo)\{\
				 ptr = (RecordGroup *)[list elementAt:[list count] - 1];\
				 [self discardRecordGroup:ptr];\
				 [list removeElementAt:[list count]-1];\
			\}\
		\}\
	\}\
	return self;\
\}\
\
- 
\b endUndoRecordGrouping
\b0  \{ recordGrouping--; return self; \}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- disableUndoRegistration, - reenableUndoRegistration\
	\
	These methods allow the undomanager to ignore events. This is useful in a case like mouse dragging, where the application only wants the final move to be undoable and not all of the intermediate drags. These should be strategically placed in blocks and can be nested.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b disableUndoRegistration
\b0  \{ disabled++; return self; \}\
- 
\b reenableUndoRegistration
\b0  \{ disabled--; return self; \}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- setUndoTarget:object\
	\
	This method sets the target of following undo records. This is necessary because the sender cannot be gleaned from the forward method. Returns self.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b setUndoTarget
\b0 :
\i object
\i0  \{ target = object; return disabled? NULL : self; \}\
\
\
// Setting the current name of RecordGroup\
- setUndoName:(const char *)aName\
\{\
	RecordGroup *ptr;\
	id list;\
	\
	if(undoName)\
		NX_FREE(undoName);\
	undoName = NXCopyStringBufferFromZone(aName,[self zone]);\
	if(undoing || redoing)\{\
		list = undoing? redoList :undoList;\
		ptr = (RecordGroup *)[list elementAt:0];\
		if(ptr)\{\
			ptr->undoName = undoName;\
			undoName = NULL;\
		\}\
	\}\
	return self;\
\}\
\
- setRedoName:(const char *)aName\
\{\
	RecordGroup *ptr;\
	id list;\
	\
	if(redoName)\
		NX_FREE(redoName);\
	redoName = NXCopyStringBufferFromZone(aName,[self zone]);\
	if(undoing || redoing)\{\
		list = undoing? redoList :undoList;\
		ptr = (RecordGroup *)[list elementAt:0];\
		if(ptr)\{\
			ptr->redoName = redoName;\
			redoName = NULL;\
		\}\
	\}\
	return self;\
\}\
\
// Querying the Undo/Redo status for menu validation\
- (const char *)lastUndoName\
\{\
	RecordGroup *ptr = (RecordGroup *)[undoList elementAt:0];\
	\
	if(ptr)\
		return ptr->undoName;\
	else\
		return NULL;\
\}\
\
- (const char *)lastRedoName\
\{\
	RecordGroup *ptr = (RecordGroup *)[redoList elementAt:0];\
	\
	if(ptr)\
		return ptr->redoName;\
	else\
		return NULL;\
\}\
\
\

\gray108\fc1\cf1 /******************************************************************************\
	- freeUndoTarget, - freeUndoArgs, - freeUndoArgAt:(int)pos\
\
	These methods specify whether the UndoManager should free the target and/or its args when an undo record is executed or when it exceeds the number of levels of undo. These methods set bits in the freeArgsMask representing the argument position. Position 0 is the target. Position 1-15 represent the first 15 arguments to the method. Bits 0-15 in the freeArgsMask represent args to be freed when the record is discarded, while Bits 16-31 represent args to be freed when the record is executed.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b freeUndoTarget
\b0  \{ freeArgsMask |= BIT(0)|BIT(16); return self; \}\
- 
\b freeUndoArgs
\b0  \{ freeArgsMask |= ((-1) & (!BIT(0)) & (!BIT(16))); return self; \}\
- 
\b freeUndoArgAt
\b0 :(int)pos \{ freeArgsMask |= (BIT(pos)|BIT(pos+16));return self; \}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- freeUndoTargetOnRecordDiscard, - freeUndoArgsOnRecordDiscard,\
	- freeUndoArgOnRecordDiscardAt:(int)pos\
\
	These methods specify whether the UndoManager should free the target and/or its args when an undo record exceeds the number of levels of undo. These methods set bits 0 - 15 in the freeArgsMask representing the argument position. Position 0 is the target. Position 1-15 represent the first 15 arguments to the method.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b freeUndoTargetOnRecordDiscard
\b0  \{ freeArgsMask |= BIT(0); return self; \}\
- 
\b freeUndoArgsOnRecordDiscard
\b0  \{ freeArgsMask |= BITS(1,15); return self; \}\
- 
\b freeUndoArgOnRecordDiscardAt
\b0 :(int)pos\
\{ freeArgsMask |= BIT(pos); return self; \}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- freeUndoTargetOnRecordExecute, - freeUndoArgsOnRecordExecute,\
	- freeUndoArgOnRecordExecuteAt:(int)pos\
\
	These methods specify whether the UndoManager should free the target and/or its args when an undo record is executed. These methods set bits 16 - 31 in the freeArgsMask representing the argument position. Position 16 is the target. Position 17-31 represent the first 15 arguments to the method.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b freeUndoTargetOnRecordExecute 
\b0 \{ freeArgsMask |= BIT(16); return self; \}\
- 
\b freeUndoArgsOnRecordExecute
\b0  \{ freeArgsMask |= BITS(17,31); return self; \}\
- 
\b freeUndoArgOnRecordExecuteAt
\b0 :(int)pos\
\{ freeArgsMask |= BIT(pos+16); return self; \}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- copyUndoArgs, - copyUndoArgsAt:(int)pos\
\
	copyUndoArgs and copyUndoArgsAt: tell the UndoManager to copy pointer arguments(like objects or strings) for convenience. It is assumed that the UndoManager will free them when the undoRecord is freed. Returns self.\
	copyUndoArgsFreeOnDiscard and copyUndoArgsFreeOnExecute are just like copyUndoArgs but they specify whether to free the args when the record is discarded or when it is executed. They all Return self.\
******************************************************************************/\

\gray0\fc3\cf3 - 
\b copyUndoArgs
\b0  \{ copyArgsMask = -1
\fc0\cf0 ;
\fc3\cf3  [self freeUndoArgs]; return self; \}\
- 
\b copyUndoArgAt
\b0 :(int)
\i pos
\i0 \
\{ copyArgsMask |= 
\fc0\cf0 BIT(pos);
\fc3\cf3  [self freeUndoArgAt:pos]; return self; \}\
\
- 
\b copyUndoArgsFreeOnDiscard
\b0 \
\{ [self copyUndoArgs]
\fc0\cf0 ;
\fc3\cf3  [self freeUndoArgsOnRecordDiscard]; return self; \}\
- 
\b copyUndoArgFreeOnDiscardAt
\b0 :(int)
\i pos
\i0 \
\{ [self copyUndoArgAt:pos]
\fc0\cf0 ;
\fc3\cf3 [self freeUndoArgOnRecordDiscardAt:pos];return self;\}\
\
- 
\b copyUndoArgsFreeOnExecute
\b0 \
\{ [self copyUndoArgs]
\fc0\cf0 ;
\fc3\cf3  [self freeUndoArgsOnRecordDiscard]; return self; \}\
- 
\b copyUndoArgFreeOnExecuteAt
\b0 :(int)
\i pos
\i0 \
\{ [self copyUndoArgAt:pos]
\fc0\cf0 ;
\fc3\cf3 [self freeUndoArgOnRecordDiscardAt:pos];return self;\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- copyUndoArgsForRecord:(UndoRecord *)undoRecord\
\
	copyUndoArgsForRecord is used internally and does the actual work of copying the pointers.\
******************************************************************************/\

\gray0\fc3\cf3 - 
\b copyUndoArgsForRecord
\b0 :(UndoRecord *)
\i undoRecord
\i0 \

\fc0\cf0 \{\
\
	Method	method = class_getInstanceMethod([TARGET(undoRecord) class],\
		SELECTOR(undoRecord));\
	int argCount = method_getNumberOfArguments(method);\
	int i, offset;\
	char *type;\
\

\gray300\fc2\cf2 	// Copy the target if requested\

\gray0\fc0\cf0 	if(copyArgsMask & BIT(0)) TARGET(undoRecord) =\
		[TARGET(undoRecord) copyFromZone:[self zone]];\
	\

\gray300\fc2\cf2 	// Copy each argument that the client requested\

\gray0\fc0\cf0 	for(i=2; i<argCount; i++) if(copyArgsMask & BIT(i-1)) \{\

\gray300\fc2\cf2 \
		// Get argument type and offset\

\gray0\fc0\cf0 		method_getArgumentInfo(method, i, &type, &offset);\

\gray300\fc2\cf2 #ifdef hppa\

\gray0\fc0\cf0 		offset += undoRecord->argSize;\

\gray300\fc2\cf2 #endif
\gray0\fc0\cf0 \
		\

\gray300\fc2\cf2 		// If argument is a string copy with NXCopyStringBufferFromZone\

\gray0\fc0\cf0 		if(!strncmp(type,"*",1)) \{\
			char **stringPtr = (char **)(undoRecord->args+offset);\
			*stringPtr = (*stringPtr)? \
				NXCopyStringBufferFromZone(*stringPtr, [self zone]) : NULL;\
		\}\
		\

\gray300\fc2\cf2 		// If argument is an object copy with -copyFromZone:\

\gray0\fc0\cf0 		if(!strncmp(type,"@",1)) \{\
			id *objectPtr = (id *)(undoRecord->args+offset);\
			*objectPtr = [*objectPtr copyFromZone:[self zone]];			\
		\}\
	\}\
	return self;\
\}\

\gray108\fc1\cf1 \
/******************************************************************************\
	- forward:(SEL)aSelector :(marg_list)argFrame\
	\
	This method is how events are registered with the Undo Manager. When the UndoManager receives a message that it doesn't respond to, it assumes that this is an undo event and associates it with the current target. This undoRecord is then added to the undoList.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b forward
\b0 :(SEL)
\i aSelector
\i0  :(marg_list)
\i argFrame
\i0 \
\{\
	RecordGroup *ptr;\
	UndoRecord undoRecord;\
	\

\gray300\fc2\cf2 	// Register the event if we are not disabled and there is a current target\

\gray0\fc0\cf0 	if((!disabled) && target) \{\
\

\gray300\fc2\cf2 		// Allocate a new undo record and set its fields
\gray0\fc0\cf0 \
		int argSize = method_getSizeOfArguments(\
			class_getInstanceMethod([target class], aSelector));\
		undoRecord.freeArgsMask = freeArgsMask;\
		undoRecord.argSize = argSize;\

\gray300\fc2\cf2 		\
		// Make a copy of the arguements (copy deep if requested)\

\gray0\fc0\cf0 		undoRecord.args = NXZoneMalloc([self zone], argSize);\

\gray300\fc2\cf2 #ifdef hppa\

\gray0\fc0\cf0 		bcopy(argFrame - argSize, undoRecord.args, argSize);\

\gray300\fc2\cf2 #else\

\gray0\fc0\cf0 		bcopy(argFrame, undoRecord.args, argSize);\

\gray300\fc2\cf2 #endif\

\gray0\fc0\cf0 		TARGET((&undoRecord)) = target;\

\fc3\cf3 		if(copyArgsMask) [self copyUndoArgsForRecord:&undoRecord];\

\fc0\cf0 \

\gray300\fc2\cf2 		// Do implicit recordGrouping if needed\

\gray0\fc3\cf3 		if(!recordGrouping) [[self beginUndoRecordGrouping]\
			endUndoRecordGrouping];\
			\

\fc0\cf0 		if(undoing)\{\
			ptr = (RecordGroup *)[redoList elementAt:0];\
			[ptr->recordList addElement:(void *)&undoRecord];\
		\} else \{\
			ptr = (RecordGroup *)[undoList elementAt:0];\
			[ptr->recordList addElement:(void *)&undoRecord];\
		\}\
		\

\gray300\fc2\cf2 		// Make sure that redo list is empty if in normal mode.\

\gray0\fc0\cf0 		if(!undoing && !redoing) \
			while([redoList count])\{\
				ptr = (RecordGroup *)[redoList elementAt:0];\
				[self discardRecordGroup:ptr];\
				[redoList removeElementAt:0];\
			\}\
	\}\
	\

\gray300\fc2\cf2 	// Reset registration masks\

\gray0\fc0\cf0 	freeArgsMask = 0; copyArgsMask = 0;\
	return self;\
\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- undo:sender\
	\
	This message actually triggers the UndoManager to take the top undo record and send out all of the events. The record is then freed.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b undo
\b0 :
\i sender
\i0 \
\{\
	RecordGroup recordGroup,*ptr;\
	\
	ptr = (RecordGroup *)[undoList elementAt:0];\

\gray300\fc2\cf2 	// Get the last list that actually had undo records in it\

\gray0\fc0\cf0 	while(ptr && ![ptr->recordList count])\{\
		NX_FREE(ptr->undoName);\
		NX_FREE(ptr->redoName); \
		[undoList removeElementAt:0];\
		ptr = (RecordGroup *)[undoList elementAt:0];\
	\}\
	recordGroup.undoName = ptr->undoName;\
	recordGroup.redoName = ptr->redoName;\
	recordGroup.recordList = ptr->recordList;\
	[undoList removeElementAt:0];\
	
\gray300\fc2\cf2 // Beep if there was no record group
\gray0\fc0\cf0 \
 	if(!ptr) NXBeep();\

\gray300\fc2\cf2 \
	// Otherwise set redo flag, execute record group, reset redo flag & return\

\gray0\fc3\cf3 	else \{ 
\fc0\cf0 undoing = YES; [self executeRecordGroup:&recordGroup]; undoing = NO; \}\
	return self;\
\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- redo\
	\
	This message actually triggers the UndoManager to take the top redo record and send out all of the events. The record is then freed.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b redo
\b0 :
\i sender
\i0 \
\{\
	RecordGroup recordGroup,*ptr;\
	\
	ptr = (RecordGroup *)[redoList elementAt:0];\

\gray300\fc2\cf2 	// Get the last list that actually had undo records in it\

\gray0\fc0\cf0 	while(ptr && ![ptr->recordList count])\{\
		NX_FREE(ptr->undoName);\
		NX_FREE(ptr->redoName); \
		[redoList removeElementAt:0];\
		ptr = (RecordGroup *)[redoList elementAt:0];\
	\}\
	recordGroup.undoName = ptr->undoName;\
	recordGroup.redoName = ptr->redoName;\
	recordGroup.recordList = ptr->recordList;\
	[redoList removeElementAt:0];\
	
\gray300\fc2\cf2 // Beep if there was no record group
\gray0\fc0\cf0 \
 	if(!ptr) NXBeep();\

\gray300\fc2\cf2 \
	// Otherwise set redo flag, execute record group, reset redo flag & return\

\gray0\fc3\cf3 	else \{ 
\fc0\cf0 redoing = YES; [self executeRecordGroup:&recordGroup]; redoing = NO; \}\
	return self;\
\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- (int)levelsOfUndo, - setLevelsOfUndo:(int)value\
	\
	These methods allow programmatic manipulation for the levels of undo that an UndoManager maintains.\
******************************************************************************/\

\gray0\fc0\cf0 - (int)
\b levelsOfUndo
\b0  \{ return levelsOfUndo; \}\
- 
\b setLevelsOfUndo
\b0 :(int)
\i value
\i0  \{ levelsOfUndo = value; return self; \}\
\
\

\gray108\fc1\cf1 /******************************************************************************\
	- addUndoDelegate:object, - removeUndoDelegate:object\
	\
	These methods add and remove objects that are to receive undo notification. The two notifications that are currently supported are undoManagerWillUndo: and undoManagerDidUndo (sent before and after an Undo or Redo).\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b addUndoDelegate
\b0 :
\i object
\i0  \{ [delegateList addObject:object]; return self; \}\
- 
\b removeUndoDelegate
\b0 :
\i object
\i0  \{ [delegateList removeObject:object]; return self; \}\
- 
\b sendNotification
\b0 :(SEL)
\i action
\i0 \
\{\
	int i; for(i=0; i<[delegateList count]; i++)\
		if([[delegateList objectAt:i] respondsTo:action])\
			[[delegateList objectAt:i] perform:action with:self];\
	return self;\
\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- discardRecordGroup:recordGroup;\
	- executeRecordGroup:recordGroup;\
	- freeUndoRecord:(UndoRecord *)undoRecord;\
	\
	These methods free the individual records in a record group. discardRecordGroup: calls freeUndoRecord:withMask: with the discard part of the freeArgsMask while executeRecordGroup: executes the records in the record group then calls freeUndoRecord:withMask: with the execute part of the the freeArgsMask.\
	freeUndoRecord walks through the record arguments free those that the freeMask requests to be freed. Returns self.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b discardRecordGroup
\b0 :
\i (RecordGroup *)group
\i0 \
\{\
	int i;\
	\
	NX_FREE(group->undoName);\
	NX_FREE(group->redoName);\
	
\gray300\fc2\cf2 // Free the records in the list, then the list\

\gray0\fc0\cf0 	for(i=0; i<[group->recordList count]; i++)\
		[self freeUndoRecord:[group->recordList elementAt:i] withFreeMask:freeArgsMask];\
	[group->recordList free];\
	return self;\
\}\
\
- 
\b executeRecordGroup
\b0 :
\i (RecordGroup *)group
\i0 \
\{\
	int i;\
	\

\gray300\fc2\cf2 	// Send WillUndo notification\

\gray0\fc0\cf0 	[self sendNotification:@selector(undoManagerWillUndo:)];\

\gray300\fc2\cf2 \
	// Start undo record grouping, execute & free records & stop record grouping\

\gray0\fc0\cf0 	[self beginUndoRecordGrouping];\
	for(i=0; i<[group->recordList count]; i++) \{\
		UndoRecord *rec = [group->recordList elementAt:i];\

\gray300\fc2\cf2 #ifdef hppa\

\gray0\fc0\cf0 		[TARGET(rec) performv:SELECTOR(rec) :rec->args + rec->argSize];\

\gray300\fc2\cf2 #else\

\gray0\fc0\cf0 		[TARGET(rec) performv:SELECTOR(rec) :rec->args];\

\gray300\fc2\cf2 #endif\

\gray0\fc0\cf0 		[self freeUndoRecord:rec withFreeMask:(rec->freeArgsMask>>16)];\
	\}\
	[self endUndoRecordGrouping];\
\

\gray300\fc2\cf2 	// Send DidUndo notification and return\

\gray0\fc0\cf0 	[self sendNotification:@selector(undoManagerDidUndo:)];\
	return self;\
\}\
\
- 
\b freeUndoRecord
\b0 :(UndoRecord *)
\i undoRecord
\i0  
\b withFreeMask
\b0 :(int)free
\i Mask
\i0 \
\{\
    Method	method = class_getInstanceMethod([TARGET(undoRecord) class],\
    	SELECTOR(undoRecord));\
	int argCount = method_getNumberOfArguments(method);\
	int i, offset;\
	char *type;\
	\

\gray300\fc2\cf2 	// Free the target if requested\

\gray0\fc0\cf0 	if(freeMask & BIT(0)) [TARGET(undoRecord) free];\
	\

\gray300\fc2\cf2 	// Free anything that the args point to if requested\

\gray0\fc0\cf0 	for(i=2; i<argCount; i++) if(freeMask & BIT(i-1)) \{\
	\
			method_getArgumentInfo(method, i, &type, &offset);\

\gray300\fc2\cf2 #ifdef hppa\

\gray0\fc0\cf0 			offset += undoRecord->argSize;\

\gray300\fc2\cf2 #endif
\gray0\fc0\cf0 \
			\

\gray300\fc2\cf2 		// Free any strings or pointers with free() function\

\gray0\fc0\cf0 		if(!strncmp(type,"*",1)) free(*(char **)(undoRecord->args+offset));\
			\

\gray300\fc2\cf2 		// Free any objects with -free method\

\gray0\fc0\cf0 		else if(!strncmp(type,"@",1)) [*(id *)(undoRecord->args+offset) free];			\
	\}\
\

\gray300\fc2\cf2 	// Free the args\

\gray0\fc0\cf0 	free(undoRecord->args);\

\gray300\fc2\cf2 \

\gray0\fc0\cf0 	return self;\
\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- emptyUndoManager\
	\
	emptyUndoManager removes all of the recordGroups from the undoList and redoList. An UndoManager client might do this when a document is saved. Returns self.\
******************************************************************************/\

\gray0\fc3\cf3 - 
\b emptyUndoManager
\b0 \
\{\
	RecordGroup *ptr;\
	\

\fc0\cf0 	while([undoList count])\{\
		ptr = (RecordGroup *)[undoList elementAt:0];\
		[self discardRecordGroup:ptr];\
		[undoList removeElementAt:0];\
	\}\
	while([redoList count])\{\
		ptr = (RecordGroup *)[redoList elementAt:0];\
		[self discardRecordGroup:ptr];\
		[redoList removeElementAt:0];\
	\}\

\fc3\cf3 	return self;\
\}\
\

\gray108\fc1\cf1 /******************************************************************************\
	- free\
	\
	Frees each undo group in the undo and redo lists then it frees the undo and redo lists.\
******************************************************************************/\

\gray0\fc0\cf0 - 
\b free
\b0 \
\{\
	[self emptyUndoManager];\
	NX_FREE(undoName);\
	NX_FREE(redoName);\
	[undoList free]; [redoList free]; [delegateList free];\
	return [super free];\
\}\
\

\b @end
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.