command processor

Most actions from the users which result in changes to a document, are done via commands. For this there are several reasons.

Undo and Redo

A command is a class, and each command issued to make a change to documents result in a new instance being stored on the undo stack. The command stores enough information to reverse the change it did when submitted. So the command brings the document from one state into another, and it can be submitted reverse to get the previous state. All handling of commands is via a2dCommandProcessor, it stores commands submitted to it, and also implements undo and redoing those commands. In general at least every document has its own command processor. The reason for this is simple. Imagine drawing into one document, and next click on a view of a second document and start drawing there, a bit later going back to draw in the first document. One wants to be able to undo all changes in the first document, without undoing changes to the second document. To be able to do this one needs to have two separate undo stack, since commands can only be undone in reverse sequence. At the same time one likes to be able to submit commands to open files, close files etc. For this a central command processor is used. Commands issued to that, in general can not be undone, but still are very useful for scripting and recording, and communicating changes in an application.

tree of grouped commands

Commands in a command processor are stored as a sequence, but at the same time a tree. Each group of commands, can become a new branch in the tree. The leafs on the tree are the actual commands. Why is it important to be able to group commands? There are situations where one action needs to be build up using several commands. As an example: drag an object which is connected by wires to other objects. Changing the position of an object is one command, but the changing wires are rewired, and all those changes are submitted from several locations inside the code. So a command can result in extra commands being submitted, and the command itself does not know this. Now by grouping such commands, makes it possible to undo the whole set of commands at once. The commands which did place the dragged object at a new position, and the commands which resulted indirectly from wires being replaced/rerouted, can be undone as if it was just one command. If we did not have such a grouping mechanism, the user could only undo the rewiring one wire at the time, and at last undo the actual drag of the object. This is not what he expects. One the other hand when drawing a polygon, one segment at a time, the user wants to be able to undo/redo the drawing one segment at a time. As soon as the polygon is finished and some other objects are drawn, undo should result in removing the polygons at once and not segment per segment. Again the segments drawing commands are placed inside a group, and as long as drawing the polygon is in action, the commands inside the group can be undone/redone. But when the polygon is finished, the group is closed, and a next polygon draw is placed in its own group. Undoing commands at the group level here, result in undoing the whole groups at once. Undoing a group commands is nothing more the automatically take all commands in the group and undoing them one by one.

In the end we have a tree structure, a grouped command contains several commands, which them selfs can be groups again.

where and when

groups of commands

As explained above a change in a document can result in more changes to other objects. And those changes are triggered by, but unknown to the first change. And in most cases they can not even be issued as a range of commands in a single part of the code. In fact the commands for the changes are distributed in various parts of the library, and they can only be combined by grouping them up front. Grouping such distributed submitted commands, is simply a matter of opening a new command group in the command processor. The groups is started, and all subsequent changes which are submitted using commands, will become part of the group. This way it does not matter from where the commands were submitted, they will automatically form a group on the command stack. When the group is ended, later on the whole group can be undone as a whole.

groups of commands in a menu

A second use of a group is, to combine several commands in a group command first, without submitting it right away. Such a combined command can be attached to a menu. And when submitting the group command, all individual commands in the group will be submitted.

command classes with arguments

For commands a special way of adding arguments to the command are used. This makes it appear as a hash list of arguments. The advantage is that not all arguments need to be given, and the order is also not important. Here two examples of two commands which are prepared to be submitted:

   1 a2dCommand_GroupAB* command = new a2dCommand_GroupAB(     a2dCommand_GroupAB::Args().
   2         what( a2dCommand_GroupAB::BoolOperation_GroupAB ).
   3         operation( BOOL_A_SUB_B ).
   4         selectedA( true ).
   5         selectedB( true ) );

   1 a2dCommand_AskFile* command = new a2dCommand_AskFile( a2dCommand_AskFile::Args().
   2         message( _("Give Name of input Layer file") ).
   3         defaultDir( _T("%{layerFileSavePath}") ).
   4         extension( _T("*.cvg") ).
   5         fileFilter( _T("*.cvg") ).
   6         flags( wxOPEN | wxFILE_MUST_EXIST ).
   7         storeInVariable( _T("ask_file_result") ) );

As you you see, the period separates the arguments for the command. Although this does not look like standard C++, it still is. Not all commands use this way to set the arguments to the command, and writing the command class to use this system is a bit more complicated, the advantages become clear when there are many options to a command. Study the code of a2dCommand__AskFile to see how its done.

communication of command changes

