Difference between revisions of "Framework/Article/Implementation of the Dispatch API In SFX2"

From Apache OpenOffice Wiki
Jump to: navigation, search
Line 260: Line 260:
 
void Class::ExecMethod( SfxRequest& );
 
void Class::ExecMethod( SfxRequest& );
 
</code>
 
</code>
 +
The ItemSet is used to collect the status information, the SfxRequest contains everything necessary for the execution of a slot.
 +
<br/>
 +
The svidl compiler takes the shell class name and the method name and generates an inline stub function that calls the C++ member. A pointer to each stub is generated into the slot definitions. The stubs are generated into the same header file that contains the generated arrays. Here is an example for the first slot of the interface "Document" shown above:
 +
<code>[cpp]
 +
SFX_EXEC_STUB( SfxObjectShell, ExecFile_Impl )
 +
SFX_STATE_STUB( SfxObjectShell, GetState_Impl )
 +
</code>
 +
We use function stubs here because pointer to member functions don't have a fixed size and so we can't store them inside a struct like SfxSlot. A pointer to a C function always has the same size as normal pointer on the machine.
 +
<br/>
 +
The slots in our example show that both pointers can be set or only one of them. A slot without an "Exec" function is a slot that only has a status, execution is done by another slot. A slot without a "GetState" function is always enabled and has no status. SFX automatically generates empty stub in these cases.
  
 
=== Items and UNO structs ===
 
=== Items and UNO structs ===

Revision as of 16:03, 7 February 2007

Implementation of the Dispatch API in SFX2

The Dispatch API as described in chapter 6.1.6 of the Developer's Guide is the backbone of our communication between "generic" UI elements and the document core implementations.

Traditionally SFX based components implemented this communication with a much older framework based on the so called slots and their organization into shells. While up to OpenOffice.org 1.1 our generic UI elements (menus, toolbars, etc.) still used the SFX based API directly to a large degree in OpenOffice.org 2.0 we changed this to using the Dispatch API exclusively, thus enabling us to share the UI components with other, not SFX based components. This paper describes how the old SFX implementation is matched to the DispatchProvider implementation that the component provides to the outside to make this happen.

The basic entity in the dispatch API is a command, and it's represented by a CommandURL. For simplifications we suppose that this CommandURL is a string (though the API transport it as a pre-parsed struct). All commands supported by our components have the form .uno:xxxxx for historical reasons, where xxxxx represents an internal command name.

The basic idea is that the Controller (the component used by a document to participate in the dispatch process) is able to take any CommandURL it receives in a request for a Dispatch Object and compares it with the CommandURLs in the set of commands it supports. If a match can be achieved a Dispatch Object is returned. The SFX based components charge their common Controller base class (SfxBaseController) implementation with the task of implementing this job based on the old SFX framework.

This old framework organized functionality in context that can be merged together and exposed to the outside world by a single point of contact. This contact point is an SfxDispatcher object that every SfxBaseController owns. The SfxDispatcher internally maitains a stack of objects, each representing a context. Examples for the contexts are "document, "view", "text", "table", "cell" etc. The complete stack of all contextual objects (all deriving from the common base class SfxShell) available in a particular situation represents the function set applicable to the current selection or cursor position in a document. Context changes inside the documents editing window causes pushing and poping of shell objects to and from the stack.

This is how a typical stack in Writer looks:

  • Text
  • View
  • Document
  • Frame
  • Module
  • Application

The Frame and Application contexts are there for historical reasons, we are still in the process of moving their functionality to the framework where other DispatchProviders outside of the component take over. I omitted the "Form" context here because it's somewhat confusing.

Each SfxShell owns an interface, an array of SfxSlot structs. Each slot represents a command supported by the context it's assigned to and the struct contains all necessary information that the SfxDispatcher needs to work together with the Shell object containing the slot without knowing its particular implementation. The most important part of information are the "name" of the slot that is identical with the internal command name of the dispatch command it represents and two pointers to functions inside the shell that the SFX can call to retrieve status information for the command or execute it. More about slots can be found below.

When the SfxDispatcher is asked for support of a particular command it searches for a slot with the given internal command name string with the topmost shell on the stack and the proceeding with the next one until success. This way "higher prioritized" contexts can overrule functionality from "lower prioritized" contexts. As an example, the "document" context has a generic implementation of the "Print" command, but each particular "view" context can overrule the generic behavior and define a specific way of printing.

