Framework/Article/Implementation of the Dispatch API In SFX2

From Apache OpenOffice Wiki
< Framework
Revision as of 13:54, 12 January 2007 by Cd (Talk | contribs)

Jump to: navigation, search

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.

Items and UNO structs

Personal tools