When a command is issued, in general some class instance its data is changed. For example the central strored current fill style is changed, using a command line command. New objects added to the document via commands will get this new fill style. But assume at the moment the fill style is changed, the canvas object edit tool a2dObjectEditTool is active, editing a selected canvas object. Very likely you want that object to take on that new fill style. And if at the same time the a2dStyleDialog is shown, you also want that dialog to show the new fill style. To achieve that, each submitted command generates some events, a2dCommand has DistributeEvent( wxEventType eventType ) member, which is called with wxEVT_DO. The default implementation is calling a2dGeneralGlobals->GetEventDistributer()->ProcessEvent() to distribute the event to all object that registrated to receive that event. See wxArt2dEvents and a2dEventDistributer to learn more about that. The same with Undo and Redo is happening. The events are of type a2dCommandProcessorEvent and this event has a pointer to the command that was issued and placed on the command stack. A command has a2dCommandId which is in general just a member of the command itself, like a2dCommand_SetContourWidth::Id. Using this Id, it easy to test what command was submitted when intercepting the wxEVT_DO event.

Menu and commands

Menu's work best using Id's. Each Id is coupled via a static event table to a member function in a class. And from there that member function can do what is needed. When the action to be done by the menu chosen, has to be undo able, there will be one or more commands submitted in the handler for the menu. Now there are a lot of predefined commands in wxArt2D. And several wxArt2D modules extend the central command processor to add more commands. Commands classes often come with several arguments. Therefore it is not possible to uniquely couple some Id to a command class. Still for the common use of a command and its arguments, wxArt2D has predefined menu Id's. Using this id, one can very easily add menu to an application, which are directly coupled to a certain function in the command processor. For instance for the a2dEditorFrame class, menu are dynamically added using code like the next:

   1     wxMenu* file_menu = new wxMenu;
   2         AddCmdMenu( file_menu, CmdMenu_FileOpen );
   3         AddCmdMenu( file_menu, CmdMenu_FileClose );
   4         AddCmdMenu( file_menu, CmdMenu_FileSave );
   5         AddCmdMenu( file_menu, CmdMenu_FileSaveAs );
   6         AddCmdMenu( file_menu, CmdMenu_Print );

This piece of code did add some basic menu's for commands defined in the docview module. The Id defined by a a2dMenuIdItem like CmdMenu_FileOpen, already contains the menu label text and a help text. The a2dMenuIdItem is used as a template to realize a real wxMenuItem in wxWidgets. The imported thing is the Id defined by it, which is already coupled to some action/functionality in a command processor. Like for CmdMenu_FileOpen, in the docview module its central command processor it is coupled to a2dDocumentCommandProcessor::OnMenu() and from there the right commands are issued. Also notice the interception of the update event, which is keeping the menu updated according to the state of the application.

   1 A2D_BEGIN_EVENT_TABLE( a2dDocumentCommandProcessor, a2dCommandProcessor )
   2     A2D_EVT_MENU( CmdMenu_FileOpen.GetId(), a2dDocumentCommandProcessor::OnMenu )
   3     A2D_EVT_UPDATE_UI( CmdMenu_FileOpen.GetId(), a2dDocumentCommandProcessor::OnUpdateFileOpen)
   4 A2D_END_EVENT_TABLE()

The dynamic event connection is made in:

   1 void a2dDocumentFrame::AddCmdMenu( wxMenu* parentMenu, const a2dMenuIdItem& cmdId )
   2 {
   3         Connect( cmdId.GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( a2dDocumentFrame::OnCmdMenuId ) );
   4         parentMenu->Append( cmdId.GetId(), cmdId.GetLabel(), cmdId.GetHelp(), cmdId.IsCheckable() );
   5 }

And in the next function all those event are simply skipped, which results in those events to arrive in the central command processor, where they are handled.

   1 void a2dDocumentFrame::OnCmdMenuId(wxCommandEvent &event)
   2 {
   3     event.Skip();
   4 }

This approach has several advantages:

Commands in toolbars

Also for tool bars it is easy to add tools. Using the predefined a2dMenuIdItem Id's, it is a matter of adding handlers. Like in a2dEditorFrame it is done like this:

   1 void a2dEditorFrame::SetupToolbar()
   2 {
   3     m_toolbar = new wxToolBar(this,wxNewId());
   4     wxBitmap* toolbitmap = GetBitmap( _T("polygon") );
   5     AddCommandToToolbar( *toolbitmap, CmdMenu_PushTool_DrawPolygonL );
   6     delete toolbitmap;
   7 }
   8 
   9 void a2dEditorFrame::AddCommandToToolbar( wxBitmap& bitmap, const a2dMenuIdItem& cmdId )
  10 {
  11 if ( ! m_toolbar )
  12 return;
  13 Connect( cmdId.GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( a2dEditorFrame::OnCmdMenuId ) );
  14 m_toolbar->AddTool( cmdId.GetId(), cmdId.GetLabel(), bitmap, cmdId.GetHelp() );
  15 }

wxArt2D: wxArt2dCommandProcessing (last edited 2009-09-27 19:09:03 by KlaasHolwerda)