The slot array aka interfaces are static to each shell class and so each interface is part of the data segment of the library that contains the shell code assigned to this interface. The data for these arrays needs to be predefined in soure files. Writing huge arrays into C++ source files is a very tedious work and so we use a different kind of source file that is easier to edit and have a compiler svidl that takes these files and compiles them into a generated header file containing the definition of the arrays. These idl files are located in the sdi sub folders of each SFX based project and have the extension "sdi". The generated header file contains the definitions (not only declarations) of all interfaces of the library and some "#define" magic allows to have each interface generated only once. More about this process follows below.

In OpenOffice.org 1.1 the SfxBaseController utilized its SfxDispatcher and its stack to provide a generic Dispatch object for every supported command. It gets the CommandURL in the queryDispatch call, looks for a slot on the dispatcher stack with a suitable internal command name and in case of success creates a dispatch object and returns it. There is a drawback in this approach: if the context changes (means: shell are pushed of popped) slot might appear of disappear from the stack so commands that had been supported aren't any longer or the other way around. This forces the DispatchProvider to request a complete refetching of all dispatch objects and so can be time consuming.

In OpenOffice.org 2.0 we utilize the "slot pool" of the SFX based modules. This in the entirety of all interfaces (slot arrays) the module supports. On startup all interfaces register at the module class (SfxModule base class) so it's easy to iterate through its interfaces. Now a dispatch object is returned when the slot is found anywhere in the slot pool, even if the interface is not on the stack, but in this case the dispatch object reports its slot as disabled everytime it's used. This can be determined dynamically so context changes don't need external updates, it's enough to update the internal status of the already created dispatch objects. This approach also enables some future optimizations for the representation of slot arrays that will be discussed later.

Slot Processing

In former (Pre-UNO) time the slots where not only used for command dispatching but also for the implementation of our Basic API (up to StarOffice 5.2), so the slot arrays where "real" interfaces. There are a lot residues of the in the sdi files (see below), but they aren't used anymore.

The use case explains why there are two different kinds of slots, property slots and method slots, and the difference between them can be spotted easily from the definition of a slot in the sdi file. Here are two examples, the first one describing a method slot and the second one describing a property slot:

[cpp] SfxVoidItem About SID_ABOUT () [

   /* flags: */
   AutoUpdate = FALSE,
   Cachable = Cachable,
   FastCall = FALSE,
   HasCoreId = FALSE,
   HasDialog = TRUE,
   ReadyOnlyDoc = TRUE,
   Toggle = FALSE,
   Container = FALSE,
   RecordAbsolute = FALSE,
   RecordPerSet;
   Synchron;
   /* config */
   AccelConfig = TRUE,
   MenuConfig = TRUE,
   StatusBarConfig = FALSE,
   ToolBoxConfig = TRUE,
   GroupId = GID_APPLICATION;

]

SfxBoolItem DesignerDialog SID_STYLE_DESIGNER

[

   /* flags: */
   AutoUpdate = TRUE,
   Cachable = Cachable,
   FastCall = TRUE,
   HasCoreId = FALSE,
   HasDialog = FALSE,
   ReadyOnlyDoc = FALSE,
   Toggle = FALSE,
   Container = FALSE,
   RecordAbsolute = FALSE,
   RecordPerSet;
   Synchron;
   Readyonly = FALSE,
   /* config */
   AccelConfig = TRUE,
   MenuConfig = TRUE,
   StatusBarConfig = FALSE,
   ToolBoxConfig = TRUE,
   GroupId = GID_FORMAT;

] Each slot is a block in rectangular bracket filled with attributes that is preceded by two lines that identify the slot and define its nature. The exact meaning of all the attributes is explained below, the most notable difference is seen in the second line of each block: the paranthesis of the "About" slot classify it as a method while the other one (without paranthesis) is a property. As explained above, this classification goes back to the old Basic API but besides that it's important for the implementation of macro recording that is also based on the Dispatch API (and for SFX component it's implemented using slots). "Method" and "property" slots are recorded differently.
The name in the middle of each first line of a block was used as the name of the property or method in the Basic API and today this name is the internal command name that is matched to any ".uno:xxx" CommandURLs of the Dispatch API. As an example, the slot named "Undo" is assigned to the ".uno:Undo" command. This matching requires that the internal command names have to be unique in the complete module. This uniqueness is verified by the svidl compiler that breaks in case if name clashes in a module.
The last part of each first line is the so called SlotID that in former times was the real identifier (not as today the internal command name) and it was also used inside the GUI element configuration files where today CommandURLs are used. So this SlotID had to be unique for the module at least, currently it's even globally unique but at least theoretically it is now obsolete. Inside the slot processing only the textual representation of the SlotID is relevant , not its numerical value.
Property slots have a type specified by the first part of the first line. The same data in a method slot specifies the return value of the associated method. Method slots also can gave parameters (non empty paranthesis) and a return value that could be used in the old Basic API of StarOffice, but also in internal methods of the SfxDispatcher. Strictly speaking the latter is still possible but we try to get rid of this. Here are two examples for slots with arguments, with and without return value:

