UXUndoManager
UXUndoManager is a neutral undo/redo stack with the shape of NSUndoManager.
The design rests on one idea:
An undoable change registers, at the moment it happens, the call that would put things back.
To undo, the manager invokes that call. Putting things back is itself a change, so that call registers its inverse, which becomes the redo. Undo and redo are the same machine run in opposite directions, with no separate redo bookkeeping.
#use <UXKit> // or #import "UXUndoManager.xc"The pattern
Section titled “The pattern”class Model : Object { i32 x; UXUndoManager* undo;
void setX(Object* arg) { i32 want = ((Box* ?)arg).v; undo.registerUndo(&self.setX, (Object*)Box.of(x)); // "to undo, put x back" undo.setActionName((u8*)"Change X"); x = want; }}Register before you change, because the inverse needs the old value.
This one method supports undo and redo. Undoing calls setX(oldX), which
registers setX(newX) as the redo. Redoing calls that, which registers the undo
again. You write the inverse once.
x=30 canUndo=1 canRedo=0undo x=20 canRedo=1undo x=10redo x=20Grouping: one user action, several model edits
Section titled “Grouping: one user action, several model edits”A single user action often touches the model several times and must undo as one step:
undo.beginUndoGrouping();m.setX(Box.of(100));m.setX(Box.of(200));undo.endUndoGrouping();
undo.undo(); // back to 20 — the WHOLE group, not just the last editA single registration outside an explicit group gets a group implicitly, so simple cases need no extra calls.
While a group is being undone, every re-registration goes into one new group pushed to the other stack. A multi-edit action therefore also redoes in one step.
Topics
Section titled “Topics”registerUndo · setActionName · undo · redo · canUndo · canRedo · undoActionName · redoActionName · beginUndoGrouping · endUndoGrouping · removeAllActions · disableUndoRegistration · enableUndoRegistration · isUndoRegistrationEnabled · isUndoing · isRedoing
registerUndo
Section titled “registerUndo”void registerUndo(callback block void(Object* arg), Object* arg)Records the call that reverses the change about to be made. The argument is
passed as an Object*, so a value type needs boxing.
The callback never owns its receiver. A model that has been deallocated leaves a dead entry and is not resurrected.
setActionName
Section titled “setActionName”void setActionName(u8* name)Names the group for a menu item: “Undo Change X”.
void undo(void)Invokes the top undo group. Its re-registrations become the redo.
void redo(void)The reverse of undo: invokes the top redo group, and its re-registrations
become the undo.
canUndo / canRedo
Section titled “canUndo / canRedo”bool canUndo(void)bool canRedo(void)Whether each stack is non-empty. A menu item’s enabled state reads these.
undoActionName / redoActionName
Section titled “undoActionName / redoActionName”u8* undoActionName(void) // 0 when the stack is emptyu8* redoActionName(void)The name of the group that would run, for the menu title.
beginUndoGrouping / endUndoGrouping
Section titled “beginUndoGrouping / endUndoGrouping”void beginUndoGrouping(void)void endUndoGrouping(void)Explicit grouping. See above.
removeAllActions
Section titled “removeAllActions”void removeAllActions(void)Clears both stacks. A document calls this when it is saved-as or reverted, where the old history no longer describes anything reachable.
disableUndoRegistration
Section titled “disableUndoRegistration”void disableUndoRegistration(void)void enableUndoRegistration(void)bool isUndoRegistrationEnabled(void)Suspends recording. The main use is loading a document: populating a model runs the same setters as a user edit, and without this the user could undo back to an empty document they never created.
undo.removeAllActions();undo.disableUndoRegistration();self.loadFrom(file); // same setters, no historyundo.enableUndoRegistration();// canUndo() == 0isUndoing / isRedoing
Section titled “isUndoing / isRedoing”bool isUndoing(void)bool isRedoing(void)Which direction is running, if any. A model can use this to behave differently while being reversed, for example to suppress a notification or skip a validation that only applies to user input.
Example
Section titled “Example”The full program is website/site/examples/uxkit/undo.xc, and the
doc-examples gate compiles it. Its output:
x=30 canUndo=1 canRedo=0after undo x=20 canRedo=1after undo x=10after redo x=20grouped to x=200one undo takes the WHOLE group back to x=20cleared: canUndo=0after a disabled edit x=999 canUndo=0 (nothing registered)The last two lines clear the stacks first: canUndo == 0 only shows that the
disabled edit registered nothing if the stacks were empty beforehand.
Conforms to
Section titled “Conforms to”- Inherits
Object
See also
Section titled “See also”UXUndoGroupandUXUndoOp: what the stacks holdUXMenuItem: where the action names and enabled states go- Bound methods and callbacks: what
&self.setXis