Undo Redo thinking
( This thinking has led to most of the current command processor etc. implementation, in order to achieve this behaviour)
Wish List:
- When drawing a polygon ( or any other canvas object ), adding points should be undoable. But when the polygon is finished and other new canvas objects have already bin added, the undo of the polygon should not be point by point but at once.
- When editing a polygon ( or any other canvas object ), dragging an edit handle, should be undoable while busy editing, but after editing is finished Undo should be directly as before the editing. So all indiviual "dragHandle commands" should be Undoable at once, as soon as editing of object has ended.
- Recording of actions should be possible, so if a polygon is drawn, adding more points should result in an "addpoint x y" like command, which can be recorded and replayed. This command/function should fit in the scripting language in use. Those commands can be recorded to a file, or stored as history in a commandline window.
- A commandline window ( using the current scripting language as command initiator. ), can to be used at the same time as the normal interactif graphic editing using the mouse, and also dialog changes like style and layers. So when drawing a polygon, adding points with the mouse, as well as from the command line using command "addpoint x y", should be possible. Of course again using the the current command line syntax/language.
- Changes to several independant documents in the docview framework ( Multi View/Document ), should be undoable independently from each other. Not un-doable actions in one document should not block others from being undone.
- When editing/changing a connected canvasobject, the rewiring should also be undoable, either per wire rerouted or for the object inclusive its rerouted wires at once.
- when running scripts from a file, undo data needs *not* be created. But when running single lines in this scripting language, from a commandline, Undo data should/might be generated, or at least optional.
- When doing/undoing commands, modeless dialogs and other interested objects, need to be informed/notified. If a certain command effects the data that a modeless dialog needs to display, it will be done when it is notified. As an example, the style dialog will get its style updated to the object that is currently chosen to be edited. Style changes using the commandline should lead to the dialog showing the same style.
- Canvas Objects which depend upon other objects, should also respect the Undo system. So markers on curves, having a connected showmarker, should be automatically be updated.
- if editing of an object is canceled, the state of the object should be restored to the situation before the editing started. The undo stack should remove the editong actions sofar.
Questions
- What format of commands that are used internal, is best. and how are they presented to the outside? Via a translation into a script language that is used by the aplication? Currently wxArt2D uses command classes, each command submit, and its paramters are part of the command class instance. Each submit command submitted, generated an wxEVT_DO event, which is distributed. This event can be intercepted by a translation class, which translates command class instances into its command line equivalant.
- Is internal commandprocessors, plus internal undo stack the best solution, or is implementation in a script language itself better? Internal command processor is best, since having several document being undo able seperately, requires several undo stacks/commanprocessors. For each Document one, and very likely a top/central commandprocessor for the whole.
Effects on design pattern of Undo/Redo and scripting language integration
a2dCanvasObject are set into editing mode by the editing tool. A Clone of the object displayed on top of all other objects is edited, and via commands the changes are redirected to the original object. So when dragging the whole object, the orginal will be updated after a "MouseLeft Up", to reflect the transform.
- The a2dCanvasObject being edited is responsible for implementing editing features for that specific object and not the editing tool. The editing tool just selects a a2dCanvasObject to be edited, and after that redirects events directly to that object, until the object edited tells that editing is finished.
The original a2dCanvasObject can either be changed directly via class member calls of a2dCanavsObject, in which case the undo data needs to be put on the stack by wrapping them into commands, but without actually Submitting those commands. Therefore the a2dCommand::Do member is not used in such a strategy. But since we already wrap the undo data into a2dCommands anyway, we might as well Submit those commands via the documentcommandprocessor. This way the same change is achieved, while automatically the undo stack is filled. Doing it via documentcommandprocessor->Submit has the advantage that certain context settings, like current style to be set for a new object, can automatically be set.
- Although editing of a a2dCanvasObject will lead to a whole range of commands. Like for example when editing an Arc object, Dragging the object will be one command, but changing one angle of the arc will be another command. So after finishing editing an a2dCanvasObject, several commands have been stored. For Undo-ing the action, one wants to be able to directly go to the state just before the editing of the object started. Optional one wants to be able to undo all individual steps. Therefore the commandprocessor needs a feature to group sets of commands. And be able to undo such a group at once. For that i group commands are used. All commands within a Group can be undone at once. If this is really happening depends on a setting in the the commandprocessor. So during editing of an object, the individual commands in the group can be undone, but when editing is finished, the commandprocessor closses the current command group, and return to the parent of that command group. The command is not any longer a stack, but a tree of command. And the command is active with in a branch of the tree. Undoing can be complete branches with nested command groups.
- Editing an object will result in commands being submitted to the documentcommandprocessor by the editing copy of the canvasobject that is being edited. The original object is changed via the document its commandprocessor. This makes sure Undo data is stored. Submitting a command will result in sending a wxEVT_DO event around. To understand this, imagine a new rectangle being drawn with the rectangle drawing tool. The new rectangle object is created and initiated, and directly added to the document via the documentcommandprocessor as a temporary object. This renders it as if it belong to the document. But its width and height are still zero at this moment. Only after the mouse is released inside the drawing tool, the actually end width and height will be know. That is also the right moment to create an ascii string to be recorded to a file/history dialog. Something like "add_rectangle 46 36 200 100". And therefore this is the moment to remove the temporary tool object, and replace it using a command class submit, with the real object. To bad this is currently not the case. The object is add using a command right away, and its width and heigth are modified while drawing. During polygon drawing this exactly what we need, since we want to be able to undo drawn vertexes while still busy drawing the polygon. But for recording the end result as one commandline phrase is complex this way, because it is not a single command to record.
- As soon an object is being added to a document, all redraws of the document will be automatic. So zooming while drawing is in progress, is just a simple matter of redrawing the document on that view. Stacking/pushing several drawing tools is also possible, even if a first pushed tool has not finished yet. When a tool is poped again from the tools stack, the first one that is now on the stack will become active, and drawing can continue with that tools.
- Another reason for not to being able to have each internal command result in an ascii command to be recorded, is the rewiring of connected objects. The wire objects are changed using command classes, but only te end result is important. Recording the rewiring commands when a connected object is moved, makes no sence, only its end position. Moving a connected object will normally automatically result in rewiring.
Because of the above issues, i decide to have the recording of a command ( in order to replay ), independent, and not always synchronious with the actual a2dCommands that are submitted. For record there is the a2dDocviewGlobals->RecordF() function, this function will generated a a2dComEvent event of type wxEVT_RECORD. Classes used for macro recording and/or displaying a history list, will intercept this event to do there job.
But this brings up another issue, the command string sent to the a2dDocviewGlobals->RecordF(), is in principle a fixed one. What will be the script language used in such a command? Or should this command not be an assci string at all, but only a keyword plus a list of arguments, e.g. of a2dObject properties. Using that approach the commandstructure can be translated by the class intercepting the wxEVT_RECORD event, into an appropriate call for any script language in use. In both cases, it looks like there is a need for having a communication system for commands, which is scripting language independent. And which are only translated to the wanted scripting language when needed. This system can be used not only for recording of command, but also for submitting commands via a commandprocessor. In the last situation, the commandprocessor will wrap the command into a a2dCommand structure for undo, when undo is needed.
As an example:
ExecuteF( TRUE, "setproperty M_layer %d %s", m_currentlayer , "uint16" ); M_layer", a_int as become m_currentlayer, and canvasobject is the current active a2dCanvasObject, e.g. the one being edited. As can be seen the ExecuteF has advantages.
The first urgument tells to create Undo data, the rest is for creating the ascii command.
This will eventually result in the next within the command processor.
Submit(new a2dCanvas_SetCanvasProperty( name, a_int, canvasobject ), withUndo );
Here name is "
For the moment i have decided to use the simplest possible command language, which parses a command ascii string, and which executes a function in the command processor. The same command string can be used for recording purposes. So now there are two ways to submit commands to the command processor. The first is the old one, in which you wrap the command into a wxCommand class, and that is submitted to the processor. The second is via a simple ascii string which is sent to the processor, where it is evaluated, and ventually calls a function in the processor. This function is responsible for creating the undo data for this command string ( if possible). I have found this string command system very useable, but there is also a need for the structure plus a list of arguments wrapped into properties. The property will define the type of argument.
In other words we want to sent an command + arguments around in a C++ fashion, without ascii command string parsing.
One consequence of the above "in between command language", is that calls directly to a script language engine from within the code are not desirable, in case one once to have Undo data. Undo information for commands is not maintained within the scripting lanaguge, but only within the internal commandprocessors. One for each document, and a central commandprocessor. The calls coming from the scripting language via a commandline interface, which should generate undo data, should first be translated into the internal command language/structure, and then be submitted to the internal command processors. Of course if other calls work directly on the application, this is not a problem, but they will not generate undo info. I think the best is to do Undoable calls from within a scripting engine, via the internal central commandprocessor. This makes the generation of undo data possible, but not required.
If the external scripting language calls function in the libarary directly, like adding a point to a polygon by calling AddPoint( polygon, x, y ), undo information can only be achieved when the undo stack is filled within the C++ polygon::AddPoint( x, y ) member. It would only generate the commands, but not submit them, instead only store them on the undo stack. I think this would be a very confusing strategy, since the polygon::AddPoint( x, y ) is also used in situation where undo data is not wanted. The approach via the commandprocessor is likely better in general, unless no undo is needed, or makes no sence.
- In relation to point 3, the editing/drawing of a polygon (wxCanvasobject in general), and at the same time typing an "addpoint x y" command, is not a problem. Both types of ( entry with mouse versus ascii commands ), will lead to the same effect, and undo data is stored in the same commandprocessor. Still "addpoint x y" is an ascii command defined in the internal command communication system/language. The external scripting language would not understand those internal command strings. Therefore they need to be translated into proper calls for the extrenal scripting language. For example "addpoint 34 35" might be translated into addpoint( 34, 35 ). When callin in the scripting language the addpoint( 34, 35 ) function later on, the function is translated back to the internal command structure, in order to sent it to the commandprocessor. This last translation is not to an inetrnal ascii string command, but into a keyword plus a list of wxNamedProperty objects, which directly define the type of arguments. This last is a good interface towards the internal command processors, and prevent a second parsing round.
- A translation/wrapping of the script language functions, in fact are translation to internal commands here, and not directly to internal C++ member functions. The wrappers for the script language undoable statements to the internal commands structures to be submitted, are taking the function name and its argments, and wrap those arguments into properties. That is enough to give it as a call to the internal command processor.
- All data changes that can be undone, and which are achieved via a scripting language command line, should go via the central command structure.
- Non Undoable calls from the command language are free to wrap what the want. So script function calls to traverse a a2dCanvasDocument hierarchy, are direct calls to a2dCanvasObject members function. For all functions via the command processor undo must be optional.
- Each document has its own documentcommandprocessor, which records all changes to that document. This asures that Undo can be done independently for each document. A central commandprocessor, can be used to store commands that are not storing undo data which effects a document its contents. Therefore zooming into a a2dCanvas view, will result in a command to the central commandprocessor. And undoing that command is independent of the commands that store document editing operation etc. In fact many commands to the central commandprocessor are redirected to the documentcommandprocessor of the currently active document, which is used by with the currently active view.
- Connected canvasobjects that are edited, will start rewiring the connected wireObject ( via its pins), which are a2dCanvasObjects themselfs. So the wireobject will report/execute the changes as wxCommands to the documentcommandprocessor. What exactly is sent to the documentcommandprocessor, is not know by the a2dCanvasObject that is currenly being edited. It is more a consequence of that editing. And the object that is edited, might not even be aware of the extra changes in the document caused by changes to itself. The following problems arrises. A drag of a canvasobject result in sending a command, but that same command will result in rewiring the object being edited by the command. Therefore more sub commands will be generated within/while the drag command its Do() member is in progress. To make sure those sub commands are stored on the undo stack in the right order, the first main command needs to be stored before the next "subcommand" is comming. The documentcommandprocessor Submit was changed to achieve this. Therefore now the command is stored before it has succeeded, if in the end the command was wrong, it will be removed again. New sub commands will now always be added to the end of the undo stack, and after the actually "parent" drag command.
Commands stored in a file, that are normally undoable, will by default not be undoable, when read back from that file. So macro like commands stored a subcommands in a file, are not undoable, unless the SetUndo command/setting is set to TRUE. One can switch to recording undo information or not. Now one can in principle store the contents of a whole document into a file as scripting commands. Of course when reading such a script, undo information is not required. But when typing commands on the commandline engine of the application, one by will by default have undo information stored. Even if the command itself is a script, and might set the storing of undo information to false. Every new command from the commandline should respect that last setting, but an easy way to switch from recording to non recording undo info is needed in the command line dialog too.
- When doing or undoing a comand a a2dCommonEvent event of type wxEVT_DO/wxEVT_UNDO is sent to the central event distributer. All registrated classes will recieve this event. In the handler for the event, they can test if the command effect the data that they maintain, and modify it to the current state of the application. A simular mechanism is used to sent wxCanvasEvent's around from within the commandprocessors. This event can also be intercepted like the command event. The above event are for instance used in the modeless style dialog, to show the current active style. Instead of having the style dialog in idle time poll the commandProcessor its current style, it is no notified when the style actually did change.
- Dependency of a2dCanvasObject on other wxCanavsObject is an integral part of a a2dCanvasDocument. Si if a curve its vertexes are dragged, so will the marker be replaced and its x,y position will be update in a showmarker object. Here it becomes clear that when draging a curve vertex, and one wants to be able to undo this action, that dependent a2dCanvasObject will automatically follow. To restore the old version of dependent objects, extra undo data is not required in most situation. But if the relation is as complex as in the rewiring of complete objects, extra commands can/will be added to the command stack.
- Canceling editing or drawing of an object, should result in unwinding all commands on the undo stack to the situation before the editing started. Therefore the editing should start with a groupmarkercommand, to easily undo all subcommands that were part of the canceled editing session.
Current status
Currently a docviewcommandprocessor is the central place where commands are handled in wxArt2D. For that i have currently a simple one string commandparser for ascii commands. The command calls can choose to store or not store the command data. So undo is already organized within wxArt2d ( two types of commandprocessors one central for docview and for each document one more. ) I want lua on top of that, and it should use a function replacement for my internal commands. And that is all Lua will ever know about wxArt2D, commands. All the rest in the lua script is for the library not of interest, since only commands which will modify my documents etc. need to be undo-able. If there is a for loop, all within the foreloop that result in changes to documents, will be undoable.
In principle all undoable calls from the external scripting language are lead via the commandprocessor. In there the right Undo data the proper undo data is generated. This might be even a range of seperate commands. I will have a special command for this situation where a script call, internal generates extra commands. They will be grouped, in order to undo them as a group at once.
So in my case Lua etc. calls which are Undoable, will be resulting in a range of undo data stored inside wxCommands, and on a C++ commandStack.
Why? I can edit interactif, and that needs to result in undo data, and it does not make sence to play those as commands via the Lua engine, an internal command stack and processor is best here. I see lua more as an automation language ( for all non undoable calls from e.g. a file ), but some calls typed from a command line can be undoable. Making this undo optional, will make it possible to use the same call from the command line as in a complete script from a file.