-
Notifications
You must be signed in to change notification settings - Fork 14
Helper undo_manager
The Undo Manager is an API that manages an undo/redo queue for your application.
To add the Undo Manager helper to your application add it under the helpers
section of the app.yml
file:
# app.yml
helpers:
- folder: ./helpers
- filename: "[[FRAMEWORK]]/helpers/undo_manager"
An undo stack is a queue of undo actions. An application can have one or more undo stacks depending on the need. For example, an application that supports multiple document editors would use a different undo stack for each document editor window.
Each undo stack has an undo queue which consists of the undo actions that can be undone. When an undo action is undone it is moved to the redo queue.
Each undo stack has a redo queue which consists of the undo actions that can be redone. When an undo action is redone it is moved to the undo queue.
An undo action represents an action that the user performed that can be undone or redone. Undo actions are of a specific type and can have a memento associated with them.
A memento array is the array containing the information necessary to reverse an undo action (or at least part of it). Each memento array has at a minimum a type
key and one other key. A simple memento array containing an id would look like this:
local tMementoA
put "delete" into tMementoA["type"]
put 35 into tMementoA["id"]
Each memento array must have a type
key. The type
tells the undoStoreMementos
message what type of memento to store.
By default the Undo Manager defines three types that you can assign to a memento array.
create
delete
update
Each type has an inverse type. When calling undoUndo
and an undo action is processed the action is moved from the undo queue to the redo queue and the memento array action is changed to the inverse type. This allows the action to be redone. By default the following inverse types are defined:
Type | Inverse Type |
---|---|
create | delete |
delete | create |
update | update |
Each undo action has a "mementos" property which is a numerically indexed array. Each numeric element is a memento array. The undo action mementos
key must have at least one memento array. A simple mementos
key with one memento array would be constructed thusly:
local tMementosA, tMementoA
put "delete" into tMementoA["type"]
put 35 into tMementoA["id"]
put tMementoA into tMementosA[1]
The Undo Manager helper provides an API that coordinates the storing and processing of undo and redo actions in your application. The helper DOES NOT provide the actual logic for performing the undo or redo operation. That job falls upon you the developer as undo actions will be specific to your application.
What the helper does do is manage undo actions. Here is a description of the life of an undo action:
- When the user performs an action the developer adds an undo action to an undo stack. The undo action has a bare bones
mementos
key associated with it. - The
undoStoreMementos
message is dispatched tothe target
when an undo action is added. In theundoStoreMementos
handler the developer generates the memento(s) necessary to reverse the action. The Undo Manager will store these memento(s) for use later on. - When the user chooses to undo an action and
undoUndo
is called the Undo Manager dispatchesundoRestoreMementos
tothe target
. The message handler is responsible for using the undo action's mementos to restore the application to the correct state. The undo action is moved to the redo queue and each memento is assigned the inverse of it's currenttype
. E.g. if the memento's type iscreate
then it would be set todelete
.
Here are some diagrams showing what happens when adding an undo action and undoing an action.
Each time the user performs an action that can be undone the developer adds an undo action to an undo stack. The undo action has one or more mementos associated with it. These mementos are stored in the mementos
key of an undo action. The mementos
key is a numerically indexed array where each index is a memento
array which has the information necessary to reverse the action. For example, if you created a new object then you probably need one memento array that has the information needed to delete that object if the user decides to undo the action.
Mementos for an undo action will be populated primarily in the undoStoreMementos
handler. However, you do need to provide some basic information about the undo action mementos when you call undoAddAction
.
An undo action is added to the undo queue using the undoAddAction
command. The undo action can then be processed when the user undoes or redoes the action. Each time the the user undoes or redoes the action the undo action memento(s) needs to be updated. Because of this need to update the memento(s) throughout the life of the undo action the memento(s) is updated using the undoStoreMementos
callback.
The undoStoreMementos
callback is first called by undoAddAction
. The callback will subsequently be called when calling undoUndo
and undoRedo
. The mementos array that you pass to undoAddAction
should contain the minimum amount of information necessary for undoStoreMementos
callback to store the actual memento. For example, you could just pass in a reference to a LiveCode control if you are changing properties on the control. The undoStoreMementos
callback would use the control reference to store the current state of the properties.
When you call undoUndo
or undoRedo
then the mementos stored for the most recent undo action will be used to reverse the action in the application. The undoRestoreMementos
will be dispatched to the target
and the memento array(s) can be used to update the application state.
Now that you have a foundational understanding of how the Undo Manager works, let's look at an example.
Here is an example of how you would add a single memento array to an undo action to an undo stack.
# Application creates new content. New content id is placed in tContentIds variable
...
# Create the memento array used to reverse the undo action. Since the code is creating content the
# memento array will use the `create` type – you will undo a creation action. Store the minimum amount of information
# that the `undoStoreMementos` callback will need to fill in the memento.
# In this case the callback just needs the id of the content that was created.
local tMementoA, tMementosA
put "create" into tMementoA["type"]
put tContentIds into tMementoA["ids"]
put tMementoA into tMementosA[1]
# Add the undo action with the memento to the undo stack. The label in the Undo menu is "Undo Add Content".
undoAddAction "document 1", "Add Content", tMementosA
Now that an undo action has been added to the "document 1" undo stack, let's look at the code that fills in the memento array. The memento is filled in based on the action being performed. As you look at the code below keep in mind that after a user undoes an action the user then has to be able to redo the action. The undoStoreMementos
callback will need to be able to create mementos for both undo and redo requests. It has the following signature:
command undoStoreMementos pUndoStack, @xMementosA, pUndoing
end undoStoreMementos
-
pUndoStack
is the first parameter you passed toundoAddAction
-
xMementosA
is a numerically indexed array of mementos associated with the undo action. -
pUndoing
is a boolean value. If true thenundoStoreMementos
is being triggered by a call toundoUndo
orundoRedo
. If false then it is being triggered by a call toundoAddAction
.
Also note that the xMementosA
parameter is passed in by reference. The callback will be updating the memento array(s) and the Undo Manager will store any modifications made to the parameter. xMementosA
will be passed to undoRestoreMementos
later on.
Important! Be aware of when you are calling undoAddAction
in your code. If the user is deleting an object and you delete the object before calling undoAddAction
then your application may not be able to generate a memento because the object doesn't exist. In such cases call undoAddAction
before you delete the object. If an error occurs while deleting the object you can use undoRemoveLatest
to remove the undo action from the undo stack.
command undoStoreMementos pUndoStack, @xMementosA, pUndoing
local i
repeat with i = 1 to the number of elements of xMementosA
switch xMementosA[i]["type"]
case "create"
# Add keys to memento array necessary to undo the create action
_StoreMementoToUndoCreateAction xMementosA[i], pUndoing
break
case "delete"
# Add keys to memento array that will be required to undo the delete action
_StoreMementoToUndoDeleteAction xMementosA[i], pUndoing
break
case "update"
# Add keys to memento array that will allow the code to restore properties.
_StoreMementoToUndoUpdateAction xMementosA[i], pUndoing
break
end switch
end repeat
end undoStoreMementos
private command _StoreMementoToUndoCreateAction @xMementoA, pUndoing
# TODO: Add keys to xMementoA necessary to delete the content created
end _StoreMementoToUndoCreateAction
private command _StoreMementoToUndoDeleteAction @xMementoA, pUndoing
# TODO: Add keys to xMementoA that will be required to create the content that was deleted
end _StoreMementoToUndoDeleteAction
private command _StoreMementoToUndoUpdateAction @xMementoA, pUndoing
# TODO: Add keys to xMementoA that will allow the code to restore properties.
end _StoreMementoToUndoUpdateAction
command undoRestoreMementos pUndoStack, @xMementosA
# Lock screen so that all UI updates appear at once
lock screen
# Use the information in the mementos to reverse the action and update the UI.
repeat with i = 1 to the number of elements of xMementosA
switch xMementosA[i]["type"]
case "create"
# Undo the create action
...
break
case "delete"
# Undo the delete action
...
break
case "update"
# Undo the update action
...
break
end switch
end repeat
unlock screen
end undoRestoreMementos
Of interest: When moving an undo action from the undo queue to the redo queue (or vice versa) the Undo Manager reverses the sequence of elements in the mementos
array. For example, if you pass in two memento arrays when creating an undo action then key "2" will become key "1" and key "1" will become key "2" when the undo action is moved to the redo queue.
The Undo Manager has built in support for edits made during a single field editing session. The undo stack covers any edits made in a field after openField and before closeField/exitField. To turn this feature on call undoRegisterEditFieldType
when your app starts up. After calling undoRegisterEditFieldType
any calls to handlers such as undoCanUndo
and undoUndo
will automatically target the "edit field" undo stack if the user is currently editing text in a field. Once focus leaves the field then calls to handlers such as undoCanUndo
will target the specified undo stack.
When undoing changes in a field the textChanged
message is sent for each memento that is processed in undoRestoreMementos
(there should only be one memento). In addition, after all mementos have been processed the EditFieldMementosRestored
message is sent to the field. This is a good place to sync up the UI if your app requires it.
Type: command
Syntax: undoRestoreMementos <pUndoStack>,<pMementosA>
Summary: Restores mementos for a field edit.
Type: command
Syntax: undoStoreMementos <pUndoStack>,<pMementosA>
Summary: Stores a memento for a field prior to changes being made.
Type: command
Syntax: undoAddAction <pUndoStack>,<pActionName>,<pMementosA>
Summary: Adds an undo action to an undo stack.
Returns: The array representing the undo action that was created.
Parameters:
Name | Description |
---|---|
pUndoStack |
The name of the undo stack to associate the action with. If empty then the undo stack is "default". |
pActionName |
The name of the undo action. This is what you will display in the Edit > Undo/Redo menus. |
pMementosA |
A numerically indexed array of memento arrays for this action. |
Description:
Use this handler to add undo actions to an undo stack in your application.
local tMementoA, tMementosA
put tId into tMementoA["id"]
put tIndex into tMementoA["index"]
put tMementoA into tMementosA[1]
undoAddAction "document 1", "Create thing", tMementosA
Type: function
Syntax: undoCanRedo(<pUndoStack>)
Summary: Returns true if there is a redo action.
Returns: Boolean
Parameters:
Name | Description |
---|---|
pUndoStack |
The name of the undo stack to target. Leave empty for "default" |
Type: function
Syntax: undoCanUndo(<pUndoStack>)
Summary: Returns true if there is an undo action.
Returns: Boolean
Parameters:
Name | Description |
---|---|
pUndoStack |
The name of the undo stack to target. Leave empty for "default" |
Type: command
Syntax: undoCleanupStack <pUndoStack>
Summary: Removes all undo entries for a particular owner.
Returns: empty
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Type: function
Syntax: undoGetMaxUndos()
Summary: Returns the maximum number of undo actions that are stored in an undo stack.
Returns: Number of undo entries to store
Type: function
Syntax: undoGetNextUndoInStack(<pUndoStack>)
Summary: Returns the undo structure for the next undo in the stack.
Returns: Array or empty
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Description:
Use this for comparisons to determine if a new undo action should be added to the stack (e.g. actions that repeat but you only want 1 undo for).
Type: function
Syntax: undoGetTargetForStackCallbacks(<pUndoStack>)
Summary: Returns the target that messages are sent to for an undo stack.
Returns: Object reference
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack of interest. |
Type: function
Syntax: undoPrintLog()
Summary: Prints the undo log array. Use for troublehsooting.
Returns: text representation of internal array
Type: command
Syntax: undoRedo <pUndoStack>
Summary: Performs an redo operation for the last redo added to the stack.
Returns: empty or "can't redo"
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Type: function
Syntax: undoRedoActionName(<pUndoStack>)
Summary: Returns the next redo action name in a stack.
Returns: String
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Description:
The action name that was assigned when calling undoAddAction.
Type: command
Syntax: undoRegisterEditFieldType
Summary: Registers the "edit field" undo stack for use while a user is editing text in a field.
Returns: empty
Description:
Thie handler registers the "edit field" undo stack and inserts a frontscript into the message path. The frontscript monitors edits made in a field while it has focus. Undo/redo affects typing done before focus is removed from the field.
Type: command
Syntax: undoRegisterType <pType>,<pInverseType>
Summary: Registers an undo type with the Undo Manager.
Returns: empty
Parameters:
Name | Description |
---|---|
pType |
The type to register. |
pInverseType |
When calling undoUndo the type of the undo action will be set to |
this value. For example, if pType is "create" then this might be | |
"delete". Leave empty if there is no inverse type. If this value is | |
different then pType then make sure you register this type as well. |
Description:
When adding an undo entry to an undo stack, you specify a type. The type helps the application know how to undo or redo the action. Each type has callbacks that will be triggered when that type of undo entry needs to be undone or redone.
Type: command
Syntax: undoRemoveLatest <pUndoStack>
Summary: Removes the last added undo action from the undo stack.
Returns: empty
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Type: command
Syntax: undoReset <pUndoStack>
Summary: Resets an undo stack.
Returns: empty
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Description:
Cleanup callbacks will be sent for any undo actions in the stack.
Type: command
Syntax: undoSetMaxUndos <pMax>
Summary: Sets the maximum number of undo actions that will be saved in an undo stack.
Returns: empty
Parameters:
Name | Description |
---|---|
pMax |
The maximum number of undo entries to store. |
Type: command
Syntax: undoSetTargetForStackCallbacks <pUndoStack>,<pTarget>
Summary: Sets the target for undo type callbacks for an undo stack.
Returns: empty
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
pTarget |
The target object for messages. |
Description:
If a callback target control is set for an undo stack then any undo type which doesn't specify a contorl will send to the undo stack control.
Type: command
Syntax: undoUndo <pUndoStack>
Summary: Performs an undo operation for the last undo added to the stack.
Returns: empty or "can't undo"
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Type: function
Syntax: undoUndoActionName(<pUndoStack>)
Summary: Returns the next undo action name in a stack.
Returns: String
Parameters:
Name | Description |
---|---|
pUndoStack |
The undo stack to target. |
Description:
The action name that was assigned when calling undoAddAction.
Levure is an application development framework written for LiveCode.