[cpp] SfxVoidItem Undo SID_UNDO ( SfxUInt16Item Undo SID_UNDO ) [

   /* flags: */
   AutoUpdate = FALSE,
   Cachable = Volatile,
   FastCall = FALSE,
   HasCoreId = FALSE,
   HasDialog = FALSE,
   ReadyOnlyDoc = FALSE,
   Toggle = FALSE,
   Container = FALSE,
   RecordAbsolute = FALSE,
   RecordPerSet;
   Synchron;
   /* status */
   SlotType = SfxStringItem
   /* config */
   AccelConfig = TRUE,
   MenuConfig = TRUE,
   StatusBarConfig = FALSE,
   ToolBoxConfig = TRUE,
   GroupId = GID_EDIT;

]

SfxObjectItem Open SID_OPENDOC (SfxStringItem URL SID_FILE_NAME, SfxStringItem FilterName SID_FILTER_NAME, SfxStringItem OpenFlags SID_OPTIONS, SfxStringItem Password SID_PASSWORD, SfxStringItem FilterOptions SID_FILE_FILTEROPTIONS, SfxInt16Item Version SID_VERSION, SfxStringItem Referer SID_REFERER) [

   /* flags: */
   AutoUpdate = FALSE,
   Cachable = Cachable,
   FastCall = FALSE,
   HasCoreId = FALSE,
   HasDialog = TRUE,
   ReadyOnlyDoc = TRUE,
   Toggle = FALSE,
   Container = TRUE,
   RecordAbsolute = FALSE,
   RecordPerSet;
   Asynchron;
   /* config */
   AccelConfig = TRUE,
   MenuConfig = TRUE,
   StatusBarConfig = FALSE,
   ToolBoxConfig = TRUE,
   GroupId = GID_APPLICATION;

] As shown in the examples, property types and return values are given as Items, the whole slot "API" is based on SfxPoolItems, each identified by its ID. These IDs must be unique only in the context of the method.
Slots are used to bind their functionality to UI elements. In former times this was done by writing the SlotIDs into the UI element configuration files, today we use CommandURLs. Binding slots to UI elements (today indirectly through the Dispatch Object) means that the binding client wants to get status information from it and/or possibly wants to execute it, in most cases (means: the standard controllers) without any parameters.
The type of the status update information is given by the SlotType attribute. For property slots like "DesignerDialog" this is identical with its property type (and so this attribute is not explictly assigned in the block), for method slots it must be specified like shown in the "Undo" example. If a method slot doesn't specify a SlotType it has no status (except disabled or enabled). So in the examples "Undo" is of type String, "DesignerDialog" of type Boolean while the other slots don't have a status.
A menu controller will reflect this by showing status of the "Undo" entry as its title and setting or unsetting a check mark in front of the "DesignerDialof" entry reflecting its boolean status, a toolbar controller reflects the latter one in a pressed/unpressed state, the former in different button titles (or quick help texts that replace the button title if it's switched off). Other types of status information need speccialized controller that know how to deal with this kind of information.
All the other attributes inside the block of a slot are flags that define the behavior of the slot at runtime. Some of the attributes are contradicting (like synchron or asynchron) and so of course only one of them can be set. The table below sums them up in one line.

As already mentioned, slots are bundled to interfaces and interfaces are used by shells. SDI files know both terms. Here's an example how this looks. For clarity only a few slots are shown, the read file contains much more of them. [cpp] interface Document : Object [ Automation = FALSE ; ] { SID_CLOSEDOC [ ExecMethod = ExecFile_Impl ; StateMethod = GetState_Impl ; ] } interface OfficeDocument : Document [ Automation = FALSE ] { SID_DOC_MODIFIED [ StateMethod = GetState_Impl ; ] SID_PRINTDOC [ ExecMethod = PrintExec_Impl ; StateMethod = NoState ; ] } shell SfxObjectShell { import OfficeDocument [AUTOMATION]; SID_DOCINFO [ ExecMethod = ExecFile_Impl ; StateMethod = GetState_Impl ; ] }; The interface term is used slightly different in the sdi files that it is used by SFX, so now we use the name "slot arrays" for what the SFX itself calls "interfaces" to avoid confusion.
As a residue of the old Basic API sdi files define interfaces and shells separately, thus differentiating between slots for both Basic and UI usage and slots only used for UI purposes. This is obsolete today, but still used. Additionally sdi files define base and derived interfaces and shells in a granularity we don't need anymore, but this is also a residue of the StarBasic support. This might change in the near future. Some interfaces even contained real basic properties that are not represented by slots at all (like the "object" interface referenced in the example) but they are not used anymore.
The "shell" defined in the file references a C++ implementation class SfxObjectShell. This part of the sdi file defines the complete slot interface of this class. The "import" statement tells the svidl compiler that it should add all slots of the interface "OfficeDocument" here (and of course all slots of its "base class" interfaces).
The "Automation" attribute that is assigned to both interfaces and shells tells the svidl compiler whether it should generate a slot array definition for this interface/shell or not. We only generate arrays for shell, not for interfaces (because we don't need them anymore). In former times we also generated arrays for interfaces, and the "Automation" attribute allowed us to prevent svidl from generating them for base class interfaces also. The "Automation" attribute will become obsolete pretty soon.
Inside the interface/shell definition block slots are referenced by their SlotID. Svidl will get all attributes of the slot it needs to generate the array definition from the slot definition (that of course needs to be included and processed before svidl gets to the shell definition). In the block below this identifier (again with rectangular brackets) two additional attributes are specified: the names of one or two methods can be defined that SFX calls to get status information or execute the slot. These methods have a prescribed signature: [cpp] void Class::StateMethod( SfxItemSet& ); void Class::ExecMethod( SfxRequest& ); The ItemSet is used to collect the status information, the SfxRequest contains everything necessary for the execution of a slot.
The svidl compiler takes the shell class name and the method name and generates an inline stub function that calls the C++ member. A pointer to each stub is generated into the slot definitions. The stubs are generated into the same header file that contains the generated arrays. Here is an example for the first slot of the interface "Document" shown above: [cpp] SFX_EXEC_STUB( SfxObjectShell, ExecFile_Impl ) SFX_STATE_STUB( SfxObjectShell, GetState_Impl ) We use function stubs here because pointer to member functions don't have a fixed size and so we can't store them inside a struct like SfxSlot. A pointer to a C function always has the same size as normal pointer on the machine.
The slots in our example show that both pointers can be set or only one of them. A slot without an "Exec" function is a slot that only has a status, execution is done by another slot. A slot without a "GetState" function is always enabled and has no status. SFX automatically generates empty stub in these cases.

Items and UNO structs

The old SFX based Basic API was made up by the property and method slot defined in the sdi files. All types are specified as SfxPoolItems and this was fine for StarBasic because internally these types are used also (for Sbx classes). For recording purposes it was necessary to know the "real" types behind a particular SfxPoolItem type and especially structured items needed to be split up into basic types because StarBasic doesn't support structs.
The mapping from a SfxPoolItem to an atomar or structured type is done by using a mapping table in another sdi file. Here's an example for atomar types from the SFX module: [cpp] item void SfxVoidItem item BOOL SfxBoolItem item INT32 SfxUInt16Item item INT16 SfxInt16Item item INT32 SfxUInt32Item item INT32 SfxInt32Item item String SfxStringItem item BYTE SfxByteItem item INT16 SfxEnumItem item INT16 SfxAllEnumItem item INT16 TbxImageItem

Attribute Meaning
Cachable (Volatile) Slot status is cached, sending a new status to controllers need an Invalidate() call that forces SFX to fetch a new status. Volatile slots are not cached and SFX permanently asks for status information based on a timer.
AutoUpdate TRUE: After execution of the slot SFX automatically fetches the new status, no Invalidate() call necessary
FastCall TRUE: SFX doesn't check "enable" status before executing a slot
Toggle Execute without parameters automatically toggles the current status. Works for property slots with boolean or enum type.
HasCoreId Obsolete
HasDialog Obsolete
ReadOnlyDoc FALSE: SFX automatically disables this slot if the document is read only
Container TRUE: in case of OLE inplace editing this slot is automatically disabled by SFX if the component is the object.
FALSE: in case of OLE inplace editing this slot is automatically disabled by SFX if the component is the container.
RecordAbsolute Obsolete
RecordPerSet (RecordPerItem, NoRecord) See explanation for Recording.
Synchron (Asynchron) Asynchron: SFX execute the slot by posting a user event instead of directly calling the execute function of the slot
Readonly Obsolete
AccelConfig Slot may be offered in the configuration dialog for keyboard shortcuts
MenuConfig Slot may be offered in the configuration dialog for menus
StatusBarConfig Slot may be offered in the configuration dialog for status bar
ToolboxConfig Slot may be offered in the configuration dialog for toolbars
GroupId Assign the slot to a function group in the configuration dialogs (module is free to define categories)
ImageRotation Toolbar images are rotated in case of writing direction from right to left
ImageReflection Toolbar images are rotated in case of vertical text orientation
Personal tools