Framework/Article/Implementation of the Dispatch API In SFX2

    From The Document Foundation Wiki

    Implementation of the Dispatch API in SFX2

    The Dispatch API as described in chapter 7.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.

    Commands

    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. Examples are “.uno:Print” or “.uno:Paste”. The functionality provided by the Dispatch API is support to execute such commands and getting status information for them (enabled/disabled or explicit information if the commands represents something type, a boolean, a string etc.).

    As explained in the Developer's Guide the Dispatch API builds on the idea that such commands can be passed along a chain of Dispatch Provider objects. Every object in this chain is free to decide whether it will handle a command or wants to pass it to the next chain member. Each document window and the Frame object managing it has such a chain of Dispatch providers and at least one object in this chain belongs to the document model/view pair bound to this window. It's the so called Controller object that amongst other interfaces supports the XDispatchProvider interface.

    Controller objects in SFX2

    SFX2 based components share a common base class for their Controller object implementation, SfxBaseController. It has a queryDispatch() implementation of the XDispatchProvider interface where the received CommandURLs are compared with the CommandURLs in the set of commands the particular component supports. It uses some comparable functionality in the old SFX2 framework that also is able to execute commands and give status information about them. The main difference between the old SFX2 base framework and the UNO framework is that the latter defines commands by strings (CommandURLs) and the former does it by integer values (SlotID, see below). So the main job of the queryDispatch() implementation in the SfxBaseController is to match a commandURL to the corresponding SlotID and then proceed using the internal old SFX2 implementation.

    SFX2 organizes functionality in contexts 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. [Remark: this ownership is indirect as the real owner of the SfxDispatcher is an SfxViewFrame; but there is always a 1:1 relationship between an SfxViewFrame and an SfxBaseController.] The SfxDispatcher internally maintains a stack of objects, each representing a context. Examples for these contexts are “document”, “view”, “text”, “table”, “cell” etc. All these objects are derived from a common base class “SfxShell”. The complete stack of all contextual objects available in a particular situation, the “merged context”, represents the function set applicable to the current selection or cursor position in a document. Context changes inside the document's editing window cause pushing and popping of shell objects to and from the stack.

    This is how a typical stack in Writer looks when the cursor is in a normal text paragraph:

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

    The same stack when the cursor is in a text paragraph in a table:

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

    Another stack when a graphic is selected:

    • Graphic
    • 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.

    Slots and Interfaces

    Each SfxShell class has a static member called SfxInterface. It is an object containing an array of SfxSlot structs. Each “slot” represents a command supported by the context (shell) it is assigned to. So the complete SfxInterface defines the functionality an instance of the particular SfxShell class using this interface provides.

    The SfxSlot struct

    The “slot” struct contains all necessary information that the SfxDispatcher needs to work together with the Shell object containing the slot. The slot contains function pointers that the dispatcher can call to either execute the command represented by the slot or get status information about it. Thus the dispatcher doesn't need to know the concrete implementation classes of the shells, it can work with them through their base class (SfxShell) interface.

    Other important parts of the SfxSlot struct are the “name” of the slot and the numerical ID of it, its so called SlotID. Slot names have been chosen so that they match the part after the colon in “.uno:...” CommandURLs. Mapping between CommandURLs and SlotIDs to then need a simple lookup in all SfxInterfaces of the SfxShells on the shell stack of the SfxDispatcher. 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 starting with the top most shell on the stack and then 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 SfxInterface class

    The slot arrays aka SfxInterfaces are static to each shell class and so each interface is a 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 source 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 below.

    Slots, interfaces, shells, dispatchers

    In OOo1.x 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: shells are pushed or popped) slots might appear or 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 OOo2.x we utilize the “slot pool” of the SFX based modules. This is the entirety of all “interfaces” (slot arrays) the module supports. On startup all interfaces register at the module class (SfxModule base class) so it is 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 slots as disabled everytime it is 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.

    Slot definitions

    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:

    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 parenthesis of the "About" slot classify it as a method while the other one (without parenthesis) 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:

    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 specialized 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 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.

    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

    Interface definitions

    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.

    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:

    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:

    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.

    Inside the block defining the functions theoretically all slot attributes of the referenced slot can be overwritten and sometimes this can be found. This should be seen as exceptional cases and it might happen in the near future that this overwriting will become forbidden for more and more attributes. The benefit of this is very limited but the risk of dangerous inconsistencies is high.

    Here's an exerpt of the generated header file that belongs to the SID_CLOSEDOC slot shown above:

    SFX_SLOTMAP_ARG(SfxObjectShell)
    {
    // Slot Nr. 0 : 5502
        SFX_NEW_SLOT_ARG( SfxObjectShell,SID_SAVEASDOC,SID_SAVEASDOC,GID_DOCUMENT,
                    0,&aSfxObjectShellSlots_Impl[1] /* Offset Next*/,
                    SFX_STUB_PTR(SfxObjectShell,ExecFile_Impl),SFX_STUB_PTR(SfxObjectShell,GetState_Impl),
                    SFX_SLOT_CACHABLE|SFX_SLOT_SYNCHRON|SFX_SLOT_RECORDPERSET|SFX_SLOT_HASDIALOG|SFX_SLOT_MENUCONFIG|
                    SFX_SLOT_TOOLBOXCONFIG|SFX_SLOT_ACCELCONFIG|SFX_SLOT_CONTAINER|SFX_SLOT_READONLYDOC|0,
                    0,
                    SfxStringItem,
                    0/*Offset*/, 9/*Count*/,".SaveAs",SFX_SLOT_METHOD|0,"SaveAs" ),
    // Slot Nr.1 : 5503
        SFX_NEW_SLOT_ARG( SfxObjectShell,SID_CLOSEDOC,SID_CLOSEDOC,GID_DOCUMENT,
                    0,&aSfxObjectShellSlots_Impl[3] /* Offset Next*/,
                    SFX_STUB_PTR(SfxObjectShell,ExecFile_Impl),SFX_STUB_PTR(SfxObjectShell,GetState_Impl),
                    SFX_SLOT_CACHABLE|SFX_SLOT_ASYNCHRON|SFX_SLOT_RECORDPERSET|SFX_SLOT_MENUCONFIG|
                    SFX_SLOT_TOOLBOXCONFIG|SFX_SLOT_ACCELCONFIG|SFX_SLOT_CONTAINER|SFX_SLOT_READONLYDOC|0,
                    0,
                    SfxStringItem,
                    9/*Offset*/, 2/*Count*/,".CloseDoc",SFX_SLOT_METHOD|0,"CloseDoc" ),
    // Slot Nr.2 : 5504
        SFX_NEW_SLOT_ARG( SfxObjectShell,SID_PRINTDOC,SID_PRINTDOC,GID_DOCUMENT,
                    0,&aSfxObjectShellSlots_Impl[2] /* Offset Next*/,
                    SFX_STUB_PTR(SfxObjectShell,ExecFile_Impl),SFX_STUB_PTR_NONE,
                    SFX_SLOT_CACHABLE|SFX_SLOT_ASYNCHRON|SFX_SLOT_RECORDPERSET|SFX_SLOT_HASDIALOG|SFX_SLOT_MENUCONFIG|
                    SFX_SLOT_TOOLBOXCONFIG|SFX_SLOT_ACCELCONFIG|SFX_SLOT_CONTAINER|SFX_SLOT_READONLYDOC|0,
                    0,
                    SfxStringItem,
                    11/*Offset*/, 10/*Count*/,".Print",SFX_SLOT_METHOD|0,"Print" ),
    
             // ... to be continued ...

    The SFX_NEW_SLOT_ARG macro initializes a complete slot. Most of it should be pretty obvious from what was described already, please note the macros for the generated stubs (and especially SFX_STUB_PTR_STATE_NONE for the empty stub).

    An interesting addition is the second line of each block, it contains an "Offset Next". It is a pointer that points to the next slot in this shell that uses the same "GetState" method as the current slot. This enables SFX to collects status information for several slots at once when a complete update is requested. In general the shell code allows to group slots together in Exec/GetState methods and have more than one per shell for clarity reasons.
    The last line in each block also contains an offset and a count. These attributes point to the arguments of the slot (of course only for methods). All possible arguments of all slots inside a shell are grouped into a single array, arguments that belong to the same slot of course beneath each other. The offset points to the first argument of a slot:

    SFX_ARGUMENTMAP(SfxObjectShell)
    {
        SFX_ARGUMENT(SID_FILE_NAME,"URL",SfxStringItem),
        SFX_ARGUMENT(SID_FILTER_NAME,"FilterName",SfxStringItem),
        SFX_ARGUMENT(SID_PASSWORD,"Password",SfxStringItem),
        SFX_ARGUMENT(SID_FILE_FILTEROPTIONS,"FilterOptions",SfxStringItem),
        SFX_ARGUMENT(SID_DOCINFO_COMMENTS,"VersionComment",SfxStringItem),
        SFX_ARGUMENT(SID_DOCINFO_AUTHOR,"VersionAuthor",SfxStringItem),
        SFX_ARGUMENT(SID_OVERWRITE,"Overwrite",SfxBoolItem),
        SFX_ARGUMENT(SID_UNPACK,"Unpacked",SfxBoolItem),
        SFX_ARGUMENT(SID_SAVETO,"SaveTo",SfxBoolItem),
        SFX_ARGUMENT(SID_CLOSEDOC_SAVE,"SaveChanges",SfxBoolItem),    // <---- here args for SID_CLOSEDOC start
        SFX_ARGUMENT(SID_CLOSEDOC_FILENAME,"FileName",SfxStringItem), // "count" is 2, so this is the second one
    
        // ... to be continued ...
    };

    In each module one header file containing all shells is created. It must be included in the source file of each shell class that contains the SFX_IMPL_INTERFACE macro. To avoid multiple definitions of the slot arrays by this multiple inclusion each code block assigned to a particular shell is framed by an "#ifdef ClassName" block, so in the source file the typical code sequence is

    #define SfxObjectShell
    #include "sfxslots.hxx"

    Other parts of the header file are framed by "#ifdef SFX_TYPEMAP" and this part mustbe activated only once also so each module contains one file that includes the generated header file and defined SFX_TYPEMAP before. This can be on e of the shell files that also defines its shell interface or it can be a different one. The part of the generated header file that is activated here is not necessary for the SFX core (the slot dispatching), but was necessary in former times for the type information of the old Basic API and for macro recording. Today it is used for the wrapper that mediates between SFX dispatching and the Dispatch API and thus also for macro recording because the current macro recorder also uses the Dispatch API.

    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 atomic or structured type is done by using a mapping table in another sdi file. Here's an example for atomic types from the SFX module:

    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    SbxImageItem

    It's pretty straightforward: "item" declares the type to be atomic and it is followed by the Basic (or better: UNO) type and the Item representing it.

    Structured types are a little bit more complicated:

    struct Point
    {
        INT32    X    MID_X;
        INT32    Y    MID_Y;
    };
    item Point SfxPointItem;
    
    struct Rectangle
    {
        INT32    Left    MID_RECT_LEFT;
        INT32    Top     MID_RECT_TOP;
        INT32    Width   MID_WIDTH;
        INT32    Height  MID_HEIGHT;
    };
    item Rectangle SfxRectangleItem;
    
    struct DocInfo
    {
        BOOL    UseUserData    MID_DOCINFO_USEUSERDATA;
        BOOL    DeleteUserData MID_DOCINFO_DELETEUSERDATA;
        String  Title          MID_DOCINFO_TITLE;
        String  Subject        MID_DOCINFO_SUBJECT;
        String  KeyWords       MID_DOCINFO_KEYWORDS;
        String  Description    MID_DOCINFO_KEYWORDS;
        BOOL    AutoReload     MID_DOCINFO_AUTOLOADENABLED;
    };
    item DocInfo SfxDocumentInfoItem;

    The examples show simple structs like a Point or more complex structs like a DocInfo. Structs can be nested, a DocInfo could contain a Point also. In the old StarBasic and also in todays macro recording each member of a struct was represented by an individual item (so an ID was needed that is unique in the complete argument set) and the names of these arguments were assembled from the name of the struct parameter name and the member name in the obvious from ArgName.MemberName.
    For some items that are used only internally in SFX based code (but not for recording purposes) no mapping was necessary and for syntactical completeness they are mapped to an atomic type, but this mapping is never used:

    item String    SbxItem;                //! Dummy
    item String    SfxObjectItem;          //! Dummy
    item String    SfxTemplateItem;        //! Dummy
    item String    SfxMacroInfoItem;       //! Dummy
    item String    SfxImageItem;           //! Dummy
    item String    SfxFrameItem;           //! Dummy
    item String    SfxObjectShellItem;     //! Dummy

    The svidl compiler creates the type mappings into the following form:

    // example for an atomic type
    #ifdef SFX_TYPEMAP
    SfxType0 aSfxStringItem_Impl =
    {
        TYPE(SfxStringItem), 0
    };
    #endif
    
    // example for a struct with 4 members
    extern SfxType4 aSfxRectangleItem_Impl;
    #ifdef SFX_TYPEMAP
    SfxType4 aSfxRectangleItem_Impl =
    {
        TYPE(SfxRectangleItem}, 4, { {MID_RECT_LEFT,"Left"}, {MID_RECT_TOP,"Top"}, {MID_WIDTH,"Width"},
    {MID_HEIGHT,"Height"} }
    };
    #endif

    It utilizes the TOOLS based RTTI that allows to offer a static factory method by using one of the TYPEINIT_AUTOFACTORY macros. This factory that can be used to create an item of the particular class. This is very important for the conversion routines that match items to UNO structs and vice versa. An item that does not offer a factory can't be converted properly and so macro recording and Dispatch API are broken for this item.

    The conversion between an item and a UNO type uses the virtual PutValue and QueryValue methods of the items. In the case of macro recording (where only atomic types are allowed) the "MemberID" parameter of these calls is used always in case of structured items and so it is important that the MIDs in the sdi file match the MIDs used in the implementation of the item. As an example, an SfxRectangleItem is recorded as two integer values.

    The matching we use for the Dispatch API in our controllers on the other hand always converts to complete structs, not to atomic members, so the SfxRectangleItem is converted to a com.sun.star.awt.Rectangle. The rect Put/QueryValue in this case is called with MID=0. It's vital for this process that "0" is an invalid MID that must not be used for any member.

    Apache Feather Logo.svg

    This work is licensed under the Apache License version 2.0