LibreOffice Developer's Guide: Chapter 6 - Office Development

    From The Document Foundation Wiki

    This chapter describes the application environment of the LibreOffice application. It assumes that you have read the chapter First Steps, and that you are able to connect to the office and load documents.

    In most cases, users use the functionality of LibreOffice by opening and modifying documents. The interfaces and services common to all document types and how documents are embedded in the surrounding application environment are discussed.

    It is also possible to extend the functionality of LibreOffice by replacing the services mentioned here by intercepting the communication between objects or by creating your own document type and integrating it into the desktop environment. All these things are discussed in this chapter.

    LibreOffice Application Environment

    The LibreOffice application environment is made up of the desktop environment and the framework API.

    The LibreOffice Application Environment

    The desktop environment consists of the desktop service and auxiliary objects. The desktop environment's functions are carried out by the framework API. The framework API has two parts: the component framework and the dispatch framework. The component framework follows a special Frame-Controller-Model paradigm to manage components viewable in LibreOffice. The dispatch framework handles command requests sent by the GUI.

    Desktop Environment

    The com.sun.star.frame.Desktop service is the central management instance for the LibreOffice application framework. All LibreOffice application windows are organized in a hierarchy of frames that contain viewable components. The desktop is the root frame for this hierarchy. From the desktop you can load viewable components, access frames and components, terminate the office, traverse the frame hierarchy and dispatch command requests.

    The name of this service originates at StarOffice 5.x, where all document windows were embedded into a common application window that was occupied by the StarOffice desktop, mirroring the Windows desktop. The root frame of this hierarchy was called the desktop frame. The name of this service and the interface name com.sun.star.frame.XDesktop were kept for compatibility reasons.

    The desktop object and frame objects use auxiliary services, such as the com.sun.star.document.TypeDetection service and other, opaque implementations that interact with the UNO-based office, but are not accessible through the LibreOffice API. Examples for the latter are the global document event handling and its user interface (Tools ▸ Customize ▸ Events), and the menu bars that use the dispatch API without being UNO services themselves. The desktop service, together with these surrounding objects, is called the desktop environment.

    The Desktop terminates the office and manages components and frames

    The viewable components managed by the desktop can be three different kinds of objects: full-blown office documents with a document model and controllers, components with a controller but no model, such as the bibliography and database browser, or simple windows without API-enabled controllers, for example, preview windows. The commonality between these types of components is the com.sun.star.lang.XComponent interface. Components with controllers are also called office components, whereas simple window components are called trivial components.

    Frames in the LibreOffice API are the connecting link between windows, components and the desktop environment. The relationship between frames and components is discussed in the next section, Framework API.

    Like all other services, the com.sun.star.frame.Desktop service can be exchanged by another implementation that extends the functionality of LibreOffice. By exchanging the desktop service it is possible to use different kinds of windows or to make LibreOffice use MDI instead of SDI. This is not an easy thing to do, but it is possible without changing any code elsewhere in LibreOffice.

    Framework API

    The framework API does not define an all-in-one framework with strongly coupled interfaces, but defines specialized frameworks that are grouped together by implementing the relevant interfaces at LibreOffice components. Each framework concentrates on a particular aspect, so that each component decides the frameworks it wants to participate in.

    Currently, there are two of these frameworks: the component framework that implements the frame-controller-model paradigm and the dispatch framework that handles command requests from and to the application environment. The controller and frame implementations form the bridge between the two frameworks, because controllers and frames implement interfaces from the component framework and dispatch framework.

    The framework API is an abstract specification. Its current implementation uses the Abstract Window Toolkit (AWT) specified in com.sun.star.awt, which is an abstract specification as well. The current implementation of the AWT is the Visual Component Library (VCL), a cross-platform toolkit for windows and controls written in C++ created before the specification of com.sun.star.awt and adapted to support com.sun.star.awt.

    Frame-Controller-Model Paradigm in LibreOffice

    The well-known Model-View-Controller (MVC) paradigm separates three application areas: document data (model), presentation (view) and interaction (controller). LibreOffice has a similar abstraction, called the Frame-Controller-Model (FCM) paradigm. The FCM paradigm shares certain aspects with MVC, but it has different purposes; therefore, it is best to approach FCM independently of MVC. The model and controller in MVC and FCM are quite different things.

    The FCM paradigm in LibreOffice separates three application areas: document object (model), screen interaction with the model (controller) and controller-window linkage (frame).

    • The model holds the document data and has methods to change these data without using a controller object. Text, drawings, and spreadsheet cells are accessed directly at the model.
    • The controller has knowledge about the current view status of the document and manipulates the screen presentation of the document, but not the document data. It observes changes made to the model, and can be duplicated to have multiple controllers for the same model.
    • The frame contains the controller for a model and knows the windows that are used with it, but does not have window functionality.

    The purpose of FCM is to have three exchangeable parts that are used with an exchangeable window system:

    It is possible to write a new controller that presents an existing model differently without changing the model or the frame. A controller depends on the model it presents; therefore, a new controller for a new model can be written.

    Developers can introduce new models for new document types without taking care of the frame and underlying window management system. However, since there is no default controller, it is necessary to write a suitable controller also.

    By keeping all window-related functionality separate from the frame, it is possible to use one single frame implementation for every possible window in the entire LibreOffice application. Thus, the presentation of all visible components is customized by exchanging the frame implementation. At runtime you can access a frame and replace the controller, together with the model it controls, by a different controller instance.

    Frames

    Linking Components and Windows

    The main role of a frame in the Frame-Controller-Model paradigm is to act as a liaison between viewable components and the window system.

    Frames can hold one component, or a component and one or more subframes. The following two illustrations depict both possibilities. The first illustration shows a frame containing only a component. It is connected with two window instances: the container window and component window.

    Frame containing a component

    When a frame is constructed, the frame must be initialized with a container window using initialize(). This method expects the com.sun.star.awt.XWindow interface of a surrounding window instance, which becomes the container window of the frame. The window instance passed to initialize() must also support com.sun.star.awt.XTopWindow to become a container window. The container window must broadcast window events, such as windowActivated(), and appear in front of other windows or be sent to the background. The fact that container windows support com.sun.star.awt.XTopWindow does not mean the container window is an independent window of the underlying window system with a title bar and a system menu. An XTopWindow acts as a window if necessary, but it can also be docked or depend on a surrounding application window.

    After initializing the frame, a component is set into the frame by a frame loader implementation that loads a component into the frame. It calls setComponent() that takes another com.sun.star.awt.XWindow instance and the com.sun.star.frame.XController interface of a controller.Usually the controller is holding a model, therefore the component gets a component window of its own, separate from the container window.

    A frame with a component is associated with two windows: the container window which is an XTopWindow and the component window, which is the rectangular area that displays the component and receives GUI events for the component while it is active. When a frame is initialized with an instance of a window in a call to initialize(), this window becomes its container window. When a component is set into a frame using setComponent(), another com.sun.star.awt.XWindow instance is passed and becomes the component window.

    When a frame is added to the desktop frame hierarchy, the desktop becomes the parent frame of our frame. For this purpose, the com.sun.star.frame.XFramesSupplier interface of the desktop is passed to the method setCreator() at the XFrame interface. This happens internally when the method append() is called at the com.sun.star.frame.XFrames interface supplied by the desktop.

    Note pin.svg

    Note:
    A component window can have sub-windows, and that is the case with all documents in LibreOffice. For instance, a text document has sub-windows for the toolbars and the editable text. Form controls are sub-windows, as well, however, these sub-windows depend on the component window and do not appear in the Frame-Controller-Model paradigm, as discussed above.

    The second diagram shows a frame with a component and a sub-frame with another component. Each frame has a container window and component window.

    Frame containing a component and a sub-frame

    In the LibreOffice GUI, sub-frames appear as dependent windows. The sub-frame in the illustration above could be a dockable window, such as the beamer showing the database browser or a floating frame in a document created with Insert ▸ Frame.

    Note that a frame with a component and sub-frame is associated with four windows. The frame and the sub-frame have a container window and a component window for the component.

    When a sub-frame is added to a surrounding frame, the frame becomes the parent of the sub-frame by a call to setCreator() at the sub-frame. This happens internally when the method append() is called at the com.sun.star.frame.XFrames interface supplied by the surrounding frame.

    The section Creating Frames Manually shows examples for the usage of the XFrame interface that creates frames in the desktop environment, constructs dockable and standalone windows, and inserts components into frames.

    Communication through Dispatch Framework

    Besides the main role of frames as expressed in the com.sun.star.frame.XFrame interface, frames play another role by providing a communication context for the component they contain, that is, every communication from a controller to the desktop environment and the user interface, and conversely, is done through the frame. This aspect of a frame is published through the com.sun.star.frame.XDispatchProvider interface, which uses special command requests to trigger actions.

    The section Using the Dispatch Framework discusses the usage of the dispatch API.

    Components in Frames

    The desktop environment section discussed the three kinds of viewable components that can be inserted into a frame. If the component has a controller and a model like a document, or if it has only a controller, such as the bibliography and database browser, it implements the com.sun.star.frame.Controller service represented by the interface com.sun.star.frame.XController. In the call to setComponent(), the controller is passed with the component window instance. If the component has no controller, it directly implements com.sun.star.lang.XComponent and com.sun.star.awt.XWindow. In this case, the component is passed as XWindow parameter, and the XController parameter must be a XController reference set to null.

    If the viewable component is a trivial component (implementing XWindow only), the frame holds a reference to the component window, controls the lifetime of the component and propagates certain events from the container window to the component window. If the viewable component is an office component (having a controller), the frame adds to these basic functions a set of features for integration of the component into the environment by supporting additional command URLs for the component at its com.sun.star.frame.XDispatchProvider interface.

    Controllers

    Controllers in LibreOffice are between a frame and document model. This is their basic role as expressed in com.sun.star.frame.XController, which has methods getModel() and getFrame(). The method getFrame() provides the frame that the controller is attached to. The method getModel() returns a document model, but it may return an empty reference if the component does not have a model.

    Usually the controller objects support additional interfaces specific to the document type they control, such as com.sun.star.sheet.XSpreadsheetView for Calc document controllers or com.sun.star.text.XTextViewCursorSupplier for Writer document controllers.

    Controller with Model and Frame

    A single document model can be controlled simultaneously by several controller instances, each associated with a separate frame. Multiple controllers and frames are created by LibreOffice when the user clicks Window ▸ New Window.

    Windows

    Windows in the LibreOffice API are rectangular areas that are positioned and resized, and inform listeners about UI events (com.sun.star.awt.XWindow). They have a platform-specific counterpart that is wrapped in the com.sun.star.awt.XWindowPeer interface, which is invalidated (redrawn), and sets the system pointer and hands out the toolkit for the window. The usage of the window interfaces is outlined in the section Window Interfaces below.

    Dispatch Framework

    The dispatch framework is designed to provide uniform access to components for a GUI by using command URLs that mirror menu items, such as Edit ▸ Select All, with various document components. Only the component knows how to execute a command. Similarly, different document components trigger changes in the UI by common commands. For example, a controller might create UI elements like a menu bar, or open a hyperlink.

    Command dispatching follows a chain of responsibility. Calls to the dispatch API are moderated by the frame, so all dispatch API calls from the UI to the component and conversely are handled by the frame. The frame passes on the command until an object is found that can handle it. It is possible to restrict, extend or redirect commands at the frame through a different frame implementation or through other components connecting to the frame.

    It has already been discussed that frames and controllers have an interface com.sun.star.frame.XDispatchProvider. The interface is used to query a dispatch object for a command URL from a frame and have the dispatch object execute the command. This interface is one element of the dispatch framework.

    By offering the interception of dispatches through the interface com.sun.star.frame.XDispatchProviderInterception, the Frame service offers a method to modify a component's handling of GUI events while keeping its whole API available simultaneously.

    Note pin.svg

    Note:
    Normally, command URL dispatches go to a target frame, which decides what to do with it. A component can use globally accessible objects like the desktop service to bypass restrictions set by a frame, but this is not recommended. It is impossible to prevent an implementation of components against the design principles, because the framework API is made for components that adhere to its design.

    The usage of the Dispatch Framework is described in the section Using the Dispatch Framework.

    Using the Desktop

    Desktop Service and Component Framework

    The com.sun.star.frame.Desktop service available at the global service manager includes the service com.sun.star.frame.Frame. The Desktop service specification provides three interfaces: com.sun.star.frame.XDesktop, com.sun.star.frame.XComponentLoader and com.sun.star.document.XEventBroadcaster, as shown in the following UML chart:

    UML description of the desktop service

    The interface com.sun.star.frame.XDesktop provides access to frames and components, and controls the termination of the office process. It defines the following methods:

    com::sun::star::frame::XFrame getCurrentFrame ()
    com::sun::star::container::XEnumerationAccess getComponents ()
    com::sun::star::lang::XComponent getCurrentComponent ()
    boolean terminate ()
    void addTerminateListener ( [in] com::sun::star::frame::XTerminateListener xListener)
    void removeTerminateListener ( [in] com::sun::star::frame::XTerminateListener xListener)

    The methods getCurrentFrame() and getCurrentComponent() distribute the active frame and document model, whereas getComponents() returns a com.sun.star.container.XEnumerationAccess to all loaded documents. For documents loaded in the desktop environment the methods getComponents() and getCurrentComponent() always return the com.sun.star.lang.XComponent interface of the document model.

    Tip.svg

    Tip:
    If a specific document component is required, but you are not sure whether this component is the current component, use getComponents() to get an enumeration of all document components, check each for the existence of the com.sun.star.frame.XModel interface and use getURL() at XModel to identify your document. Since not all components have to support XModel, test for XModel before calling getURL().


    The office process is usually terminated when the user selects File ▸ Exit or after the last application window has been closed. Clients can terminate the office through a call to terminate() and add a terminate listener to veto the shutdown process.

    As long as the Windows quickstarter is active, the soffice executable is not terminated.

    The following sample shows an com.sun.star.frame.XTerminateListener implementation that prevents the office from being terminated when the class TerminationTest is still active.

    For Java, the TerminateListener public class is defined in its own file:

    import com.sun.star.frame.TerminationVetoException;
    import com.sun.star.frame.XTerminateListener;
    
    public class TerminateListener implements XTerminateListener {
    
        public void notifyTermination (com.sun.star.lang.EventObject eventObject) {
            System.out.println("about to terminate...");
        }
    
        public void queryTermination (com.sun.star.lang.EventObject eventObject)
            throws TerminationVetoException {
    
            // test if we can terminate now
            if (TerminationTest.isAtWork()) {
                System.out.println("Terminate while we are at work? No way!");
                throw new TerminationVetoException() ; // this will veto the termination,
                                                        // a call to terminate() returns false
            }
        }
    
        public void disposing (com.sun.star.lang.EventObject eventObject) {
        }
    }

    The following code tests the TerminateListener (and also defines TerminateListener for C++):

    import com.sun.star.bridge.XUnoUrlResolver;
    import com.sun.star.uno.UnoRuntime;
    import com.sun.star.uno.XComponentContext;
    import com.sun.star.lang.XMultiComponentFactory;
    import com.sun.star.beans.XPropertySet;
    import com.sun.star.beans.PropertyValue;
    
    import com.sun.star.frame.XDesktop;
    import com.sun.star.frame.TerminationVetoException;
    import com.sun.star.frame.XTerminateListener;
    
    public class TerminationTest {
    
        private static boolean atWork = false;
    
        public static void main(String[] args) {
    
            XComponentContext xRemoteContext = null;
            XMultiComponentFactory xRemoteServiceManager = null;
            XDesktop xDesktop = null;
    
            try {
                // connect and retrieve a remote service manager and component context
                XComponentContext xLocalContext =
                    com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
                XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager();
                Object urlResolver = xLocalServiceManager.createInstanceWithContext(
                    "com.sun.star.bridge.UnoUrlResolver", xLocalContext );
                XUnoUrlResolver xUnoUrlResolver = UnoRuntime.queryInterface(
                    XUnoUrlResolver.class, urlResolver );
                Object initialObject = xUnoUrlResolver.resolve(
                    "uno:socket,host=localhost,port=2083;urp;StarOffice.ServiceManager" );
                XPropertySet xPropertySet = UnoRuntime.queryInterface(
                    XPropertySet.class, initialObject);
                Object context = xPropertySet.getPropertyValue("DefaultContext");
                xRemoteContext = UnoRuntime.queryInterface(XComponentContext.class, context);
                xRemoteServiceManager = xRemoteContext.getServiceManager();
    
                // get Desktop instance
                Object desktop = xRemoteServiceManager.createInstanceWithContext (
                    "com.sun.star.frame.Desktop", xRemoteContext);
                xDesktop = UnoRuntime.queryInterface(XDesktop.class, desktop);
    
                TerminateListener terminateListener = new TerminateListener ();
                xDesktop.addTerminateListener (terminateListener);
    
                // try to terminate while we are at work
                atWork = true;
                boolean terminated = xDesktop.terminate ();
                System.out.println("The Office " +
                    (terminated ? "has been terminated" : "is still running, we are at work"));
    
                // no longer at work
                atWork = false;
                // once more: try to terminate
                terminated = xDesktop.terminate ();
                System.out.println("The Office " +
                    (terminated ? "has been terminated" :
                        "is still running. Someone else prevents termination, e.g. the quickstarter"));
            }
            catch (java.lang.Exception e) {
                e.printStackTrace();
            }
            finally {
                System.exit(0);
            }
    
        }
        public static boolean isAtWork() {
            return atWork;
        }
    
    }
    #include <sal/main.h>
    #include <cppuhelper/bootstrap.hxx>
    #include <cppuhelper/implbase1.hxx>
    #include <com/sun/star/uno/XComponentContext.hpp>
    #include <com/sun/star/lang/XMultiComponentFactory.hpp>
    #include <com/sun/star/frame/XDesktop.hpp>
    #include <com/sun/star/frame/XTerminateListener.hpp>
    #include <com/sun/star/frame/TerminationVetoException.hpp>
    
    #include <iostream>
    
    using namespace ::cppu;
    using namespace ::css::uno;
    using namespace ::css::lang;
    using namespace ::css::frame;
    
    namespace
    {
    bool atWork = false;
    }
    
    class TerminateListener : public WeakImplHelper1<XTerminateListener>
    {
    public:
        // XTerminateListener
        virtual void SAL_CALL notifyTermination(const EventObject& event) SAL_OVERRIDE
        {
            std::cout << "About to terminate...\n";
        }
    
        virtual void SAL_CALL queryTermination(const EventObject& event) SAL_OVERRIDE
        {
            // Test if we can terminate now
            if (atWork)
            {
                std::cout << "Terminate while we are at work? You can't be serious ;-)!\n";
                throw TerminationVetoException();
            }
        }
    
        // XEventListener
        virtual void SAL_CALL disposing(const EventObject& event) SAL_OVERRIDE {}
    };
    
    SAL_IMPLEMENT_MAIN()
    {
        try
        {
            // Connect to, or create, an instance of the Office.
            Reference<XComponentContext> xContext(bootstrap());
            std::cout << "Connected to a running office...\n";
    
            // Get a reference to the multi-component factory, and use it
            // to create a Desktop reference.
            Reference<XMultiComponentFactory> xMCF(xContext->getServiceManager());
            Reference<XDesktop> xDesktop(
                xMCF->createInstanceWithContext("com.sun.star.frame.Desktop", xContext),
                UNO_QUERY_THROW);
    
            // Create our termination request listener, and register it.
            TerminateListener listener;
            Reference<XTerminateListener> xTerminateListener(listener, UNO_QUERY_THROW);
            xDesktop->addTerminateListener(xTerminateListener);
    
            // Try to terminate while we are at work.
            atWork = true;
            bool terminated = xDesktop->terminate();
            std::cout << "The Office "
                      << (terminated ? "has been terminated" : "is still running, we are at work.")
                      << '\n';
    
            // Try to terminate when we are NOT at work.
            atWork = false;
            terminated = xDesktop->terminate();
            std::cout << "The Office "
                      << (terminated ? "has been terminated"
                                     : "is still running. Something else prevents termination,"
                                       "such as the quickstarter.")
                      << '\n';
        }
        catch (const RuntimeException& e)
        {
            std::cerr << e.Message << '\n';
            return 1;
        }
        return 0;
    }

    The office freezes when terminate() is called if there are unsaved changes. As a workaround set all documents into an unmodified state through their com.sun.star.util.XModifiable interface or store them using com.sun.star.frame.XStorable.

    The Desktop offers a facility to load components through its interface com.sun.star.frame.XComponentLoader. It has one method:

    com::sun::star::lang::XComponent loadComponentFromURL ( [in] string aURL,
                    [in] string aTargetFrameName,
                    [in] long nSearchFlags,
                    [in] sequence < com::sun::star::beans::PropertyValue aArgs > )

    Refer to chapter Handling Documents for details about the loading process.

    For versions beyond 641, the desktop also provides an interface that allows listeners to be notified about certain document events through its interface com.sun.star.document.XEventBroadcaster.

    void addEventListener ( [in] com::sun::star::document::XEventListener xListener)
    void removeEventListener ( [in] com::sun::star::document::XEventListener xListener)

    The XEventListener must implement a single method (besides disposing()):

    [oneway] void notifyEvent ( [in] com::sun::star::document::EventObject Event )

    The struct com.sun.star.document.EventObject has a string member EventName that assumes one of the values specified in com.sun.star.document.Events. The corresponding events are found on the Events tab of the Tools - Configure dialog when the option OpenOffice is selected.

    The desktop broadcasts these events for all loaded documents.

    The current version of LibreOffice does not have a GUI element as a desktop. The redesign of the LibreOffice GUI in StarOffice 5.x and later resulted in the com.sun.star.frame.Frame service part of the desktop service is now non-functional. While the XFrame interface can still be queried from the desktop, almost all of its methods are dummy implementations. The default implementation of the desktop object in LibreOffice is not able to contain a component and refuses to be attached to it, because the desktop is still a frame that is the root for the common hierarchy of all frames in LibreOffice. The desktop has to be a frame because its com.sun.star.frame.XFramesSupplier interface must be passed to setCreator() at the child frames, therefore the desktop becomes the parent frame. However, the following functionality of com.sun.star.frame.Frame is still in place:

    The desktop interface com.sun.star.frame.XFramesSupplier offers methods to access frames. This interface inherits from com.sun.star.frame.XFrame, and introduces the following methods:

    com::sun::star::frame::XFrames getFrames ()
    com::sun::star::frame::XFrame getActiveFrame ()
    void setActiveFrame ( [in] com::sun::star::frame::XFrame xFrame)

    The method getFrames() returns a com.sun.star.frame.XFrames container, that is a com.sun.star.container.XIndexAccess, with additional methods to add and remove frames:

    void append ( [in] com::sun::star::frame::XFrame xFrame )
    sequence < com::sun::star::frame::XFrame > queryFrames ( [in] long nSearchFlags )
    void remove ( [in] com::sun::star::frame::XFrame xFrame )

    This XFrames collection is used when frames are added to the desktop to become application windows.

    Through getActiveFrame(), you access the active sub-frame of the desktop frame, whereas setActiveFrame() is called by a sub-frame to inform the desktop about the active sub-frame.

    The object returned by getFrames() does not support XTypeProvider, therefore it cannot be used with LibreOffice Basic.

    The parent interface of XFramesSupplier, com.sun.star.frame.XFrame is functional by accessing the frame hierarchy below the desktop. These methods are discussed in the section Frames below:

    com::sun::star::frame::XFrame findFrame ( [in] string aTargetFrameName, [in] long nSearchFlags );
    boolean isTop ();

    The generic dispatch interface com.sun.star.frame.XDispatchProvider executes functions of the internal Desktop implementation that are not accessible through specialized interfaces. Dispatch functions are described by a command URL. The XDispatchProvider returns a dispatch object that dispatches a given command URL. A reference of command URLs supported by the desktop is available on DispatchCommands. Through the com.sun.star.frame.XDispatchProviderInterception, client code intercepts the command dispatches at the desktop. The dispatching process is described in section Using the Dispatch Framework.

    Using the Component Framework

    The component framework comprises the interfaces of frames, controllers and models used to manage components in the LibreOffice desktop environment. In our context, everything that "dwells" in a frame of the desktop environment is called a component, because the interface com.sun.star.lang.XComponent is the common denominator for objects that are loaded into frames.

    Frames, controllers and models hold references to each other. The frame is by definition the default owner of the controller and the model, that is, it is responsible to call dispose() on the controller and model when it is destroyed itself. Other objects that are to hold references to the frame, controller, or model must register as listeners to be informed when these references become invalid. Therefore XModel, XController and XFrame inherit from XComponent:

    void dispose ()
    void addEventListener ( [in] com::sun::star::lang::XEventListener xListener)
    void removeEventListener ( [in] com::sun::star::lang::XEventListener aListener)

    The process to resolve the circular dependencies of the component framework is complex. For instance, the objects involved in the process may be in a condition where they may not be disposed of. Refer to the section Closing Documents for additional details.

    Theoretically every UNO object could exist in a frame, as long as it is willing to let the frame control its existence when it ends.

    A trivial component (XWindow only) is enough for simple viewing purposes, where no activation of a component and related actions like cursor positioning or user interactions are necessary.

    If the component participates in more complex interactions, it must implement the controller service.

    Many features of the desktop environment are only available if the URL of a component is known. For example:

    • Presenting the URL or title of the document.
    • Inserting the document into the autosave queue.
    • Preventing the desktop environment from loading documents twice.
    • Allow for participation in the global document event handling.

    In this case, com.sun.star.frame.XModel comes into operation, since it has methods to handle URLs, among others.

    So a complete office component is made up of

    Getting Frames, Controllers and Models from Each Other

    Usually developers require the controller and frame of an already loaded document model. The com.sun.star.frame.XModel interface of LibreOffice document models gets the controller that provides access to the frame through its com.sun.star.frame.XController interface. The following illustration shows the methods that get the controller and frame for a document model and conversely. From the frame , obtain the corresponding component and container window.

    Frame-Controller-Model Organization

    If the loaded component is a trivial component and implements com.sun.star.awt.XWindow only, the window and the window peer is reached by querying these interfaces from the com.sun.star.lang.XComponent returned by loadComponentFromURL().

    Frames

    Frame Service
    XFrame
    Frame Setup

    The main role of a frame is to link components into a surrounding window system. This role is expressed by the following methods of the frame's main interface com.sun.star.frame.XFrame:

    // methods for container window
    void initialize ( [in] com::sun::star::awt::XWindow xWindow);
    com::sun::star::awt::XWindow getContainerWindow ();
    
    // methods for component window and controller
    boolean setComponent ( [in] com::sun::star::awt::XWindow xComponentWindow,
                            [in] com::sun::star::frame::XController xController );
    com::sun::star::awt::XWindow getComponentWindow ();
    com::sun::star::frame::XControllergetController ();

    The first two methods deal with the container window of a frame, the latter three are about linking the component and the component window with the frame. The method initialize() expects a top window that is created by the AWT toolkit that becomes the container window of the frame and is retrieved by getContainerWindow().

    Frame Hierarchies

    When frames link components into a surrounding window system, they build a frame hierarchy. This aspect is covered by the hierarchy-related XFrame methods:

    [oneway] void setCreator ( [in] com::sun::star::frame::XFramesSupplier xCreator );
    com::sun::star::frame::XFramesSupplier getCreator ();
    string getName ();
    [oneway] void setName ( [in] string aName );
    com::sun::star::frame::XFrame findFrame ( [in] string aTargetFrameName, [in] long nSearchFlags );
    boolean isTop ();

    The XFrame method setCreator() informs a frame about its parent frame and must be called by a frames container (com.sun.star.frame.XFrames) when a frame is added to it by a call to append(). A frames container is provided by frames supporting the interface com.sun.star.frame.XFramesSupplier. XFramesSupplier is currently supported by the desktop frame and by the default frame implementation used by LibreOffice documents. It is described below.

    The frame has a custom name that is read through getName() and written through setName(). Frames in the desktop hierarchy created by GUI interaction usually do not have names. The getName() returns an empty string for them, whereas frames that are created for special purposes, such as the beamer frame or the online help, have names. Developers can set a name and use it to address a frame in findFrame() calls or when loading a component into the frame. Custom frame names must not start with an underscore. Leading underscores are reserved for special frame names. See below.

    Every frame in the frame hierarchy is accessed through any other frame in this hierarchy by calling the findFrame() method. This method searches for a frame with a given name in five steps: self, children, siblings, parent, and create if not found. The findFrame() checks the called frame, then calls findFrame() at its children, then its siblings and at its parent frame. The fifth step in the search strategy is reached if the search makes it to the desktop without finding a frame with the given name. In this case, a new frame is created and assigned the name that was searched for. If the top frame is outside the desktop hierarchy, a new frame is not created.

    The name used with findFrame() can be an arbitrary string without a leading underscore or one of the following reserved frame names. These names are for internal use for loading documents. Some of the reserved names are logical in a findFrame() call, also. A complete list of reserved frame names can be found in section Target Frame.

    _top
    Returns the top frame of the called frame, first frame where isTop() returns true when traveling up the hierarchy.
    _parent
    Returns the next frame above in the frame hierarchy.
    _self
    Returns the frame itself, same as an empty target frame name. This means you are searching for a frame you already have, but it is legal to do so.
    _blank
    Creates a new top-level frame whose parent is the desktop frame.

    Calls with "_top" or "_parent" return the frame itself if the called frame is a top frame or has no parent. This is compatible to the targeting strategies of web browsers.

    We have seen that findFrame() is called recursively. To control the recursion, the search flags parameter specified in the constants group com.sun.star.frame.FrameSearchFlag is used. For all of the five steps mentioned above, a suitable flag exists (SELF, CHILDREN, SIBLINGS, PARENT, CREATE). Every search step can be prohibited by deleting the appropriate FrameSearchFlag. The search flag parameter can also be used to avoid ambiguities caused by multiple occurrences of a frame name in a hierarchy by excluding parts of the frame tree from the search. If findFrame() is called for a reserved frame name, the search flags are ignored.

    Tip.svg

    Tip:
    An additional flag can be used to extend a bottom-up search to all LibreOffice application windows, no matter where the search starts. Based on the five flags for the five steps, the default frame search stops searching when it reaches a top frame and does not continue with other LibreOffice windows. Setting the TASKS flag overrides this.


    There are separate frame hierarchies that do not interact with each other. If a frame is created, but not inserted into any hierarchy, it becomes the top frame of its own hierarchy. This frame and its contents can not be accessed from other hierarchies by traversing the frame hierarchies through API calls. Also, this frame and its content cannot reach frames and their contents in other hierarchies. It is the code that creates a frame and decides if the new frame becomes part of an existing hierarchy, thus enabling it to find other frames, and making it and its viewable component visible to the other frames. Examples for frames that are not inserted into an existing hierarchy are preview frames in dialogs, such as the document preview in the File ▸ New ▸ Templates and Documents dialog.

    Note pin.svg

    Note:
    This is the only way the current frame and desktop implementation handle this. If one exchanges either or both of them by another implementation, the treatment of the "_blank" target and the CREATE SearchFlag may differ.

    Frame Actions

    Several actions take place at a frame. The context of viewable components can change, a frame may be activated or the relationship between frame and component may be altered. For instance, when the current selection in a document has been changed, the controller informs the frame about it by calling contextChanged(). The frame then tells its frame action listeners that the context has changed. The frame action listeners are also informed about changes in the relationship between the frame and component, and about frame activation. The corresponding XFrame methods are:

    void contextChanged ();
    
    [oneway] void activate ();
    [oneway] void deactivate ();
    boolean isActive ();
    
    [oneway] void addFrameActionListener ( [in] com::sun::star::frame::XFrameActionListener xListener );
    [oneway] void removeFrameActionListener ( [in] com::sun::star::frame::XFrameActionListener xListener );

    The method activate() makes the given frame the active frame in its parent container. If the parent is the desktop frame, this makes the associated component the current component. However, this is not reflected in the user interface by making the corresponding window the top window. If the container of the active frame is to be the top window, use setFocus() at the com.sun.star.awt.XWindow interface of the container window.

    The interface com.sun.star.frame.XFrameActionListener used with addFrameActionListener() must implement the following method:

    Method of com.sun.star.frame.XFrameActionListener
    frameAction() Takes a struct com.sun.star.frame.FrameActionEvent. The struct contains two members: the source com.sun.star.frame.XFrame Frame and an enum com.sun.star.frame.FrameActionEvent Action value with one of the following values:

    COMPONENT_ATTACHED: a component has been attached to a frame. This is almost the same as the instantiation of the component within that frame. The component is attached to the frame immediately before this event is broadcast.

    COMPONENT_DETACHING: a component is detaching from a frame. This is the same as the destruction of the component which was in that frame. The moment the event is broadcast the component is still attached to the frame, but in the next moment it will not be..

    COMPONENT_REATTACHED: a component has been attached to a new model. In this case, the component remains the same, but operates on a new model component.

    FRAME_ACTIVATED: a component has been activated. Activations are broadcast from the top component which was not active, down to the innermost component.

    FRAME_DEACTIVATING: broadcast immediately before the component is deactivated. Deactivations are broadcast from the innermost component which does not stay active up to the outermost component which does not stay active.

    CONTEXT_CHANGED: a component has changed its internal context, for example, the selection. If the activation status within a frame changes, this is a context change, also.

    FRAME_UI_ACTIVATED: broadcast by an active frame when it is getting UI control (tool control).

    FRAME_UI_DEACTIVATING: broadcast by an active frame when it is losing UI control (tool control).

    Warning.svg

    Warning:
    At this time, the XFrame methods used to build a frame-controller-model relationship can only be fully utilized by frame loader implementations or customized trivial components. Outside a frame loader you can create a frame, but the current implementations cannot create a standalone controller that could be used with setComponent(). Therefore, you can not remove components from one frame and add them to another or create additional controllers for a loaded model using the component framework. This is due to restrictions of the VCL and the C++ implementation of the current document components.

    Currently, the only way for clients to construct a frame and insert a LibreOffice document into it, is to use the com.sun.star.frame.XComponentLoader interface of the com.sun.star.frame.Desktop or the interfaces com.sun.star.frame.XSynchronousFrameLoader, the preferred frame loader interface, and the asynchronous com.sun.star.frame.XFrameLoader of the com.sun.star.frame.FrameLoader service that is available at the global service factory.

    The recommended method to get additional controllers for loaded models is to use the OpenNewView property with loadComponentFromURL() at the com.sun.star.frame.XComponentLoader interface of the desktop.

    There is also another possibility: dispatch a ".uno:NewWindow" command to a frame that contains that model.


    XFramesSupplier

    The Frame interface com.sun.star.frame.XFramesSupplier offers methods to access sub-frames of a frame. The frame implementation of LibreOffice supports this interface. This interface inherits from com.sun.star.frame.XFrame, and introduces the following methods:

    com::sun::star::frame::XFrames getFrames ()
    com::sun::star::frame::XFrame getActiveFrame ()
    void setActiveFrame ( [in] com::sun::star::frame::XFrame xFrame)

    The method getFrames() returns a com.sun.star.frame.XFrames container, that is a com.sun.star.container.XIndexAccess with additional methods to add and remove frames:

    void append ( [in] com::sun::star::frame::XFrame xFrame )
    sequence < com::sun::star::frame::XFrame > queryFrames ( [in] long nSearchFlags )
    void remove ( [in] com::sun::star::frame::XFrame xFrame );

    This XFrames collection is used when frames are appended to a frame to become sub-frames. The append() method implementation must extend the existing frame hierarchy by an internal call to setCreator() at the parent frame in the frame hierarchy. The parent frame is always the frame whose XFramesSupplier interface is used to append a new frame.

    Through getActiveFrame() access the active sub-frame in a frame with subframes. If there are no sub-frames or a sub-frame is currently non-active, the active frame is null. The setActiveFrame() is called by a sub-frame to inform the frame about the activation of the sub-frame. In setActiveFrame(), the method setActiveFrame() at the creator is called, then the registered frame action listeners are notified by an appropriate call to frameAction() with Action set to FRAME_UI_ACTIVATED.

    XDispatchProvider and XDispatchProviderInterception

    Frame services also support com.sun.star.frame.XDispatchProvider and com.sun.star.frame.XDispatchProviderInterception. The section Using the Dispatch Framework explains how these interfaces are used.

    XStatusIndicatorFactory

    The frame implementation supplies a status indicator through its interface com.sun.star.task.XStatusIndicatorFactory. A status indicator can be used by a frame loader to show the loading process for a document. The factory has only one method that returns an object supporting com.sun.star.task.XStatusIndicator:

    com::sun::star::task::XStatusIndicator createStatusIndicator ()

    The status indicator is displayed by a call to start(). Pass a text and a numeric range, and use setValue() to let the status bar grow until the maximum range is reached. The method end() removes the status indicator.

    Controllers

    Controller Service
    XController

    A com.sun.star.frame.XController inherits from com.sun.star.lang.XComponent and introduces the following methods:

    com::sun::star::frame::XFrame getFrame ()
    void attachFrame (com::sun::star::frame::XFrame xFrame)
    com::sun::star::frame::XModel getModel ()
    boolean attachModel (com::sun::star::frame::XModel xModel)
    boolean suspend (boolean bSuspend)
    any getViewData ()
    void restoreViewData (any Data)

    The com.sun.star.frame.XController links model and frame through the methods get/attachModel() and get/attachFrame(). These methods and the corresponding methods in the com.sun.star.frame.XModel and com.sun.star.frame.XFrame interfaces act together. Calling attachModel() at the controller must be accompanied by a corresponding call of connectController() at the model, and attachFrame() at the controller must have its counterpart setComponent() at the frame.

    The controller is asked for permission to dispose of the entire associated component by using suspend(). The suspend() method shows dialogs, for example, to save changes. To avoid the dialog, close the corresponding frame without using suspend() before. The section Closing Documents provides additional information.

    Developers retrieve and restore data used to setup the view at the controller by calling get/restoreViewData(). These methods are usually called on loading and saving the document, but they also allow developers to manipulate the state of a view from the outside. The exact content of this data depends on the concrete controller/model pair.

    XDispatchProvider

    Through com.sun.star.frame.XDispatchProvider, the controller participates in the dispatch framework. It is described in section Using the Dispatch Framework.

    XSelectionSupplier

    The optional Controller interface com.sun.star.view.XSelectionSupplier accesses the selected object and informs listeners when the selection changes:

    boolean select ( [in] any aSelection)
    any getSelection ()
    void addSelectionChangeListener ( [in] com::sun::star::view::XSelectionChangeListener xListener)
    void removeSelectionChangeListener ( [in] com::sun::star::view::XSelectionChangeListener xListener)

    The type of selection depends on the type of the document and the selected object. It is also possible to get the current selection in the active or last controller of a model by calling the method getCurrentSelection() in the com.sun.star.frame.XModel interface.

    XContextMenuInterception

    The optional Controller interface com.sun.star.ui.XContextMenuInterception intercepts requests for context menus in the document's window. See chapter Intercepting Context Menus.

    Document Specific Controller Services

    The com.sun.star.frame.Controller specification is generic and does not describe additional features required for a fully functional document controller specification, such as the controller specifications for Writer, Calc and Draw documents. The following table shows the controller services specified for LibreOffice document components.

    Once the reference to a controller is retrieved, you can query for these interfaces. Use the com.sun.star.lang.XServiceInfo interface of the model to ask it for the supported service(s). The component implementations in LibreOffice support the following services. Refer to the related chapters for additional information about the interfaces you get from the controllers of LibreOffice documents.

    Component and Chapter Specialized Controller Service General Description
    Writer Text Document Controller com.sun.star.text.TextDocumentView The text view supplies a text view cursor that has knowledge about the current page layout and page number. It can walk through document pages, screen pages and lines. The selected ruby text is also available, a special Asian text formatting, comparable to superscript.
    Calc Spreadsheet Document Controller com.sun.star.sheet.SpreadsheetView The spreadsheet view is extremely powerful. It includes the services com.sun.star.sheet.SpreadsheetViewPane and com.sun.star.sheet.SpreadsheetViewSettings. The view pane handles the currently visible cell range and provides controllers for form controls in the spreadsheet. The view settings deal with the visibility of spreadsheet elements, such as the grid and current zoom mode. Furthermore, the spreadsheet view provides access to the active sheet in the view and the collection of all view panes, allowing to split and freeze the view, and control the interactive selection of a cell range.
    Draw Drawing and Presentation Document Controller com.sun.star.drawing.DrawingDocumentDrawView The drawing document view toggles master page mode and layer mode, controls the current page and supplies the currently visible rectangle.
    Impress Drawing and Presentation Document Controller com.sun.star.drawing.DrawingDocumentDrawView

    com.sun.star.presentation.PresentationView

    The presentation view does not introduce presentation specific features. Running presentations are controlled by the com.sun.star.presentation.XPresentationSupplier interface of the presentation document model.
    DataBaseAccess com.sun.star.sdb.DataSourceBrowser This controller has no published functionality that would be useful for developers.
    Bibliography (no special controller specified) -
    Writer (PagePreview) (no special controller specified) -
    Writer/Webdocument (SourceView) (no special controller specified) -
    Calc (PagePreview) (no special controller specified) -
    Chart Chart Document Controller (no special controller specified) -
    Math (no special controller specified) -

    Models

    There is not an independent specification for a model service. The interface com.sun.star.frame.XModel is currently supported by Writer, Calc, Draw and Impress document components. In our context, we call objects supporting com.sun.star.frame.XModel, model objects. All LibreOffice document components have the service com.sun.star.document.OfficeDocument in common. An OfficeDocument implements the following interfaces:

    XModel

    The interface com.sun.star.frame.XModel inherits from com.sun.star.lang.XComponent and introduces the following methods, which handle the model's resource description, manage its controllers and retrieves the current selection.

    string getURL ()
    sequence < com::sun::star::beans::PropertyValue > getArgs ()
    boolean attachResource ( [in] string aURL,
                              [in] sequence < com::sun::star::beans::PropertyValue > aArgs )
    
    com::sun::star::frame::XController getCurrentController ()
    void setCurrentController (com::sun::star::frame::XController xController)
    void connectController (com::sun::star::frame::XController xController)
    void disconnectController (com::sun::star::frame::XController xController)
    void lockControllers ()
    void unlockControllers ()
    boolean hasControllersLocked ()
    
    com::sun::star::uno::XInterface getCurrentSelection ()

    The method getURL() provides the URL where a document was loaded from or last stored using storeAsURL(). As long as a new document has not been saved, the URL is an empty string. The method getArgs() returns a sequence of property values that report the resource description according to com.sun.star.document.MediaDescriptor, specified on loading or saving with storeAsURL. The method attachResource() is used by the frame loader implementations to inform the model about its URL and MediaDescriptor.

    The current or last active controller for a model is retrieved through getCurrentController(). The corresponding method setCurrentController() sets a different current controller at models where additional controllers are available. However, additional controllers can not be created at this time for LibreOffice components using the component API. The method connectController() is used by frame loader implementations and provides the model with a new controller that has been created for it, without making it the current controller. The disconnectController() tells the model that a controller may no longer be used. Finally, the model holds back screen updates using lockControllers() and unlockControllers(). For each call to lockControllers(), there must be a call to unlockControllers() to remove the lock. The method hasControllersLocked() tells if the controllers are locked.

    The currently selected object is retrieved by a call to getCurrentSelection(). This method is an alternative to getSelection() at the com.sun.star.view.XSelectionSupplier interface supported by controller services.

    XModifiable

    The interface com.sun.star.util.XModifiable traces the modified status of a document:

    void addModifyListener ( [in] com::sun::star::util::XModifyListener aListener)
    void removeModifyListener ( [in] com::sun::star::util::XModifyListener aListener)
    boolean isModified ()
    void setModified ( [in] boolean bModified)

    XStorable

    The interface com.sun.star.frame.XStorable stores a document under an arbitrary URL or its current location. Details about how to use this interface are discussed in the chapter Handling Documents.

    XPrintable

    The interface com.sun.star.view.XPrintable is used to set and get the printer and its settings, and dispatch print jobs. These methods and special printing features for the various document types are described in the chapters Printing Text Documents, Printing Spreadsheet Documents, Printing Drawing Documents and Printing Presentation Documents.

    sequence< com::sun::star::beans::PropertyValue > getPrinter ()
    void setPrinter ( [in] sequence< com::sun::star::beans::PropertyValue > aPrinter )
    void print ( [in] sequence< com::sun::star::beans::PropertyValue > xOptions )

    XEventBroadcaster

    For versions later than 641, the optional interface com.sun.star.document.XEventBroadcaster at office documents enables developers to add listeners for events related to office documents in general, or for events specific for the individual document type.See Document Events).

    void addEventListener ( [in] com::sun::star::document::XEventListener xListener)
    void removeEventListener ( [in] com::sun::star::document::XEventListener xListener)

    The XEventListener must implement a single method, besides disposing():

    [oneway] void notifyEvent ( [in] com::sun::star::document::EventObject Event )

    The struct com.sun.star.document.EventObject has a string member EventName, that assumes one of the values specified in com.sun.star.document.Events. These events are also on the Events tab of the Tools ▸ Configure dialog.

    The general events are the same events as those provided at the XEventBroadcaster interface of the desktop. While the model is only concerned about its own events, the desktop broadcasts the events for all the loaded documents.

    XEventsSupplier

    The optional interface com.sun.star.document.XEventsSupplier binds the execution of dispatch URLs to document events, thus providing a configurable event listener as a simplification for the more general event broadcaster or listener mechanism of the com.sun.star.document.XEventBroadcaster interface. This is done programmatically versus manually in Tools ▸ Configure ▸ Events.

    XDocumentPropertiesSupplier

    The optional interface com.sun.star.document.XDocumentPropertiesSupplier provides access to document information as described in section Document Info. Document information is presented in the File ▸ Properties dialog in the GUI.

    XViewDataSupplier

    The optional com.sun.star.document.XViewDataSupplier interface sets and restores view data.

    com::sun::star::container::XIndexAccess getViewData ()
    void setViewData ( [in] com::sun::star::container::XIndexAccess aData)

    The view data are a com.sun.star.container.XIndexAccess to sequences of com.sun.star.beans.PropertyValue structs. Each sequence represents the settings of a view to the model that supplies the view data.

    Document Specific Features

    Every service specification for real model objects provides more interfaces that constitute the actual model functionality For example, a text document service com.sun.star.text.TextDocument provides text related interfaces. Having received a reference to a model, developers query for these interfaces. The com.sun.star.lang.XServiceInfo interface of a model can be used to ask for supported services. The LibreOffice document types support the following services:

    Document Service Chapter
    Calc com.sun.star.sheet.SpreadsheetDocument Spreadsheet Documents
    Draw com.sun.star.drawing.DrawingDocument Drawing Documents and Presentation Documents
    Impress com.sun.star.presentation.PresentationDocument Drawing Documents and Presentation Documents
    Math com.sun.star.formula.FormulaProperties -
    Writer (all Writer modules) com.sun.star.text.TextDocument Text Documents
    Chart com.sun.star.chart.ChartDocument and com.sun.star.chart2.ChartDocument Charts

    Refer to the related chapters for additional information about the interfaces of the documents of LibreOffice.

    Window Interfaces

    The window interfaces of the component window and container window control the LibreOffice application windows. This chapter provides a short overview.

    XWindow

    The interface com.sun.star.awt.XWindow is supported by the component and controller windows. This interface comprises methods to resize a window, control its visibility, enable and disable it, and make it the focus for input device events. Listeners are informed about window events.

    [oneway] void setPosSize ( long X, long Y, long Width, long Height, short Flags );
    com::sun::star::awt::Rectangle getPosSize ();
    
    [oneway] void setVisible ( boolean Visible );
    [oneway] void setEnable ( boolean Enable );
    [oneway] void setFocus ();
    
    [oneway] void addWindowListener ( com::sun::star::awt::XWindowListener xListener );
    [oneway] void removeWindowListener ( com::sun::star::awt::XWindowListener xListener );
    [oneway] void addFocusListener ( com::sun::star::awt::XFocusListener xListener );
    [oneway] void removeFocusListener ( com::sun::star::awt::XFocusListener xListener );
    [oneway] void addKeyListener ( com::sun::star::awt::XKeyListener xListener );
    [oneway] void removeKeyListener ( com::sun::star::awt::XKeyListener xListener );
    [oneway] void addMouseListener ( com::sun::star::awt::XMouseListener xListener );
    [oneway] void removeMouseListener ( com::sun::star::awt::XMouseListener xListener );
    [oneway] void addMouseMotionListener ( com::sun::star::awt::XMouseMotionListener xListener );
    [oneway] void removeMouseMotionListener ( com::sun::star::awt::XMouseMotionListener xListener );
    [oneway] void addPaintListener ( com::sun::star::awt::XPaintListener xListener );
    [oneway] void removePaintListener ( com::sun::star::awt::XPaintListener xListener );

    The com.sun.star.awt.XWindowListener gets the following notifications. The com.sun.star.awt.WindowEvent has members describing the size and position of the window.

    [oneway] void windowResized ( [in] com::sun::star::awt::WindowEvent e )
    [oneway] void windowMoved ( [in] com::sun::star::awt::WindowEvent e )
    [oneway] void windowShown ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowHidden ( [in] com::sun::star::lang::EventObject e );

    What the other listeners do are evident by their names.

    XTopWindow

    The interface com.sun.star.awt.XTopWindow is available at container windows. It informs listeners about top window events, and it can put itself in front of other windows or withdraw into the background. It also has a method to control the current menu bar:

    [oneway] void addTopWindowListener ( com::sun::star::awt::XTopWindowListener xListener );
    [oneway] void removeTopWindowListener ( com::sun::star::awt::XTopWindowListener xListener );
    [oneway] void toFront ();
    [oneway] void toBack ();
    [oneway] void setMenuBar ( com::sun::star::awt::XMenuBar xMenu );

    Note pin.svg

    Note:
    Although the XTopWindow interface has a method setMenuBar(), this method is not usable at this time. The com.sun.star.awt.XMenuBar interface is deprecated.

    The top window listener receives the following messages. All methods take a com.sun.star.lang.EventObject.

    [oneway] void windowOpened ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowClosing ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowClosed ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowMinimized ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowNormalized ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowActivated ( [in] com::sun::star::lang::EventObject e )
    [oneway] void windowDeactivated ( [in] com::sun::star::lang::EventObject e )

    XWindowPeer

    Each XWindow has a com.sun.star.awt.XWindowPeer. The com.sun.star.awt.XWindowPeer interface accesses the window toolkit implementation used to create it and provides the pointer of the pointing device, and controls the background color. It is also used to invalidate a window or portions of it to trigger a redraw cycle.

    com::sun::star::awt::XToolkit getToolkit ()
    [oneway] void setPointer ( [in] com::sun::star::awt::XPointer Pointer )
    [oneway] void setBackground ( [in] long Color )
    [oneway] void invalidate ( [in] short Flags )
    [oneway] void invalidateRect ( [in] com::sun::star::awt::Rectangle Rect,
    [in] short Flags )

    Creating Frames Manually

    Frame Creation

    Every time a frame is needed in LibreOffice, the com.sun.star.frame.Frame service is created. LibreOffice has an implementation for this service, available at the global service manager.

    This service can be replaced by a different implementation, for example, your own implementation in Java, by registering it at the service manager. In special cases, it is possible to use a custom frame implementation instead of the com.sun.star.frame.Frame service by instantiating a specific implementation using the implementation name with the factory methods of the service manager. Both methods can alter the default window and document handling in LibreOffice, thus changing or extending its functionality.

    Assigning Windows to Frames

    Every frame can be assigned to any LibreOffice window. For instance, the same frame implementation is used to load a component into an application window of the underlying windowing system or into a preview window of a LibreOffice dialog. The com.sun.star.frame.Frame service implementation does not depend on the type of the window, although the entirety of the frame and window will be a different object by the user.

    If you have a window in your application and want to load a LibreOffice document, create a frame and window object, and put them together by a call to initialize(). A default frame is created by instantiating an object implementing the com.sun.star.frame.Frame service at the global service manager. For window creation, the current com.sun.star.awt implementation has to be used to create windows in all languages supporting UNO. This toolkit offers a method to create window objects that wrap a platform specific window, such as a Java AWT window or a Windows system window represented by its window handle. A Java example is given below.

    Two conditions apply to windows that are to be used with LibreOffice frames.

    The first condition is that the window must be created by the current com.sun.star.awt.Toolkit service implementation. Not every object implementing the com.sun.star.awt.XWindow interface is used as an argument in the initialize() method, because it is syntactically correct, but it is restricted to objects created by the current com.sun.star.awt implementation. The insertion of a component into a frame only works if all involved windows are .xbl created by the same toolkit implementation. All internal office components, such as Writer and Calc, are implemented using the Visual Component Library (VCL), so that they do not work if the container window is not implemented by VCL. The current toolkit uses this library internally, so all the windows created by the awt toolkit are passed to a frame. No others work at this time. Using VCL directly is not recommended. The code has to be rewritten, whenever this complication has incurred by the current office implementation and is removed, and the toolkit implementation is exchangeable.

    The second condition is that if a frame and its component are supposed to get windowActivated() messages, the window object implements the additional interface com.sun.star.awt.XTopWindow. This is necessary for editing components, because the windowActivated event shows a cursor or a selection in the document. As long as this condition is met, further code is not necessary for the interaction between the frame and window, because the frame gets all the necessary events from the window by registering the appropriate listeners in the call to initialize().

    When you use the com.sun.star.awt.Toolkit to create windows, supply a com.sun.star.awt.WindowDescriptor struct to describe what kind of window is required. Set the Type member of this struct to WindowClass and the WindowServiceName member to "window" if you want to have an application window, or to "dockingwindow" if a window is need to be inserted in other windows created by the toolkit.

    Setting Components into Frame Hierarchies

    Once a frame has been initialized with a window, it can be added to a frames supplier, such as the desktop using the frames container provided by getFrames(). Its method append() inserts the new frame into the XFrames container and calls setCreator() at the new frame, passing the XFramesSupplier interface of the parent frame.

    Note pin.svg

    Note:
    The parent frame must be set as the creator of the newly created frame. The current implementation of the frames container calls setCreator() internally when frames are added to it using append().

    The following example creates a new window and a frame, plugs them together, and adds them to the desktop, thus creating a new, empty LibreOffice application window.

    // Conditions: xSMGR = m_xServiceManager
    // Get access to vcl toolkit of remote office to create
    // the container window of new target frame.
    com.sun.star.awt.XToolkit xToolkit =
      UnoRuntime.queryInterface(
          com.sun.star.awt.XToolkit.class, xSMGR.createInstance("com.sun.star.awt.Toolkit") );
    
    // Describe the properties of the container window.
    // Tip: It is possible to use native window handle of a java window
    // as parent for this. see chapter "OfficeBean" for further informations
        com.sun.star.awt.WindowDescriptor aDescriptor = new com.sun.star.awt.WindowDescriptor();
    
    aDescriptor.Type              = com.sun.star.awt.WindowClass.TOP ;
    aDescriptor.WindowServiceName = "window" ;
    aDescriptor.ParentIndex       = -1;
    aDescriptor.Parent            = null;
    aDescriptor.Bounds            = new com.sun.star.awt.Rectangle(0,0,0,0);
    
    aDescriptor.WindowAttributes  =
      com.sun.star.awt.WindowAttribute.BORDER    |
      com.sun.star.awt.WindowAttribute.MOVEABLE  |
      com.sun.star.awt.WindowAttribute.SIZEABLE  |
      com.sun.star.awt.WindowAttribute.CLOSEABLE ;
    
    com.sun.star.awt.XWindowPeer xPeer = xToolkit.createWindow(aDescriptor) ;
    
    com.sun.star.awt.XWindow xWindow = (com.sun.star.awt.XWindow)UnoRuntime.queryInterface ( com.sun.star.awt.XWindow .class, xPeer);
    // Create a new empty target frame.
    // Attention: Before OpenOffice.org build 643 we must use
    // com.sun.star.frame.Task instead of com.sun.star.frame.Frame,
    // because the desktop environment accepts only this special frame type
    // as direct children. It will be deprecated from build 643
    xFrame = UnoRuntime.queryInterface(com.sun.star.frame.XFrame.class,
            xSMGR.createInstance ("com.sun.star.frame.Task "));
    
    // Set the container window on it. xFrame.initialize(xWindow) ;
    // Insert the new frame in desktop hierarchy.
    // Use XFrames interface to do so. It provides access to the
    // child frame container of the parent node.
    // Note: append(xFrame) calls xFrame.setCreator(Desktop) automatically.
    com.sun.star.frame.XFramesSupplier xTreeRoot =
    UnoRuntime.queryInterface(com.sun.star.frame.XFramesSupplier.class,
            xSMGR.createInstance("com.sun.star.frame.Desktop") );
    
    com.sun.star.frame.XFrames xChildContainer = xTreeRoot.getFrames ();
    xChildContainer.append(xFrame) ;
    
    // Make some other initializations.
    xPeer.setBackground(0xFFFFFFFF);
    xWindow.setVisible(true);
    xFrame.setName("newly created 1") ;

    Handling Documents

    Loading Documents

    The framework API defines a simple but powerful interface to load viewable components, the com.sun.star.frame.XComponentLoader. This interface is implemented by the globally accessible com.sun.star.frame.Desktop service and also by the com.sun.star.frame.Frame service.

    Services Involved in Document Loading

    The interface com.sun.star.frame.XComponentLoader has one method:

    com::sun::star::lang::XComponent loadComponentFromURL ( [in] string aURL,
                    [in] string aTargetFrameName,
                    [in] long nSearchFlags,
                    [in] sequence < com::sun::star::beans::PropertyValue aArgs > )

    In the following sections the arguments of this call are explained. For a more detailed description of what happens when this call is executed, see the chapter about the filtering process. It explains how the various parameters in the MediaDescriptor allow to detect the type of the file to load, the best filter to load it and the document type that will receive the data. If these parameters were known already and a document should be loaded without becoming attached to a frame, a simple way to load a document exists that bypasses the detection step and the frame creation:

    • create the document by instantiating it with its UNO service name
    • get the com.sun.star.frame.XLoadable interface from the document
    • call its load method that gets the MediaDescriptor as parameter
    MediaDescriptor

    A call to loadComponentFromURL() receives a sequence of com.sun.star.beans.PropertyValue structs as a parameter, which implements the com.sun.star.document.MediaDescriptor service, consisting of property definitions. In general it describes a resource and how it shall be handled in the particular context where the MediaDescriptor is used. In the context of loading a file it describes the "from where" and the "how". In the content of storing a document using the interface com.sun.star.frame.XStorable it describes the "where to" and the "how". The table below shows the properties defined in the media descriptor.

    Some properties are used for loading and saving while others apply to one or the other. If a media descriptor is used, only a few of the members are specified. The others assume default values. Strings default to empty strings in general and interface references default to empty references. For all other properties, the default values are specified in the description column of the table.

    Some properties are tagged deprecated. There are old implementations that still use these properties. They are supported, but their use is discouraged. Use the new property that can be found in the description column of the deprecated property.

    To develop a UNO component that uses the media descriptor, note that all the properties are under control of the framework API. Never create your own property names for the media descriptor, or name clashes may be induced if the framework in a later version defines a property that uses the same name. Instead, use the ComponentData property to transport document specific information. ComponentData is specified to be an any, therefore it can be a sequence of property values by itself. If you do use it. make an appropriate specification available to users of your component.

    Properties of com.sun.star.document.MediaDescriptor
    AsTemplate boolean. Setting AsTemplate to true creates a new untitled document out of the loaded document, even if it has no template extension.

    Loading a template, that is, a document with a template extension, creates a new untitled document by default, but setting the AsTemplate property to false loads a template for editing.

    Author string. Only for storing versions in components supporting versioning: author of version.
    CharacterSet string. Defines the character set for document formats that contain single byte characters, if necessary. Which character set names are valid depends on the filter implementation, but with the current filters you can employ the character sets used for the conversion of byte to unicode strings.
    Comment string. Only for storing versions in components supporting versioning: comment (description) for stored version.
    ComponentData any. This is a parameter that is used for any properties specific for a special office component type.
    FileName - deprecated string. Same as URL (added for compatibility reasons)
    FilterData any. This is a parameter that is used for any properties specific for a special filter type.
    FilterName string. Name of a filter that should be used for loading or storing the component. Names must match the names of the typedetection configuration. Invalid names are ignored. If a name is specified on loading, it will be verified by a filter detection, but in case of doubt it will be preferred.
    FilterFlags - deprecated string. For compatibility reasons: same as FilterOptions
    FilterOptions string. Some filters need additional parameters. Use only together with property FilterName. Details must be documented by the filter. This is an old format for some filters. If a string is not enough, filters can use the property FilterData.
    Hidden boolean. Defines if the loaded component is made visible. If this property is not specified, the component is made visible by default. Prior to OpenOffice.org 2.3 a document could be loaded either "hidden" or "visible", but is wasn't possible to change the visibility of its frame afterwards (by calling setVisible() at the container window) without risking a crash later on. But starting with version 2.3 doing this is safe. Note that a "hidden" frame (or the document it contains) must be closed by the code that created it or switched it to the hidden state.
    InputStream com.sun.star.io.XInputStream. Used when loading a document. Reading must be done using this stream. If no stream is provided, the loader creates a stream by itself using the URL, version number, readonly flag, password, or anything required for stream creation, given in the media descriptor.

    The model becomes the final owner of the stream and usually holds the reference to lock the file. Therefore, it is not allowed to keep a reference to this InputStream after loading the component.It is useless, because an InputStream is only usable once for reading. Even if it implements the com.sun.star.io.XSeekable interface, do not interfere with the model's reading process. Consider all the objects involved in the loading process as temporary.

    InteractionHandler com.sun.star.task.XInteractionHandler. Object implementing the com.sun.star.task.InteractionHandler service that handles exceptional situations where proceeding with the task is impossible without additional information or impossible at all.

    LibreOffice provides a default implementation that can handle many situations. If no InteractionHandler is set, a suitable exception is thrown.

    It is not allowed to keep a reference to this object, not even in the loaded or stored components' copy of the MediaDescriptor provided by its arguments attribute.

    JumpMark string. Jump to a marked position after loading. The office document loaders expect simple strings used like targets in HTML documents.Do not use a leading # character. The meaning of a jump mark depends upon the filter, but in Writer, bookmarks can be used, whereas in Calc cells, cell ranges and named areas are supported.
    MediaType (string) string. Type of the medium to load that must match to one of the types defined in the typedetection configuration, otherwise it is ignored. The typedetection configuration can be found in several different xcu files in the config/registry/modules/org/openoffice/TypeDetection folders of the user or share tree. The MediaType is found in the "Type" property. This parameter bypasses the type detection of the desktop environment, so that passing a wrong MediaType causes load failures.
    OpenFlags - deprecated string. For compatibility reasons: string that summarizes some flags for loading. The string contains capital letters for the flags:
    • "ReadOnly" - "R"
    • "Preview" - "B"
    • "AsTemplate" - "T"
    • "Hidden" - "H"

    Use the corresponding boolean parameters instead.

    OpenNewView boolean. Affects the behavior of the component loader when a resource is already loaded. If true, the loader tries to open a new view for a document already loaded. For components supporting multiple views, a second window is opened as if the user clicked Window ▸ New Window. Other components are loaded one more time. Without this property, the default behavior of the loader applies, for example, the loader of the desktop activates a document if the user tries to load it a second time.
    Overwrite boolean. For storing only: overwrite existing files with the same name, default is true, so an com.sun.star.io.IOException occurs if the target file already exists. If the default is changed and the file exists, the UCB throws an exception. If the file is loaded through API, this exception is transported to the caller or handled by an interaction handler.
    Password string. A password for loading or storing a component, if necessary. If no password is specified, loading of a password protected document fails, storing is done without encryption.
    PostData sequence<byte>. HTTP post data to send to a location described by the media descriptor to get a result that is loaded as a component, usually in webforms. Default is: no PostData.
    PostString - deprecated string. Same as PostData, but the data is transferred as a string (just for compatibility).
    Preview boolean. Setting this to true tells the loaded component that it is loaded as a preview, so that it can optimize loading and viewing for this special purpose. Default is false.
    ReadOnly boolean. Tells if a document is to be loaded in a (logical) readonly or in read/write mode. If opening in the desired mode is impossible, an error occurs. By default, the loaded content decides what to do. If its UCB content supports a "readonly" property, the logical open mode depends on that property, otherwise it is read/write.

    This property only affects the UI. Opening a document in read only mode does not prevent the component from being modified by API calls, but all modifying functionality in the UI is disabled or removed.

    Referer

    (the wrong spelling is kept for compatibility reasons)

    string. A URL describing the environment of the request; for example,. a referrer may be the URL of a document, if a hyperlink inside this document is clicked to load another document. The referrer may be evaluated by the addressed UCB content or the loaded document.

    Without a referrer, the processing of URLs that require security checks is denied, for instance macro: URLs.

    StatusIndicator com.sun.star.task.XStatusIndicator. Object implementing the com.sun.star.task.XStatusIndicator interface that gives status information, such as text or progress, for the target frame.

    LibreOffice provides a default implementation that is retrieved by calling createStatusIndicator() at the frame you load a component into. Usually you do not need this parameter if you do not want to use any other indicator than the one in the status bar of the document window. It is not allowed to keep a reference to this object, not even in the loaded or stored component's copy of the MediaDescriptor provided by its getArgs() method.

    TemplateName string. The logical name of a template to load. Together with the TemplateRegionName property this is used instead of the URL of the template. The logical names are the template names you see in the templates dialog.
    TemplateRegionName string. See TemplateName. The template region names are the folder names you see in the templates dialog.
    Unpacked
    Warning.svg

    Warning:
    The possibility to store documents in unpacked way is not currently supported, the "Unpacked" property is just ignored, see i#64364.

    boolean. For storing: Setting this to true means that a zip file is not used to save the document. Use a folder instead for UCB contents that support folders, such as file, WebDAV, and ftp. Default is false.

    URL string. The location of the component in URL syntax.
    Version short. For components supporting versioning: the number of the version to be loaded or saved. Default is zero and means that no version is created or loaded, and the main document is processed.
    ViewData any. Data to set a special view state after loading. The type depends on the component and is retrieved from a controller object by its com.sun.star.document.XViewDataSupplier interface. Default is: no ViewData.
    ViewId short. For components supporting different views: a number to define the view that should be constructed after loading. Default is: zero, and this should be treated by the component as the default view.
    MacroExecutionMode short. How should the macro be executed - the value should be one from com.sun.star.document.MacroExecMode constants group
    UpdateDocMode short. Can the document be updated depending on links. The value should be one from com.sun.star.document.UpdateDocMode constant group

    The media descriptor used for loading and storing components is passed as an in/out parameter to some methods of objects that participate in the loading or storing process, e.g. the com.sun.star.document.TypeDetection service or a com.sun.star.document.ExtendedTypeDetection service, but also to the filter objcects involved. This enable these objects to add more information to the MediaDescriptor. As an example, if the MediaDescriptor at the beginning just contains a URL. If an object uses this URL to open a stream, it should add this stream to the MediaDescriptor (Stream, InputStream or OutputStream). This prevents that other objects have to create the stream a second time (unfortunate if a remote file is loaded) and it must be reused by later users of the MediaDescriptor.

    So if a method gets a MediaDescriptor parameter for loading content it is supposed to do the following:

    • first check for a "Stream" property
    • if it isn't available, check for an "InputStream" property
    • if you find a stream, don't expect that its position is at the beginning of the file, seek to the position where you want to start
    • only if none of these properties is available, try to create a stream by yourself from other properties, e.g. the URL property
    • if the resulting stream is seekable (supports com.sun.star.io.XSeekable, add it to the MediaDescriptor if it is an in/out parameter
    • if the resulting stream is seekable, the MediaDescriptor is an in-parameter and the stream isn't a return value, close it when you are done with it
    • if the stream is not seekable, it is a "one way read" stream and must not be added to the MediaDescriptor.

    If it gets a MediaDescriptor parameter for storing content the workflow is:

    • first check for a "Stream" property
    • if it isn't available, check for an "OutputStream" property
    • only if none of these properties is available, try to create a stream by yourself from other properties, e.g. the URL property
    • if the resulting stream is seekable, it can be used for a direct access
    • if the resulting stream isn't seekable, a temporary stream must be created for the storing process and after successful storing all content must be copied to the target stream.

    Methods that get the MediaDescriptor as in-parameter only of course can't modify it.

    Warning.svg

    Warning:
    It is not allowed to hold a member of this descriptor by reference longer than it is used. This is especially important for the stream properties, except if the ownership of the stream is unquestionable. If it isn't, the stream can't be closed and only releases its file by refcounting down to zero. So if you are in doubt whether your code is the owner of a stream that you have passed to a MediaDescriptor, don't keep any reference to it outside of that descriptor.


    URL Parameter

    The URL is part of the media descriptor and also an explicit parameter for loadComponentFromURL(). This enables script code to load a document without creating a media descriptor at the cost of code redundancy. The URL parameter of loadComponentFromURL() overrides a possible URL property passed in the media descriptor. Aside from valid URLs that describe an existing file, the following URLs are used to open viewable components in LibreOffice:

    Component URL
    Writer private:factory/swriter
    Calc private:factory/scalc
    Draw private:factory/sdraw
    Impress private:factory/simpress
    Database .component:DB/QueryDesign

    .component:DB/TableDesign
    .component:DB/RelationDesign
    .component:DB/DataSourceBrowser
    .component:DB/FormGridView

    Bibliography .component:Bibliography/View1

    Such empty documents also can created without a frame:

    Target Frame

    The URL and media descriptor loadComponentFromURL() have two additional arguments, the target frame name and search flags. The method loadComponentFromURL() looks for a frame in the frame hierarchy and loads the component into the frame it finds. It uses the same algorithm as findFrame() at the com.sun.star.frame.XFrame interface, described in section Frame Hierarchies.

    The target frame name is a reserved name starting with an underscore or arbitrary name. The reserved names denote frequently used frames in the frame hierarchy or special functions, whereas an arbitrary name is searched recursively. If a reserved name is used, the search flags are ignored and set to 0. The following reserved names are supported:

    _self
    Returns the frame itself. The same as with an empty target frame name. This means to search for a frame you already have, but it is legal.
    _top
    Returns the top frame of the called frame .,The first frame where isTop() returns true when traveling up the hierarchy. If the starting frame does not have a parent frame, the call is treated as a search for "_self". This behavior is compatible to the frame targeting in a web browser.
    _parent
    Returns the next frame above in the frame hierarchy. If the starting frame does not have a parent frame, the call is treated as a search for "_self". This behavior is compatible to the frame targeting in a web browser.
    _blank
    Creates a new top-level frame as a child frame of the desktop. If the called frame is not part of the desktop hierarchy, this call fails. Using the "_blank" target loads open documents again that result in a read-only document, depending on the UCB content provider for the component. If loading is done as a result of a user action, this becomes confusing to the users, therefore the "_default" target is recommended in calls from a user interface, instead of "_blank". Refer to the next section for a discussion about the _default target.
    _default
    Similar to "_blank", but the implementation defines further behavior that has to be documented by the implementer. The com.sun.star.frame.XComponentLoader implemented at the desktop object shows the following default behavior.
    First, it checks if the component to load is already loaded in another top-level frame. If this is the case, the frame is activated and brought to the foreground. When the OpenNewView property is set to true in the media descriptor, the loader creates a second controller to show another view for the loaded document. For components supporting this, a second window is opened as if the user clicked Window - New Window. The other components are loaded one more time, as if the "_blank" target had been used. Currently, almost all office components implementing com.sun.star.frame.XModel have multiple controllers, except for HTML and writer documents in the online view. The database and bibliography components have no model, therefore they cannot open a second view at all and OpenNewView leads to an exception with them.
    Next, the loader checks if the active frame contains an unmodified, empty document of the same document type as the component that is being loaded. If so, the component is loaded into that frame, replacing the empty document, otherwise a new top-level frame is created similar to a call with "_blank".

    Names starting with an underscore must not be used as real names for a frame.

    If the given frame name is an arbitrary string, the loader searches for this frame in the frame hierarchy. The search is done in the following order: self, children, siblings, parent, create if not found. Each of these search steps can be skipped by deleting it from the com.sun.star.frame.FrameSearchFlag bit vector:

    Constants in com.sun.star.frame.FrameSearchFlag group
    SELF search current frame
    CHILDREN search children recursively
    SIBLINGS search frames on the same level
    PARENT search frame above the current frame in the hierarchy
    CREATE create new frame if not found
    TASKS do not stop searching when a top frame is reached, but continue with other top frames
    ALL CHILDREN | SIBLINGS | PARENT
    GLOBAL CHILDREN | SIBLINGS | PARENT | TASKS

    A typical case for a named frame is a situation where a frame is needed to be reused for subsequent loading of components, for example, a frame attached to a preview window or a docked frame, such as the frame in LibreOffice that opens the address book when the F4 key is pressed.

    The frame names "_self", "_top" and "_parent" define a frame target relative to a starting frame. They can only be used if the component loader interface finds the frame and the setComponent() can be used with the frame. The desktop frame is the root, therefore it does not have a top and parent frame. The component loader of the desktop cannot use these names, because the desktop refuses to have a component set into it. However, if a frame implemented com.sun.star.frame.XComponentLoader, these names could be used.

    Note pin.svg

    Note:
    OpenOffice.org 1.0.x didn't have a frame implementation that supports XComponentLoader.

    The reserved frame names are also used as a targeting mechanism in the dispatch framework with regard to as far as the relative frame names being resolved. For additional information, see chapter Using the Dispatch Framework.

    The example below creates a frame, and uses the target frame and search flag parameters of loadComponentFromURL() to load a document into it.

    // Conditions: sURL = "private:factory/swriter"
    // xSMGR = m_xServiceManager
    // xFrame = reference to a frame
    // lProperties[] = new com.sun.star.beans.PropertyValue[0]
    
    // First prepare frame for loading.
    // We must adress it inside the frame tree without any complications.
    // To do so we set an unambiguous name and use it later.
    // Don't forget to reset the name to the original name after that.
    
    String sOldName = xFrame.getName();
    String sTarget = "odk_officedev_desk";
    xFrame.setName(sTarget);
    
    // Get access to the global component loader of the office
    // for synchronous loading of documents.
    com.sun.star.frame.XComponentLoader xLoader =
      UnoRuntime.queryInterface(com.sun.star.frame.XComponentLoader.class,
        xSMGR.createInstance("com.sun.star.frame.Desktop"));
    
    // Load the document into the target frame by using our unambigous name
    // and special search flags.
    xDocument = xLoader.loadComponentFromURL(
      sURL, sTarget, com.sun.star.frame.FrameSearchFlag.CHILDREN, lProperties);
    
    // dont forget to restore old frame name ...
    xFrame.setName(sOldName);

    The loadComponentFromURL() call returns a reference to a com.sun.star.lang.XComponent interface. The object belonging to this interface depends on the loaded component. If it is a component that only provides a component window, but not a controller, the returned component is this window. If it is an office component that provides a controller, the returned component is the controller or its model, if these is one. All Writer, Calc, Draw, Impress or Math documents in LibreOffice support a model, therefore the loadComponentFromURL() call returns it. The database and bibliography components however, return a controller, because they do not have a model.

    Closing Documents

    The loadComponentFromURL() has previously been discussed. The return value is a reference to a com.sun.star.lang.XComponent interface, the corresponding object is a disposable component, and the caller must take care of its lifetime. An XComponent supports the following methods:

    void dispose ()
    void addEventListener ( [in] com::sun::star::lang::XEventListener xListener)
    void removeEventListener ( [in] com::sun::star::lang::XEventListener xListener)

    In principle, there is a simple rule. The documentation of a com.sun.star.lang.XComponent specifies the objects that can own a component. Normally, a client using an XComponent is the owner of the XComponent and has the responsibility to dispose of it or it is not the owner. If it is not the owner, it may add itself as a com.sun.star.lang.XEventListener at the XComponent and not call dispose() on it. This type of XEventListener supports one method in which a component reacts upon the fact that another component is about to be disposed of:

    void disposing ( [in] com::sun::star::lang::EventObject Source )

    However, the frame, controller and model are interwoven tightly, and situations do occur in which there are several owners, for example, if there is more than one view for one model, or one of these components is in use and cannot be disposed of, for example, while a print job is running or a modal dialog is open. Therefore, developers must cope with these situations and remember a few things concerning the deletion of components.

    Closing a document has two aspects. It is possible that someone else wants to close a document being currently worked on And you may want to close a component someone else is using at the same time. Both aspects are discussed in the following sections. A code example that closes a document is provided at the end of this section.

    Reacting Upon Closing

    The first aspect is that someone else wants to close a component for which you hold a reference. In the current version of LibreOffice, there are three possibilities.

    • If the component is used briefly as a stack variable, you do not care about the component after loading, or you are sure there will be no interference, it is justifiable to load the component without taking further measures. If the user is going to close the component, let the reference go out of scope, or release the reference when no longer required.
    • If a reference is used, but it is not necessary to react when it becomes invalid and the object supports com.sun.star.uno.XWeak, you can hold a weak reference instead of a hard reference. Weak references are automatically converted to null if the object they reference is going to be disposed. Because the generic frame implementation, and also the controllers and models of all standard document types implement XWeak, it is recommended to use it when possible.
    • If a hard reference is held or you want to know that the component has been closed and the new situation has to be accommodated, add a com.sun.star.lang.XEventListener at the com.sun.star.lang.XComponent interface. In this case, release the reference on a disposing() notification.

    Sometimes it is necessary to exercise more control over the closing process. For this an optional interface com.sun.star.util.XCloseable has been introduced. If the object you are referencing is a XCloseable, register it as a com.sun.star.util.XCloseListener and throw acom.sun.star.util.CloseVetoException when prompted to close. Since XCloseable is specified as an optional interface for frames and models, do not assume that this interface is supported. It is possible that the code runs with a LibreOffice version where frames and models do not implement XCloseable. Therefore, be prepared for the case when you receive null when you try to query XCloseable. The XCloseable interface is described in more detail below.

    How to Trigger Closing

    The second aspect - to close a view of a component or the entire viewable component yourself - is more complex. The necessary steps depend on how you want to treat modified documents.

    Warning.svg

    Warning:
    If a component supports XCloseable, you must not use any closing procedure other than calling close.


    The following three diagrams show the decisions to be made when closing a frame or a document model. The important points are: if you expect modifications, you must either handle them using com.sun.star.util.XModifiable and com.sun.star.frame.XStorable, or let the user do the necessary interaction by calling suspend() on the controller. In any case, check if the frame or model is an XCloseable and prefer close() over a call to dispose(). The first two diagrams illustrate the separate closing process for frames and models, the third diagram covers the actual termination of frames and models.

    Closing a Frame
    Closing a Model
    Terminate Frame/Model
    XCloseable

    The dispose mechanism has shortcomings in complex situations, such as the frame-controller-model interaction. The dispose call cannot be rejected, but as shown above, sometimes it is necessary to prevent destruction of objects due to shared ownership or a state of the documents that forbids destruction.

    A closing mechanism is required that enables all involved objects to negotiate if deletion is possible and to veto, if necessary. By offering the interface com.sun.star.util.XCloseable, a component tells it must be destroyed by calling close(). Calling dispose() on an XCloseable might lead to deadlocks or crash the entire application.

    In LibreOffice, model or frame objects are possible candidates for implementing the interface XCloseable, therefore query for that interface before destroying the object. Call dispose() directly if the model or frame does not support the interface, thus declaring that it handles all the problems.

    An object implementing XCloseable registers close listeners. When a close request is received, all listeners are asked for permission. If a listener wants to deprecate, it throws an exception derived fromcom.sun.star.util.CloseVetoException containing the reason why the component can not be closed. This exception is passed to the close requester. The XCloseable itself can veto the destruction by throwing an exception. If there is no veto, the XCloseable calls dispose() on itself and returns.

    The XCloseable handles problems that occur if a component rejects destruction. A script programmer usually can not cope with a component not used anymore that refuses to be destroyed. Ensure that the component is destroyed to avoid a memory leak. The close() method offers a method to pass the responsibility to close the object to any possible close listener that vetoes closing or to the XCloseable if the initial caller is not able to stay in memory to try again later. This responsibility is referred to as delivered ownership. The mechanism sets some constraints on the possible reasons for an objection against a close request.

    A close listener that is asked for permission can object for any reason if the close call does not force it to assume ownership of the closeable object.The close requester is aware of a possible failure. If the close call forces the ownership, the close listener must be careful. An objection is only allowed if the reason is temporary. As soon as the reason no longer exists, the owner automatically calls close on the object that should be closed, now being in the same situation as the initial close requester.

    A permanent reason for objection is not allowed. For example, the document being modified is not a valid reason to object, because it is unlikely that the document becomes unmodified by itself. Consequently, it could never be closed. Therefore, if an API programmer wants to avoid data loss, he must use the com.sun.star.util.XModifiable and com.sun.star.frame.XStorable interfaces of the document. The fact that a model refuses to be closed if it is modified is not dependable.

    The interface com.sun.star.util.XCloseable inherits from com.sun.star.util.XCloseBroadcaster and has the following methods:

    [oneway] void addCloseListener ( [in] com::sun::star::util::XCloseListener Listener );
    [oneway] void removeCloseListener ( [in] com::sun::star::util::XCloseListener Listener );
    void close ( [in] boolean DeliverOwnership )

    The com.sun.star.util.XCloseListener is notified twice when close() is called on an XClosable :

    void queryClosing ( [in] com::sun::star::lang::EventObject Source,
                        [in] boolean GetsOwnership )
    void notifyClosing ( [in] com::sun::star::lang::EventObject Source )

    close() and queryClosing() throw acom.sun.star.util.CloseVetoException.

    In the closing negotiations, an XClosable is asked to close itself. In the call to close(), the caller passes a boolean parameter DeliverOwnership to tell the XClosable that it will give up ownership in favor of an XCloseListener, or the XCloseable that might have to finish a job first, but will close the XClosable immediately when the job is completed.

    After a call to close(), the XClosable notifies its listeners twice. First, it checks if it can be closed. If not, it throws a CloseVetoException, otherwise it uses queryClosing() to see if a listener has any objections against closing. The value of DeliverOwnership is conveyed in the GetsOwnership parameter of queryClosing(). If no listener disapproves of closing, the XClosable exercises notifyClosing() on the listeners and disposes itself. The result of a call to close() on a model is that all frames, controllers and the model itself are destroyed. The result of a call to close() on a frame is that this frame is closed, but the model stays alive if there are other controllers.

    If an XCloseListener does not agree on closing, it throws a CloseVetoException, and the XClosable lets the exception pass in close(), so that the caller receives the exception. The CloseVetoException tells the caller that closing failed. If the caller delegated its ownership in the call to close() by setting the DeliverOwnership parameter to true, an XCloseListener knows that it automatically assumes ownership by throwing a CloseVetoException. The caller knows that someone else is now the owner if it receives a CloseVetoException. The new owner is compelled to close the XClosable as soon as possible. If the XCloseable was the object that threw an exception, it is compelled also to close itself as soon as possible.

    Note pin.svg

    Note:
    No API exists for trivial components. As a consequence, components are not allowed to do anything that prevents them from being destroyed. For example, since the office crashes when a container window or component window has an open modal dialog, every component that wants to open a modal dialog must implement the com.sun.star.frame.XController interface.

    If a model object supports XCloseable, calling dispose() on it is forbidden, try to close() the XCloseable and catch a possible CloseVetoException. Components that cannot cope with a destroyed model add a close listener at the model. This enables them to object when the model receives a close() request. They also add as a close listener if they are not already added as an (dispose) event listener. This can be done by every controller object that uses that model. It is also possible to let the model iterate through its controllers and call their suspend() methods explicitly as a part of its implementation of the close method. It is only necessary to know that a method close() must be called to close the model with its controllers. The method the model chooses is an implementation detail.

    The example below closes a loaded document component. It does not save modified documents or prompts the user to save.

    // Conditions: xDocument = m_xLoadedDocument
    // Check supported functionality of the document (model or controller).
    com.sun.star.frame.XModel xModel =
      UnoRuntime.queryInterface(com.sun.star.frame.XModel.class,xDocument);
    
    if(xModel!=null)
    {
      // It is a full featured office document.
      // Try to use close mechanism instead of a hard dispose().
      // But maybe such service is not available on this model.
      com.sun.star.util.XCloseable xCloseable =
        UnoRuntime.queryInterface(com.sun.star.util.XCloseable.class,xModel);
    
    if(xCloseable!=null)
    {
      try
        {
          // use close(boolean DeliverOwnership)
          // The boolean parameter DeliverOwnership tells objects vetoing the close process that they may
          // assume ownership if they object the closure by throwing a CloseVetoException
          // Here we give up ownership. To be on the safe side, catch possible veto exception anyway.
          xCloseable.close(true);
        }
        catch(com.sun.star.util.CloseVetoException exCloseVeto)
        {
        }
    }
    // If close is not supported by this model - try to dispose it.
    // But if the model disagree with a reset request for the modify state
    // we shouldn't do so. Otherwhise some strange things can happen.
    else
    {
        com.sun.star.lang.XComponent xDisposeable =
          UnoRuntime.queryInterface(
            com.sun.star.lang.XComponent.class,xModel);
            xDisposeable.dispose();
          }
          catch(com.sun.star.beans.PropertyVetoException exModifyVeto)
          {
          }
        }
      }
    }

    Storing Documents

    After loading an office component successfully, the returned interface is used to manipulate the component. Document specific interfaces, such as the interfaces com.sun.star.text.XTextDocument, com.sun.star.sheet.XSpreadsheetDocument or com.sun.star.drawing.XDrawPagesSupplier are retrieved using queryInterface().

    If the office component supports the com.sun.star.frame.XStorable interface applying to every component implementing the service com.sun.star.document.OfficeDocument, it can be stored:

    void store ( )
    void storeAsURL ( [in] string sURL,
                      [in] sequence< com::sun::star::beans::PropertyValue > lArguments )
    void storeToURL ( [in] string sURL,
                      [in] sequence< com::sun::star::beans::PropertyValue > lArguments )
    boolean hasLocation ()
    string getLocation ()
    boolean isReadonly ()

    The XStorable offers the methods store(), storeAsURL() and storeToURL() for storing. The latter two methods are called with a media descriptor.

    The method store() overwrites an existing file. Calling this method on a document that was created from scratch using a private:factory/... URL leads to an exception.

    The other two methods storeAsURL() and storeToURL() leave the original file untouched and differ after the storing procedure. The storeToURL() method saves the current document to the desired location without touching the internal state of the document. The method storeAsURL sets the Modified attribute of the document, accessible through its com.sun.star.util.XModifiable interface, to false and updates the internal media descriptor of the document with the parameters passed in the call. This changes the document URL.

    The following example exports a Writer document, Writer/Web document or Calc sheet to HTML.

    // Conditions: sURL      = "file:///home/target.htm"
    //             xDocument = m_xLoadedDocument
    // Export can be achieved by saving the document and using
    // a special filter which can write the desired format.
    // Normally this filter should be searched inside the filter
    // configuration (using service com.sun.star.document.FilterFactory)
    // but here we use well known filter names directly.
    String sFilter = null;
    
    // Detect document type by asking XServiceInfo
      com.sun.star.lang.XServiceInfo xInfo =
    UnoRuntime.queryInterface (com.sun.star.lang.XServiceInfo.class, xDocument);
    // Determine suitable HTML filter name for export.
    if(xInfo!=null)
    {
    if(xInfo.supportsService ("com.sun.star.text.TextDocument"))
        sFilter = "HTML (StarWriter)";
    else
    if(xInfo.supportsService ("com.sun.star.text.WebDocument"))
        sFilter = "HTML";
    else
    if(xInfo.supportsService ("com.sun.star.sheet.SpreadsheetDocument"))
        sFilter = "HTML (StarCalc)";
    }
    if(sFilter!=null)
    {
    // Build necessary argument list for store properties.
    // Use flag "Overwrite" to prevent exceptions, if file already exists.
    com.sun.star.beans.PropertyValue[] lProperties =
        new com.sun.star.beans.PropertyValue[2];
    lProperties[0]       = new com.sun.star.beans.PropertyValue();
    lProperties[0].Name  = "FilterName";
    lProperties[0].Value = sFilter;
    lProperties[1]       = new com.sun.star.beans.PropertyValue();
    lProperties[1].Name  = "Overwrite";
    lProperties[1].Value = Boolean.TRUE;
    
      com.sun.star.frame.XStorable xStore =
    UnoRuntime.queryInterface (com.sun.star.frame.XStorable.class, xDocument);
      xStore.storeAsURL (sURL, lProperties);
    }

    If a model is loaded or stored successfully, all parts of the media descriptor not explicitly excluded according to the media descriptor table in section MediaDescriptor must be provided by the methods getURL() and getArgs() in the com.sun.star.frame.XModel interface. The separation of the URL and the other arguments is used, because the URL is often the most wanted part for its performance optimized access.

    Tip.svg

    Tip:
    The XModel offers a method attachResource() that changes the media descriptor of the document, but this method should only be used in special cases, for example, by the implementer of a new document model and controller. The method attachResource() does not force reloading of the document. Validation checks are done when a document is loaded through MediaDescriptor. For example, if the resource is write protected, add Readonly to the MediaDescriptor and the filter name must match the data. A possible use for attachResource() could be creating a document from a template, where after loading successfully, the document's resource is changed to an "unnamed" state by deleting the URL.


    Printing Documents

    Printing revolves around the interface com.sun.star.view.XPrintable. Its methods and special printing features for the various document types are described in the document chapters Printing Text Documents, Printing Spreadsheet Documents, Printing Drawing Documents and Printing Presentation Documents.

    Using the Dispatch Framework

    The component framework with the Frame-Controller-Model paradigm builds the skeleton of the global object structure. Other frameworks are defined that enrich the communication between an office component and the desktop environment. Usually they start at a frame object for the frame anchors an office component in the desktop environment.

    One framework is the dispatch framework. Its main purpose is to define interfaces for a generic communication between an office component and a user interface. This communication process handles requests for command executions and gives information about the various attributes of an office component. Generic means that the user interface does not have to know all the interfaces supported by the office component. The user interface sends messages to the office component and receives notifications.The messages use a simple format. The entire negotiation about supported commands and parameters can happen at runtime, while an application using the specialized interfaces of the component is bound to these interfaces at compile time. This generic approach is achieved by looking at an office component differently, not as objects with method-based interfaces, but as slot machines that take standardized command tokens.

    We have discussed the differences between the different document types. The common functionality covers the generic features, that is, an office component is considered to be the entirety of its controller, its model and many document-specific interfaces. To implement a user interface for a component, it would be closely bound to the component and its specialized interfaces. If different components use different interfaces and methods for their implementations, similar functions cannot be visualized by the same user interface implementation. For instance, an action like Edit ▸ Select All leads to different interface calls depending on the document type it is sent to. From a user interface perspective, it would be better to define abstract descriptions of the actions to be performed and let the components decide how to handle these actions, or not to handle. These abstract descriptions and how to handle them is specified in the dispatch framework.

    Command URL

    In the dispatch framework, every possible user action is defined as an executable command, and every possible visualization as a reflection of something that is exposed by the component is defined as an attribute. Every executable command and every attribute is a feature of the office component, and the dispatch framework gives every feature a name called command URL. It is represented by a com.sun.star.util.URL struct.

    Command URLs are strings that follow the protocol_scheme:protocol_specific_part pattern. Public URL schemes, such as file: or http can be used here. Executing a request with a URL that points to a location of a document means that this document is loaded. In general, both parts of the command URL can be arbitrary strings, but a request cannot be executed if there is an object that does not know how to handle its command URL.

    Processing Chain

    A request is created by any object.User interface objects can create requests. Consider a toolbox where different functions acting on the office component are presented as buttons. When a button is clicked, the desired functionality is executed. If the code assigned to the button is provided with a suitable command URL, it handles the user action by creating the request and finding a component that can handle it. The button handler does not require any prior knowledge of the component and how it would go about its task.

    This situation is handled by the design pattern chain of responsibility. Everything a component needs to know to execute a request is the last link of a chain of objects capable of executing requests. If this object gets the request, it checks if it can handle it or passes it to the next chain member until the request is executed, or the end of the chain is reached.

    The chain members in the dispatch framework are objects implementing the interface com.sun.star.frame.XDispatchProvider. Every frame and controller supports it.In the simplest case, the chain consists of two members, a frame and its controller, but concatenating several chain parts on demand of a frame or a controller is possible. Once called, a controller passes on the call, that is, it can use internal frames created by its implementation. A frame also passes the call to other objects, for example, its parent frame.

    The current implementation of the chain is different from a simple chain. A frame is always the leading chain member and must be called initially, but in the default implementation used in LibreOffice, the frame first asks its controller before it goes on with the request. Other frame implementations handle this in a different way. Other chain members are inserted into the call sequence before the controller uses the dispatch interception capability of a frame. The developers should not rely on any particular order inside the chain.

    The dispatch framework uses a generic approach to describe and handle requests with a lose coupling between the participating objects. To work correctly, it is necessary to follow certain rules:

    1. Every chain starts at a frame, and this object decides if it passes on the call to its controller. The controller is not called directly from the outside. This is not compulsory for internal usage of the dispatch API inside an office component implementation. The two reasons for this rule are:
    • A frame provides a com.sun.star.frame.XDispatchProviderInterception interface, where other dispatch providers dock. The frame implementation guarantees that these interceptors are called before the frame handles the request or passes it to the controller. This allows a sophisticated customization of the dispatch handling.
    • If a component is placed into a context where parts of its functionality are not to be exposed to the outside, a special frame implementation is used to suppress or handle requests before they are passed to the controller. This frame can add or remove arguments to requests and exchange them.
    1. A command URL is parsed into a com.sun.star.util.URL struct before passing it to a dispatch provider, because it is assumed that the call is passed on to several objects. Having a pre-parsed URL saves parsing the command string repeatedly. Parsing means that the members Complete, Main, Protocol and at least one more member of the com.sun.star.util.URL struct, depending on the given protocol scheme have to be set. Additional members are set if the concrete URL protocol supports them. For well known protocol schemes and protocol schemes specific to LibreOffice, the service com.sun.star.util.URLTransformer is used to fill the struct from a command URL string. For other protocols, the members are set explicitly, but it is also possible to write an extended version of the URLTransformer service to carry out URL parsing. An extended URLTransformer must support all protocols supported by the default URLTransformer implementation, for example, by instantiating the old implementation by its implementation name and forwarding all known URLs to it, except URLs with new protocols.

    The dispatch framework connects an object that creates a request with another object that reacts on the request. In addition, it provides feedback to the requester. It can tell if the request is currently allowed or not. If the request acts on a specific attribute of an object, it provides the current status of this attribute. Altogether, this is called status information, represented by a com.sun.star.frame.FeatureStateEvent struct. This information is reflected in a user interface by enabling or disabling controls to show their availability, or by displaying the status of objects. For example, a pressed button for the bold attribute of text, or a numeric value for the text height in a combo box.

    The com.sun.star.frame.XDispatchProvider interface does not handle requests, but delegates every request to an individual dispatch object implementing com.sun.star.frame.XDispatch.

    Note pin.svg

    Note:
    This is the concept, but the implementation is not forced and it may decide to return the same object for every request. It is not recommended using the dispatch provider object as a dispatch object.

    Dispatch Process

    This section describes the necessary steps to handle dispatch providers and dispatch objects. The illustration below shows the services and interfaces of the the Dispatch framework.

    Dispatch Framework
    Getting a Dispatch Object

    First, create a command URL that represents the desired functionality ensuring that it is parsed as described above. Tables with possible command URLs for the default office components of LibreOffice are located in the appendix.

    Request the com.sun.star.frame.XDispatchProvider interface of the frame that contains the office component for a dispatch object for the command URL by calling its queryDispatch() method.

    com::sun::star::frame::XDispatch queryDispatch ( [in] com::sun::star::util::URL URL,
                          [in] string TargetFrameName,
                          [in] long SearchFlags )
    sequence< com::sun::star::frame::XDispatch > queryDispatches (
                          [in] sequence< com::sun::star::frame::DispatchDescriptor > Requests )

    The additional parameters (TargetFrameName, SearchFlags) of this call are only used for dispatching public URL schemes, because they specify a target frame and frame search mode to the loading process. Valid target names and search flags are described in the section Target Frame. The targets "_self", "_parent" and "_top" are well-defined, so that they can be used, because a queryDispatch() call starts at a frame object. Using frame names or search flags with command URLs does not have any meaning in the office components in LibreOffice.

    You receive a dispatch object that supports at least com.sun.star.frame.XDispatch:

    [oneway] void dispatch ( [in] com::sun::star::util::URL URL,
                              [in] sequence< com::sun::star::beans::PropertyValue > Arguments )
    [oneway] void addStatusListener ( [in] com::sun::star::frame::XStatusListener Control,
                              [in] com::sun::star::util::URL URL )
    [oneway] void removeStatusListener ( [in] com::sun::star::frame::XStatusListener Control,
                              [in] com::sun::star::util::URL URL )

    Listening for Status Information

    If a dispatch object is received, add a listener for status events by calling its addStatusListener() method. A com.sun.star.frame.XStatusListener implements:

    [oneway] void statusChanged ( [in] com::sun::star::frame::FeatureStateEvent Event )

    Keep a reference to the dispatch object until you call the removeStatusListener() method, because it is not sure that any other object will keep it alive. If a status listener is not registered, because you want to dispatch a command, and are not interested in status events, release all references to the dispatch object immediately after usage. If a dispatch object is not received, the desired functionality is not available. If you have a visual user interface element that represents that functionality, disable it.

    If a status listener is registered and there is status information, a com.sun.star.frame.FeatureStateEvent is received immediately after registering the listener. Status information is still received later if the status changes and you are still listening. The IsEnabled member of the com.sun.star.frame.FeatureStateEvent tells you if the functionality is currently available, and the State member holds information about a status that could be represented by UI elements. Its type depends on the command URL. A boolean status information is visualized in a pressed or not pressed look of a toolbox button. Other types need complex elements, such as combo boxes or spinfields embedded in a toolbox that show the current font and font size. If the State member is empty, the action does not have an explicit status, such as the menu item File ▸ Print. The current status can be ambiguous, because more than one object is selected, and the objects are in a different status, for example. selected text that is partly formatted bold and partly regular.

    A special event is a status event where the Requery flag is set. This is a request to release all references to the dispatch object and to ask the dispatch provider for a new object, because the old one has become invalid. This allows the office components to accommodate internal context changes. It is possible that a dispatch object is not received, because the desired functionality has become unavailable.

    If you do not get any status information in your statusChanged() implementation, assume that the functionality is always available, but has no explicit status.

    If you are no longer interested in status events, use the removeStatusListener() method and release all references to the dispatch object. You may get a disposing() callback from the dispatch object when it is going to be destroyed. It is not necessary to call removeStatusListener(). Ensure that you do not hold any references to the dispatch object anymore.

    Listening for Context Changes

    Sometimes internal changes, for example, traveling from a text paragraph to a text table, or selecting a different type of object, force an office component to invalidate all referenced dispatch objects and provides other dispatch objects, including dispatches for command URLs it could not handle before. The component then calls the contextChanged() method of its frame, and the frame broadcasts the corresponding com.sun.star.frame.FrameActionEvent. For this reason, register a frame action listener using addFrameActionListener() at frames you want dispatch objects. Refer to section Frame Actions for additional information. If the listener is called back with a CONTEXT_CHANGED event, release all dispatch objects and query new dispatch objects for every command URL you require. You can also try command URLs that did not get a dispatch object before.

    If you are no longer interested in context changes of a frame, use the removeFrameActionListener() method of the frame to deregister and release all references to the frame. If you get a disposing() request from the frame in between, it is not necessary to call removeFrameActionListener(), but you must release all frame references you are currently holding.

    Dispatching a Command

    If the desired functionality is available, execute it by calling the dispatch() method of the dispatch object. This method is called with the same command URL you used to get it, and optionally with a sequence of arguments of type com.sun.star.beans.PropertyValue that depend on the command. It is not redundant that supplied the URL again, because it is allowed to use one dispatch object for many command URLs. The appendix shows the names and types for the parameters. However, the command URLs for simple user interface elements, such as menu entries or toolbox buttons send no parameters. Complex user interface elements use parameters, for example, a combo box in a toolbar that changes the font height.

    // Conditions: sURL = "private:factory/swriter"
    // lProperties = new com.sun.star.beans.PropertyValue[0]
    // xSMGR = m_xServiceManager
    // xListener = this
    
    // xFrame = a given frame
    // Query the frame for right interface which provides access to all
    // available dispatch objects.
    com.sun.star.frame.XDispatchProvider xProvider =
      UnoRuntime.queryInterface (
          com.sun.star.frame.XDispatchProvider .class, xFrame );
    
    // Create and parse a valid URL
    // Note: because it is an in/out parameter we must use an array of URLs
    com.sun.star.util.XURLTransformer xParser =
      UnoRuntime.queryInterface (
        com.sun.star.util.XURLTransformer .class,
          xSMGR.createInstance("com.sun.star.util.URLTransformer" ));
    
    com.sun.star.util.URL[] aParseURL = new com.sun.star.util.URL[1];
    aParseURL[0]          = new com.sun.star.util.URL();
    aParseURL[0].Complete = sURL;
    xParser.parseStrict (aParseURL);
    
    // Ask for dispatch object for requested URL and use it.
    // Force given frame as target "" which means the same like "_self".
    xDispatcher = xProvider.queryDispatch(aParseURL[0],"",0);
    if(xDispatcher!=null)
    {
      xDispatcher.addStatusListener (xListener,aParseURL[0]);
      xDispatcher.dispatch (aParseURL[0],lProperties);
    }

    Dispatch Results

    Every dispatch object implements optional interfaces. An important extension is the com.sun.star.frame.XNotifyingDispatch interface for dispatch results. The dispatch() call is a void method and should be treated as an asynchronous or oneway call. Therefore a dispatch result can not be passed as a return value; rather, a callback interface is necessary. The interface that provides dispatch results by a callback is the com.sun.star.frame.XNotifyingDispatch interface:

    [oneway] void dispatchWithNotification ( [in] com::sun::star::util::URL URL,
                                [in] sequence< com::sun::star::beans::PropertyValue > Arguments,
                                [in] com::sun::star::frame::XDispatchResultListener Listener )

    Its method dispatchWithNotification() takes a com.sun.star.frame.XDispatchResultListener interface that is called after a dispatched URL has been executed.

    Warning.svg

    Warning:
    Although the dispatch process is considered to be asynchronous, this is not necessarily so. Therefore, be prepared to get the dispatch result notification before the dispatch call returns.


    The dispatch result is transferred as a com.sun.star.frame.DispatchResultEvent struct in the callback method dispatchFinished(). The State member of this struct tells if the dispatch was successful or not, while the Result member contains the value that would be returned if the call had been executed as a synchronous function call. The appendix shows the types of return values. If a public URL is dispatched, the dispatch result is a reference to the frame the component was loaded into.

    // Conditions: sURL        = "private:factory/swriter"
    //             lProperties = new com.sun.star.beans.PropertyValue[0]
    //             xSMGR       = m_xServiceManager
    //             xListener   = this
    
    // Query the frame for right interface which provides access to all
    // available dispatch objects.
    com.sun.star.frame.XDispatchProvider xProvider =
      UnoRuntime.queryInterface (
        com.sun.star.frame.XDispatchProvider .class, xFrame);
    
    // Create and parse a valid URL
    // Note: because it is an in/out parameter we must use an array of URLs
    com.sun.star.util.XURLTransformer xParser =
      UnoRuntime.queryInterface(
        com.sun.star.util.XURLTransformer .class,
          xSMGR.createInstance ("com.sun.star.util.URLTransformer "));
    
    // Ask for right dispatch object for requested URL and use it.
    // Force given frame as target "" which means the same like "_self".
    // Attention: The interface XNotifyingDispatch is an optional one!
    com.sun.star.frame.XDispatch xDispatcher =
      xProvider.queryDispatch (aURL,"",0);
    com.sun.star.frame.XNotifyingDispatch xNotifyingDispatcher =
      UnoRuntime.queryInterface (
        com.sun.star.frame.XNotifyingDispatch.class , xDispatcher );
    
    if(xNotifyingDispatcher!=null)
      xNotifyingDispatcher.dispatchWithNotification (aURL, lProperties, xListener);

    Dispatch Interception

    The dispatch framework described in the last chapter establishes a communication between a user interface and an office component. Both can be LibreOffice default components or custom components. Sometimes it is not necessary to replace a UI element by a new implementation. It can be sufficient to influence its visualized state or to redirect user interactions to external code. This is the typical use for dispatch interception.

    The dispatch communication works in two directions: status information is transferred from the office component to the UI elements and user requests travel from the UI element to the office component. Both go through the same switching center, that is, an object implementing com.sun.star.frame.XDispatch. The UI element gets this object by calling queryDispatch() at the frame containing the office component, and usually receives an object that connects to code inside the frame, the office component or global services in LibreOffice. The frame offers an interface that is used to return third-party dispatch objects that provide the UI element with status updates. For example, it is possible to disable a UI element that would not be disabled otherwise. Another possibility is to write replacement code that is called by the UI element if the user performs a suitable action.

    Dispatch objects are provided by objects implementing the com.sun.star.frame.XDispatchProvider interface, and that is the interface you are required to implement. There is an extra step where the dispatch provider must be attached to the frame to intercept the dispatching communication, therefore the dispatch provider becomes a part of the chain of responsibility described in the previous section. This is accomplished by implementing com.sun.star.frame.XDispatchProviderInterceptor.

    This chain usually only consists of the frame and the controller of the office component it contains, but the frame offers the com.sun.star.frame.XDispatchProviderInterception interface where other providers are inserted. They are called before the frame tries to find a dispatch object for a command URL, so that it is possible to put the complete dispatch communication in a frame under external control. More than one interceptor can be registered, thus building a bigger chain.

    Routing every dispatch through the whole chain becomes a performance problem, because there could be scores of possible clients asking for a dispatch object. For this reason, there is also an API that limits the routing procedure to particular commands or command groups. This is described below.

    Once the connection is established, the dispatch interceptor decides how requests for a dispatch object are dealt with. When asked for a dispatch object for a Command URL, it can:

    • Return an empty interface that disables the corresponding functionality.
    • Pass the request to the next chain member, called slave dispatch provider described below if it is not interested in that functionality.
    • Handle the request and return an object implementing com.sun.star.frame.XDispatch. As described in the previous chapter, client objects may register at this object as status event listeners. The dispatch object returns any possible status information as long as the type of the "State" member in the com.sun.star.frame.FeatureStateEvent struct has one of the expected types, otherwise the client requesting the status information can not handle it properly. The expected types must be documented together with the existing commands.For example, if a menu entry wants status information, it handles a void, meaning nothing special should be done, or a boolean state by displaying a check mark, but nothing else. The status information could contain a disable directive. Note that a dispatch object returns status information immediately when a listener registers. Any change events can be broadcast at arbitrary points in time.
    • The returned dispatch object is also used by client objects to dispatch the command that matches the command URL. The dispatch object receiving this request checks if the code it wants to execute is valid under the current conditions. It is not sufficient to rely on disable requests, because a client is not forced to register as a status listener if it wants to dispatch a request.

    The slave dispatch provider and master dispatch provider in the com.sun.star.frame.XDispatchProviderInterceptor interface are a bit obscure at first. They are two pointers to chain members in both directions, next and previous, where the first and last member in the chain have special meanings and responsibilities.

    The command dispatching passes through a chain of dispatch providers, starting at the frame. If the frame is answered to include an interceptor in this chain, the frame inserts the interceptor in the chain and passes the following chain member to the new chain member, so that calls are passed along the chain if it does not want to handle them.

    If any interceptor is deregistered, the frame puts the loose ends together by adjusting the master and slave pointer of the chain successor and predecessor of the element that is going to be removed from the chain. All of them are interceptors, so only the last slave is a dispatch provider.

    The frame takes care of the whole chain in the register or deregister of calls in the dispatch provider interceptor, so that the implementer of an interceptor does not have to be concerned with the chain construction.

    Examples

    In Java: https://opengrok.libreoffice.org/xref/core/odk/examples/DevelopersGuide/OfficeDev/DesktopEnvironment/Interceptor.java

    In Basic:

    Global g_oInterceptor
    Global g_oSlaveDispatchProvider
    Global g_oMasterDispatchProvider
    Global g_oFrame
    Global g_oDispatch
    
    Sub AddInterceptor
        g_oInterceptor = CreateUnoListener("MyInterceptor_", "com.sun.star.frame.XDispatchProviderInterceptor")
        g_oFrame = ThisComponent.CurrentController.Frame
        g_oFrame.registerDispatchProviderInterceptor(g_oInterceptor)
        
        g_oDispatch = CreateUnoListener("MyDispatcher_", "com.sun.star.frame.XDispatch")
    End Sub
    
    Sub MyInterceptor_setSlaveDispatchProvider (oSDP)
        g_oSlaveDispatchProvider = oSDP
    End Sub
    
    Sub MyInterceptor_setMasterDispatchProvider (oMDP)
        g_oMasterDispatchProvider = oMDP
    End Sub
    
    Function MyInterceptor_queryDispatch(oUrl as com.sun.star.util.URL, sTargetFrameName As String, lSearchFlags As Long) As Variant
        oDisp = g_oSlaveDispatchProvider.queryDispatch(oUrl, sTargetFrameName, lSearchFlags)
        MyInterceptor_queryDispatch = oDisp
        
        Select Case oUrl.complete
          Case ".uno:Italic", ".uno:Bold", ".uno:Save", ".uno:InsertSymbol"
            MyInterceptor_queryDispatch = g_oDispatch
          Case Else
            ' do nothing
        End Select
    End Function
    
    Sub MyDispatcher_dispatch(oUrl as com.sun.star.util.URL, lArgs() as com.sun.star.beans.PropertyValue) 
      Select Case oUrl.complete
        Case ".uno:Italic", ".uno:Save"
          Msgbox oUrl.complete & ", ignoring"
        Case ".uno:Bold", ".uno:InsertSymbol"
          Msgbox oUrl.complete & ", executing"
          oDisp = g_oSlaveDispatchProvider.queryDispatch( oUrl, sTargetFrameName, lSearchFlags )
          oDisp.dispatch(oUrl, lArgs) 
        Case Else
        ' do nothing
      End Select
    end Sub
    
    Sub MyDispatcher_addStatusListener(l as com.sun.star.frame.XStatusListener, aUrl as com.sun.star.util.URL) 
    
    end Sub
    
    Sub MyDispatcher_removeStatusListener(l as com.sun.star.frame.XStatusListener, aUrl as com.sun.star.util.URL) 
    
    end Sub

    Java Window Integration

    This section discusses experiences obtained during the development of Java-LibreOffice integration. Usually, developers use the OfficeBean for this purpose. The following provides background information about possible strategies to reach this goal.

    There are multiple possibilities to integrate local windows with LibreOffice windows. This chapter shows the integration of LibreOffice windows into a Java bean environment. Some of this information may be helpful with other local window integrations.

    The Window Handle

    An important precondition is the existence of a system window handle of the own Java window. For this, use a java.awt.Canvas and the following JNI methods:

    • a method to query the window handle (HWND on Windows, X11 ID on UNIX)
    • a method to identify the operating system, for example, UNIX, Windows, or Macintosh

    For an example, see bean/com/sun/star/beans/LocalOfficeWindow.java

    The two methods getNativeWindow() and getNativeWindowSystemType() are declared and exported, but implemented for windows in bean/native/win32/com_sun_star_beans_LocalOfficeWindow.c through JNI

    Warning.svg

    Warning:
    It has to be a java.awt.Canvas. These JNI methods cannot be implemented at a Swing control, because it does not have its own system window. You can use a java.awt.Canvas in a Swing container environment.


    Note pin.svg

    Note:
    The handle is not available before the window is visible, otherwise the JNI function does not work. One possibility is to cache the handle and set it in show() or setVisible().

    Using the Window Handle

    The window handle create the LibreOffice window. There are two ways to accomplish this:

    A Hack

    This option is mentioned because there are situations where this is the only feasible method. The knowledge of this option can help in other situations.

    Add the UNO interface com.sun.star.awt.XWindowPeer so that it is usable for the LibreOffice window toolkit. This interface can have an empty implementation. In createWindow(), another interface com.sun.star.awt.XSystemDependentWindowPeer is expected that queries the HWND. Thus, XWindowPeer is for transporting and com.sun.star.awt.XSystemDependentWindowPeer queries the HWND.

    This method gets a com.sun.star.awt.XWindow as a child of your own Java window, that is used to initialize a com.sun.star.frame.XFrame.

    com.sun.star.awt.XToolkit xToolkit =
            UnoRuntime.queryInterface(com.sun.star.awt.XToolkit.class,
            xSMGR.createInstance("com.sun.star.awt.Toolkit"));
    
    // this is the canvas object with the JNI methods
    aParentView = ...
    // some JNI methods cannot work before this
    aParentView.setVisible(true);
    
    // now wrap the canvas (JavaWindowPeerFake) and add the necessary interfaces
    com.sun.star.awt.XWindowPeer xParentPeer =
            UnoRuntime.queryInterface(com.sun.star.awt.XWindowPeer.class,
                  new JavaWindowPeerFake(aParentView));
    
    com.sun.star.awt.WindowDescriptor aDescriptor = new
    com.sun.star.awt.WindowDescriptor();
    aDescriptor.Type              = com.sun.star.awt.WindowClass.TOP;
    aDescriptor.WindowServiceName = "workwindow";
    aDescriptor.ParentIndex       = 1;
    aDescriptor.Parent            = xParentPeer;
    aDescriptor.Bounds            = new com.sun.star.awt.Rectangle(0,0,0,0);
    
    if (aParentView.getNativeWindowSystemType()== com.sun.star.lang.SystemDependent.SYSTEM_WIN32)
          aDescriptor.WindowAttributes = com.sun.star.awt.WindowAttribute.SHOW;
    else
          aDescriptor.WindowAttributes = com.sun.star.awt.WindowAttribute.SYSTEMDEPENDENT;
    
    // now the toolkit can create an com.sun.star.awt.XWindow
    com.sun.star.awt.XWindowPeer xPeer = xToolkit.createWindow( aDescriptor );
    com.sun.star.awt.XWindow xWindow =
            (com.sun.star.awt.XWindow)UnoRuntime.queryInterface(com.sun.star.awt.XWindow.class, xPeer);

    Legal Solution

    The com.sun.star.awt.Toolkit service implements the interface com.sun.star.awt.XSystemChildFactory with a method createSystemChild(). This accepts an any with a wrapped HWND or X Window ID, as long and the system type, such as Windows, Java, and UNIX directly. Here you create an com.sun.star.awt.XWindow. This method cannot be used in LibreOffice build versions before src642, because the process ID parameter is unknown to the Java environment. Newer versions do not check this parameter, thus this new, method works.

    Warning.svg

    Warning:
    As a user of createSystemChild() ensure that your client (Java application) and your server (LibreOffice) use the same display. Otherwise the window handle is not interchangeable.


    com.sun.star.awt.XToolkit xToolkit =
            UnoRuntime.queryInterface(com.sun.star.awt.XToolkit.class,
                  xSMGR.createInstance("com.sun.star.awt.Toolkit"));
    
    // this is the canvas with the JNI functions
    aParentView = ...
    // some JNI funtions will not work without this
    aParentView.setVisible(true);
    
    // no wrapping necessary, simply use the HWND
    com.sun.star.awt.XSystemChildFactory xFac =
            UnoRuntime.queryInterface(com.sun.star.awt.XSystemChildFactory.class,
                  xToolkit);
    Integer nHandle = aParentView.getHWND();
    byte[] lIgnoredProcessID = new byte[0];
    com.sun.star.awt.XWindowPeer xPeer =
            xFac.createSystemChild(
                    (Object)nHandle,
                    lIgnoredProcessID,
                    com.sun.star.lang.SystemDependent.SYSTEM_WIN32);
    com.sun.star.awt.XWindow xWindow =
            (com.sun.star.awt.XWindow)UnoRuntime.queryInterface(com.sun.star.awt.XWindow.class, xPeer);

    Note pin.svg

    Note:
    The old method still works and can be used, but it should be considered deprecated. If in doubt, implement both and try the new method at runtime. If it does not work, try the hack.

    Resizing

    Another difficulty is resizing the window. Normally, the child window expects resize events of the parent. The child does not resize it window, because it must know the layout of the parent window. The VCL, LibreOffice's windowing engine creates a special system child window, thus we can resize windows.

    The parent window can be filled "full size" with the child window, but only for UNIX and not for Windows. The VCL's implementation is system dependent.

    The bean deals with this issue by adding another function to the local library. Windows adds arbitrary properties to an HWND. You can also subclass the window, that is, each Windows window has a function pointer or callback to the function that performs the event handling (WindowProcedure). Using this, it is possible to treat events by calling your own methods. This is useful whenever the window is not created by you and you need to influence the behavior of the window.

    In this case, the Java window has not been created by us, but we need to learn about resize events to forward these to the LibreOffice window. Look at the file bean/native/win32/com_sun_star_beans_LocalOfficeWindow.c, and find the method OpenOfficeWndProc(). In the first call of the JNI function Java_com_sun_star_beans_LocalOfficeWindow_getNativeWindow() of this file, the own handler is applied to the foreign window.

    Warning.svg

    Warning:
    The old bean implementation had a bug that is fixed in newer versions. If you did not check if the function pointer was set, and called Java_com_sun_star_beans_LocalOfficeWindow_getNativeWindow() multiple times, you created a chain of functions that called each other with the result of an endless recursion leading to a stack overflow. If the own handler is already registered, it is now marked in one of the previously mentioned properties registered with an HWND:


    In the future, VCL will do this sub-classing by itself, even on Windows. This will lead to equal behavior between Windows and UNIX.

    The initial size of the window is a related problem. If a canvas is connected with a LibreOffice window, set both sizes to a valid, positive value, otherwise the LibreOffice window will not be visible. If you are using a non-product build of OpenOffice.org, you see an assertion failed "small world isn't it". This might change when the sub-classing is done by VCL in the future.

    There is still one unresolved problem. The code mentioned above works with Java 1.3, but not for Java 1.4. There, the behavior of windows is changed. Where Java 1.3 sends real resize events from the own WindowProc, Java 1.4 does a re-parenting. The canvas window is destroyed and created again. This leads to an empty window with no LibreOffice window. This problem is under investigation.

    More Remote Problems

    There are additional difficulties to window handles and local window handles. Some personal experiences of one of the LibreOffice authors are provided:

    • Listeners in Java should be implemented in a thread. The problem is that SolarMutex, a mutex semaphore of LibreOffice, one-way UNO methods and the global Java GUI thread do not work together.
    • The Java applet should release its listeners. If they stay in the containers of LibreOffice after the Java process ends, UNO throws a com.sun.star.lang.DisposedException, which are not caught correctly. Java does not know destructors, therefore it is a difficult to follow this advice. One possibility is to register a Thread object at java.Runtime as a ShutDownHook. This is called even when CTRL-C is pressed on the command line where you can deregister the listeners. Because listeners are threads, there is some effort.

    Common Application Features

    Clipboard

    This chapter introduces the usage of the clipboard service com.sun.star.datatransfer.clipboard.SystemClipboard. The clipboard serves as a data exchange mechanism between LibreOffice custom components, or between custom components and external applications. It is usually used for copy and paste operations.

    Note pin.svg

    Note:
    Note: The architecture of the LibreOffice clipboard service is strongly conforming to the Java clipboard specification.

    Different platforms use different methods for describing data formats available on the clipboard. Under Windows, clipboard formats are identified by unique numbers, for example, under X11, a clipboard format is identified by an ATOM. To have a platform independent mechanism, the LibreOffice clipboard supports the concept of DataFlavors. Each instance of a DataFlavor represents the opaque concept of a data format as it would appear on a clipboard. A DataFlavor defined in com.sun.star.datatransfer.DataFlavor has three members:

    Members of com.sun.star.datatransfer.DataFlavor
    MimeType A string that describes the data. This string must conform to Rfc2045 and Rfc2046 with one exception. The quoted parameter may contain spaces. In section Apache OpenOffice Clipboard Data Formats, a list of common DataFlavors supported by LibreOffice is provided.
    HumanPresentableName The human presentable name for the data format that this DataFlavor represents.
    DataType The type of the data. In section Apache OpenOffice Clipboard Data Formats there is a list of common DataFlavors supported by LibreOffice and their corresponding DataType.

    The carrier of the clipboard data is a transferable object that implements the interface com.sun.star.datatransfer.XTransferable. A transferable object offers one or many different DataFlavors.

    Using the Clipboard

    Pasting Data

    The following Java example demonstrates the use of the clipboard service to paste from the clipboard.

    import com.sun.star.datatransfer.*;
    import com.sun.star.datatransfer.clipboard.*;
    import com.sun.star.uno.AnyConverter;
    ...
    
    // instantiate the clipboard service
    
    Object oClipboard =
            xMultiComponentFactory.createInstanceWithContext(
            "com.sun.star.datatransfer.clipboard.SystemClipboard",
            xComponentContext);
    
    // query for the interface XClipboard
    
    XClipboard xClipboard = (XClipboard)
            UnoRuntime.queryInterface(XClipboard.class, oClipboard);
    
    //---------------------------------------------------
    // get a list of formats currently on the clipboard
    //---------------------------------------------------
    
    XTransferable xTransferable = xClipboard.getContents();
    
    DataFlavor[] aDflvArr = xTransferable.getTransferDataFlavors();
    
    // print all available formats
    
    System.out.println("Reading the clipboard...");
    System.out.println("Available clipboard formats:");
    
    DataFlavor aUniFlv = null;
    
    for (int i=0;i<aDflvArr.length;i++)
    {
        System.out.println( "MimeType: " +
                aDflvArr[i].MimeType +
                " HumanPresentableName: " +
                aDflvArr[i].HumanPresentableName );
    
        // if there is the format unicode text on the clipboard save the
        // corresponding DataFlavor so that we can later output the string
    
        if (aDflvArr[i].MimeType.equals("text/plain;charset=utf-16"))
        {
                aUniFlv = aDflvArr[i];
        }
    }
    
    System.out.println("");
    
    try
    {
        if (aUniFlv != null)
        {
            System.out.println("Unicode text on the clipboard...");
            Object aData = xTransferable.getTransferData(aUniFlv);
    
            System.out.println(AnyConverter.toString(aData));
        }
    }
    catch(UnsupportedFlavorException ex)
    {
        System.err.println( "Requested format is not available" );
    }
    
    ...

    Copying Data

    To copy to the clipboard, implement a transferable object that supports the interface com.sun.star.datatransfer.XTransferable. The transferable object offers arbitrary formats described by DataFlavors.

    The following Java example demonstrates the implementation of a transferable object. This transferable object contains only one format, unicode text.

    //---------------------------------------
    // A simple transferable containing only
    // one format, unicode text
    //---------------------------------------
    public class TextTransferable implements XTransferable
    {
        public TextTransferable(String aText)
        {
            text = aText;
        }
    
        // XTransferable methods
        public Object getTransferData(DataFlavor aFlavor) throws UnsupportedFlavorException
        {
            if ( !aFlavor.MimeType.equalsIgnoreCase( UNICODE_CONTENT_TYPE ) )
                  throw new UnsupportedFlavorException();
            return text;
        }
        public DataFlavor[] getTransferDataFlavors()
        {
            DataFlavor[] adf = new DataFlavor[1];
            DataFlavor uniflv = new DataFlavor(
                    UNICODE_CONTENT_TYPE,
                    "Unicode Text",
                    new Type(String.class) );
            adf[0] = uniflv;
    
            return adf;
        }
        public boolean isDataFlavorSupported(DataFlavor aFlavor)
        {
            return aFlavor.MimeType.equalsIgnoreCase(UNICODE_CONTENT_TYPE);
        }
    
        // members
        private final String text;
        private final String UNICODE_CONTENT_TYPE = "text/plain;charset=utf-16";
    }

    Everyone providing data to the clipboard becomes a clipboard owner. A clipboard owner is an object that implements the interface com.sun.star.datatransfer.clipboard.XClipboardOwner. If the current clipboard owner loses ownership of the clipboard, it receives a notification from the clipboard service. The clipboard owner can use this notification to destroy the transferable object that was formerly on the clipboard. If the transferable object is a self-destroying object, destroying clears all references to the object. If the clipboard service is the last client, clearing the reference to the transferable object leads to destruction.

    All data types except for text have to be transferred as byte array. The next example shows this for a bitmap.

    public class BmpTransferable implements XTransferable
    {
        public BmpTransferable(byte[] aBitmap)
        {
            mBitmapData = aBitmap;
        }
    
        // XTransferable methods
        public Object getTransferData(DataFlavor aFlavor) throws UnsupportedFlavorException
        {
            if ( !aFlavor.MimeType.equalsIgnoreCase(BITMAP_CONTENT_TYPE ) )
                throw new UnsupportedFlavorException();
    
            return mBitmapData;
        }
        public DataFlavor[] getTransferDataFlavors()
        {
            DataFlavor[] adf = new DataFlavor[1];
            DataFlavor bmpflv= new DataFlavor(
                BITMAP_CONTENT_TYPE,
                "Bitmap",
                new Type(byte[].class) );
            adf[0] = bmpflv;
    
            return adf;
        }
        public boolean isDataFlavorSupported(DataFlavor aFlavor)
        {
            return aFlavor.MimeType.equalsIgnoreCase(BITMAP_CONTENT_TYPE);
        }
    
        // members
        private byte[] mBitmapData;
        private final String BITMAP_CONTENT_TYPE = "application/x-openoffice;windows_formatname="Bitmap"";
    }

    The following Java example shows an implementation of the interface com.sun.star.datatransfer.clipboard.XClipboardOwner.

    ...
    
    //----------------------------------------
    // A simple clipboard owner implementation
    //----------------------------------------
    
    public class ClipboardOwner implements XClipboardOwner
    {
        public void lostOwnership(
            XClipboard xClipboard,
            XTransferable xTransferable )
        {
            System.out.println("");
            System.out.println( "Lost clipboard ownership..." );
            System.out.println("");
    
            isowner = false;
        }
    
        public boolean isClipboardOwner()
        {
            return isowner;
        }
    
        private boolean isowner = true;
    }
    
    ...

    The last two samples combined show how it is possible to copy data to the clipboard as demonstrated in the following Java example.

    import com.sun.star.datatransfer.*;
    import com.sun.star.datatransfer.clipboard.*;
    import com.sun.star.uno.AnyConverter;
    ...
    // instantiate the clipboard service
    
    Object oClipboard =
            xMultiComponentFactory.createInstanceWithContext(
            "com.sun.star.datatransfer.clipboard.SystemClipboard",
            xComponentContext);
    
    // query for the interface XClipboard
    XClipboard xClipboard = UnoRuntime.queryInterface(XClipboard.class, oClipboard);
    //---------------------------------------------------
    // becoming a clipboard owner
    //---------------------------------------------------
    System.out.println("Becoming a clipboard owner...");
    System.out.println("");
    ClipboardOwner aClipOwner = new ClipboardOwner();
    
    xClipboard.setContents(new TextTransferable("Hello World!"), aClipOwner);
    while (aClipOwner.isClipboardOwner())
    {
        System.out.println("Still clipboard owner...");
        Thread.sleep(1000);
    }
    ...

    Becoming a Clipboard Viewer

    It is useful to listen to clipboard changes. User interface controls may change their visible appearance depending on the current clipboard content. To avoid polling on the clipboard, the clipboard service supports an asynchronous notification mechanism. Every client that needs notification about clipboard changes implements the interface com.sun.star.datatransfer.clipboard.XClipboardListener and registers as a clipboard listener. Implementing the interface com.sun.star.datatransfer.clipboard.XClipboardListener is simple as the next Java example demonstrates.

    //----------------------------
    // A simple clipboard listener
    //----------------------------
    public class ClipboardListener implements XClipboardListener
    {
        public void disposing(EventObject event)
        {
        }
    
        public void changedContents(ClipboardEvent event)
        {
            System.out.println("");
            System.out.println("Clipboard content has changed!");
            System.out.println("");
        }
    }

    If the interface was implemented by the object, it registers as a clipboard listener. A clipboard listener deregisters if clipboard notifications are no longer necessary. Both aspects are demonstrated in the next example.

    // instantiate the clipboard service
    Object oClipboard =
            xMultiComponentFactory.createInstanceWithContext(
            "com.sun.star.datatransfer.clipboard.SystemClipboard",
            xComponentContext);
    // query for the interface XClipboard
    XClipboard xClipboard = (XClipboard)
            UnoRuntime.queryInterface(XClipboard.class, oClipboard);
    //---------------------------------------------------
    // registering as clipboard listener
    //---------------------------------------------------
    XClipboardNotifier xClipNotifier = (XClipboardNotifier)
            UnoRuntime.queryInterface(XClipboardNotifier.class, oClipboard);
    ClipboardListener aClipListener= new ClipboardListener();
    xClipNotifier.addClipboardListener(aClipListener);
    ...
    //---------------------------------------------------
    // unregistering as clipboard listener
    //---------------------------------------------------
    xClipNotifier.removeClipboardListener(aClipListener);
    ...

    LibreOffice Clipboard Data Formats

    This section describes common clipboard data formats that LibreOffice supports and their corresponding DataType.

    As previously mentioned, data formats are described by DataFlavors. The important characteristics of a DataFlavor are the MimeType and DataType. The LibreOffice clipboard service uses a standard MimeType for different data formats if there is one registered at Iana. For example, for HTML text, the MimeType "text/html" is used, Rich Text uses the MimeType "text/richtext", and text uses "text/plain". If there is no corresponding MimeType registered at Iana, LibreOffice defines a private MimeType. Private LibreOffice MimeType always has the MimeType "application/x-openoffice". Each private LibreOffice MimeType has a parameter "windows_formatname" identifying the clipboard format name used under Windows. The used Windows format names are the format names used with older LibreOffice versions. Common Windows format names are "Bitmap", "GDIMetaFile", "FileName", "FileList", and "DIF". The DataType of a DataFlavor identifies how the data are exchanged. There are only two DataTypes that can be used. The DataType for Unicode text is a string, and in Java, String.class, For all other data formats, the DataType is a sequence of bytes in Java byte[].class.

    The following table lists common data formats, and their corresponding MimeType and DataTypes:

    Form MimeType DataType (in Java) Description
    Unicode Text text/plain;charset=utf-16 String.class Unicode Text
    Richtext text/richtext byte[].class Richtext
    Bitmap application/x-openoffice;windows_formatname="Bitmap" byte[].class A bitmap in OpenOffice bitmap format.
    HTML Text text/html byte[].class HTML Text

    Internationalization

    The I18N framework provides interfaces to access locale-dependent data (e.g. calendar data, currency) and methods (e.g. collation and transliteration). The I18N framework offers full-featured internationalization functionality that covers a range of geographic locations that include East Asia (China, Japan, and Korea, or CJK), Europe, Middle East (Hebrew, Arabic) and South-East Asia (Thai, Indian). Also, the I18N framework builds on the component model UNO, thus making the addition of new internationalization components easy.

    Introduction

    The I18N framework contains a lot of data and many interfaces and methods not important to developers of external code using the LibreOffice API, but only for developers of the LibreOffice application itself. This chapter is split into two parts, one that gives a short overview on using the API and is restricted to what is useful to external developers, and a second part that focuses on how to implement a new locale supporting the API (Note that this section does not cover how to translate and localize the LibreOffice resources).

    Overview and Using the API

    XLocaleData

    The com.sun.star.i18n.XLocaleData interface provides access to locale-specific information, such as decimal separators, group (thousands) separators, currency information, calendar data, and number format codes. No further functionality is discussed.

    XCharacterClassification

    The com.sun.star.i18n.XCharacterClassification interface is used to determine the Unicode type of a character (such as uppercase, lowercase, letter, digit, punctuation) or the script type. It also provides methods to perform simple uppercase to lowercase and lowercase to upper case conversions that are locale-dependent but do not need real transliteration. An example of locale-dependent case conversion is the Turkish lowercase i to uppercase I-dot and lowercase i-dotless to uppercase I conversion, as opposed to the western lowercase i to uppercase I conversion.

    Another provided functionality is parsing methods to isolate and determine identifiers, numbers, and quoted strings in a given string. See the description of com.sun.star.i18n.XCharacterClassification methods parseAnyToken() and parsePredefinedToken(). The parser uses com.sun.star.i18n.XLocaleData to obtain the locale-dependent decimal and group separators.

    XCalendar

    The com.sun.star.i18n.XCalendar interface enables the application to use any calendar available for a given locale, not being restricted to the Gregorian calendar. You may query the interface for the available calendars for a given locale with method getAllCalendars() and load one of the available calendars using method loadCalendar(), or you may use the default calendar loaded with method loadDefaultCalendar(). Normally, a Gregorian calendar is available with the name "gregorian" in the Name field of com.sun.star.i18n.Calendar even if the default calendar is not a Gregorian calendar, but this is not mandatory. Available calendars are obtained through the com.sun.star.i18n.XLocaleData interface.

    Warning.svg

    Warning:
    You must initially load a calendar before using any of the interface methods that perform calendar calculations.


    XExtendedCalendar

    The com.sun.star.i18n.XExtendedCalendar interface was introduced with OpenOffice.org 1.1.0 and provides additional functionality to display locale and calendar dependent calendar values. This interface is derived from com.sun.star.i18n.XCalendar. The interface provides a method to obtain display strings of date parts for specific calendars of a specific locale.

    XNumberFormatCode

    The com.sun.star.i18n.XNumberFormatCode interface provides access to predefined number format codes for a given locale, which in turn are obtained through the com.sun.star.i18n.XLocaleData interface. Normally you do not need to bother with it because the application's number formatter Number Formats manages the codes. It just might serve to get the available codes and determine default format codes of a specific category.

    XNativeNumberSupplier

    The com.sun.star.i18n.XNativeNumberSupplier interface was introduced with OpenOffice.org 1.1.0 and provides functionality to convert between ASCII Arabic digits/numeric strings and native numeral strings, such as Korean number symbols.

    XCollator

    The com.sun.star.i18n.XCollator interface provides locale-dependent collation algorithms for sorting purposes. There is at least one collator algorithm available per locale, though there may be more than one, for example dictionary and telephone algorithms, or stroke, radical, pinyin in Chinese locales. There is always one default algorithm for each locale that may be loaded using method loadDefaultCollator(), and all available algorithms may be queried with method listCollatorAlgorithms() of those a selected algorithm may be loaded using loadCollatorAlgorithm(). The available collator implementations and options are obtained through the com.sun.star.i18n.XLocaleData interface.

    Warning.svg

    Warning:
    You must initially load an algorithm prior to using any of the compare…() methods, otherwise the result will be 0 indicating any comparison being equal.


    Note pin.svg

    Note:
    Since collation may be a very time consuming procedure, use it only for user-visible data, for example for sorted lists. If, for example, you only need a case insensitive comparison without displaying the results to the user, use the com.sun.star.i18n.XTransliteration interface instead.

    XTransliteration

    The com.sun.star.i18n.XTransliteration interface provides methods to perform locale-dependent character conversions, such as case conversions, conversions between Hiragana and Katakana, and Half-width and Full-width. Transliteration is also used by the collators if, for example, a case insensitive sort is to be performed. The available transliteration implementations are obtained through the com.sun.star.i18n.XLocaleData interface.

    Warning.svg

    Warning:
    You must initially load a transliteration module prior to using any of the transliterating or comparing methods, otherwise the result is unpredictable.


    Note pin.svg

    Note:
    If you only need to determine if two strings are equal for a specific transliteration (for example a case-insensitive comparison) use the equals() method instead of the compare…() methods, it may have a faster implementation.

    XTextConversion

    The com.sun.star.i18n.XTextConversion interface provides methods to perform locale-dependent text conversions, such as Hangul/Hanja conversion for Korean, or translation between Chinese simplified and Chinese traditional.

    XBreakIterator

    The com.sun.star.i18n.XBreakIterator interface may be used to traverse the text in character mode or word mode, to jump to the beginning or to the end of a sentence, to find the beginning or the end of a given script type, and, as the name suggests, to determine a line break position, optionally using a com.sun.star.linguistic2.XHyphenator. The service implementation obtains lists of forbidden characters (characters that are not allowed at the beginning or the end of a line in certain locales) through the com.sun.star.i18n.XLocaleData interface. The XBreakIterator interface also offers methods to determine the script type of a character or to find the beginning or end of a script type along a sequence of characters.

    XIndexEntrySupplier

    The com.sun.star.i18n.XIndexEntrySupplier interface may be used to obtain information on index entries to generate a "table of alphabetical index" for a given locale. Since not all languages are alphabetical in the western sense (for example, CJK languages), different methods are needed.

    XExtendedIndexEntrySupplier

    The com.sun.star.i18n.XExtendedIndexEntrySupplier interface was introduced with OpenOffice.org 1.1.0 and provides additional functionality to generate index entries for languages that need phonetically sorted indexes, such as Japanese. The interface is derived from com.sun.star.i18n.XIndexEntrySupplier.

    The com.sun.star.i18n.XInputSequenceChecker interface was introduced with OpenOffice.org 1.1.0 and provides input sequence checking for Thai and Hindi.

    Implementing a New Locale

    XLocaleData

    One of the most important tasks in implementing a new locale is to define all the locale data to be used, listed in the following table as types returned by the com.sun.star.i18n.XLocaleData interface methods:

    Type Count
    com.sun.star.i18n.LanguageCountryInfo exactly 1
    com.sun.star.i18n.LocaleDataItem exactly 1
    sequence<com.sun.star.i18n.Calendar> 1 or more
    sequence<com.sun.star.i18n.Currency> 1 or more
    sequence<com.sun.star.i18n.FormatElement> at least all com.sun.star.i18n.NumberFormatIndex format codes (see below)
    sequence<com.sun.star.i18n.Implementation> collator implementations 0 or more, if none specified the ICU collator will be called for the language given in <LanguageCountryInfo>
    sequence<string> search options (transliteration modules) 0 or more
    sequence<string> collation options (transliteration modules) 0 or more
    sequence<string> names of supported transliterations (transliteration modules) 0 or more
    com.sun.star.i18n.ForbiddenCharacters exactly 1, though may have empty elements
    sequence<string> reserved words all words of com.sun.star.i18n.reservedWords
    sequence<com.sun.star.beans.PropertyValues> numbering levels

    (no public XLocaleData API method available, used by and accessible through com.sun.star.text.XDefaultNumberingProvider method getDefaultContinuousNumberingLevels() implemented in i18npool)

    exactly 8 <NumberingLevel> entities
    sequence<com.sun.star.container.XIndexAccess> outline styles

    (no public XLocaleData API method available, used by and accessible through com.sun.star.text.XDefaultNumberingProvider method getDefaultOutlineNumberings() implemented in i18npool )

    exactly 8 <OutlineStyle> entities consisting of 5 <OutlineNumberingLevel> entities each

    Locale data is defined in an XML file. It is translated into a C++ source file during the build process, which is compiled and linked together with other compiled locale data files into shared libraries. The contents of the XML file, their elements, and how they are to be defined are described in i18npool/source/localedata/data/locale.dtd. The latest revision available for a specific CVS branch of that file provides up-to-date information about the definitions, as well as additional information.

    If the language-country combination is not already listed in tools/inc/lang.hxx and tools/source/intntl/isolang.cxx and svx/source/dialog/langtab.src, LibreOffice is probably not prepared to deal with your specific locale. For assistance, you can consult https://openoffice.apache.org/translate.html and join the dev@openoffice.apache.org mailing list (see also https://openoffice.apache.org/mailing-lists.html#localization-mailing-list-public).

    In order to conform with the available build infrastructure, the name of your locale data file should follow the conventions used in the i18npool/source/localedata/data directory: <language>_<country>.xml, where language is a lowercase, two letter ISO-639 code, and country is an uppercase two letter ISO-3166 code. Start by copying the en_US.xml file to your <language>_<country>.xml file and adapt the entries to suit your needs. Add the corresponding *.cxx and *.obj target file name to the i18npool/source/localedata/data/makefile.mk. Note that there is an explicit rule defined, so that you do not need to add the *.xml file name anywhere. You must also add the locale to the aDllsTable structure located in i18npool/source/localedata/data/localedata.cxx. Make sure to specify the correct library name, since it must correspond to the library name used in the makefile. Finally, the public symbols to be exported must be added to the linker map file corresponding to the library. You can use the i18npool/source/localedata/data/linkermapfile-check.awk script to assist you. Instructions for how to use the script are located the header comments of the file.

    <LC_FORMAT><FormatElement>

    To be able to load documents of versions up to and including StarOffice 5.2 (old binary file format), each locale must define all number formats mentioned in com.sun.star.i18n.NumberFormatIndex and assign the proper formatindex="..." attribute.
    Failing to do so may result in data not properly displayed or not displayed at all if a built-in "System" or "Default" format code was used (as generally done by the average user) and the document is loaded under a locale not having those formats defined. Since old versions did merge some format information of the [Windows] Regional Settings, it might be necessary to define some duplicated codes to fill all positions. To verify that all necessary elements are defined, use a non-product build of OpenOffice.org and open a number formatting dialog, and select your locale from the Language list box. An assertion message box appears if there are any missing elements. The errors are only shown the very first time the locale is selected in a given document.

    <LC_FORMAT><FormatElement><FormatCode>

    In general, definition of number format codes follows the user visible rules, apart from that any non-ASCII character must be entered using UTF-8 encoding. For a detailed description of codes and a list of possible keywords please consult the LibreOffice English online help on section "number format codes".
    Be sure to use the separators you declared in the <LC_CTYPE> section in the number format codes, for example <DecimalSeparator>, <ThousandSeparator>, otherwise the number formatter generates incorrect formats.
    Verify the defined codes again by using the number formatter dialog of a non-product OpenOffice build. If anything is incorrect, an assertion message box appears containing information about the error. The format indices 1..49 are reserved and, for backward compatibility, must be used as stated in offapi/com/sun/star/i18n/NumberFormatIndex.idl. Note that 48 and 49 are used internally and must not be used in locale data XML files. All other formats must be present.

    <FormatCode usage="DATE"> and <FormatCode usage="DATE_TIME">

    Characters of date and time keywords, such as YYYY for year, had previously been localized for a few locales (for example, JJJJ in German). The new I18N framework no longer follows that approach, because it may lead to ambiguous and case insensitive character combinations that cannot be resolved at runtime. Localized keyword support is only given for some old locales, other locales must define their codes using English notation.
    The table below shows the localized keyword codes:
    DayOfWeek Era Year Month Day Hour
    English (and all other locales not mentioned) A G Y M D H
    de_AT, de_CH, de_DE, de_LI, de_LU J T
    nl_BE, nl_NL J U
    fr_BE, fr_CA, fr_CH, fr_FR, fr_LU, fr_MC O A J
    it_CH, it_IT O X A G
    pt_BR, pt_PT O A
    es_AR, es_BO, es_CL, es_CO, es_CR, es_DO, es_EC, es_ES, es_GT, es_HN, es_MX, es_NI, es_PA, es_PE, es_PR, es_PY, es_SV, es_UY, es_VE O A
    da_DK T
    nb_NO, nn_NO, no_NO T
    sv_FI, sv_SE T
    fi_FI V K P T

    <FormatCode usage="DATE" formatindex="21"> and <FormatCode usage="DATE_TIME" formatindex="47">

    The formatindex="21" com.sun.star.i18n.NumberFormatIndex DATE_SYS_DDMMYYYY format code is used to edit date formatted data. It represents a date using the most detailed information available, for example, a 4-digit year and instead of a 2-digit year. The YMD default order (how a date is assembled) is determined from the order encountered in this format.
    Similarly, the formatindex="47" com.sun.star.i18n.NumberFormatIndex DATETIME_SYS_DDMMYYYY_HHMMSS format code is used to edit date-time data. Both format codes must display data in a way that is parable by the application, in order to be able to reassemble edited data. This generally means using only YYYY,MM,DD,HH,MM,SS keywords and <DateSeparator> and <TimeSeparator>.

    <FormatCode usage="CURRENCY">

    The [$xxx-yyy] notation is needed for compatibility reasons. The xxx part denotes the currency symbol, and the yyy part specifies the locale identifier in Microsoft Language ID hexadecimal notation. For example, having "409" as the locale identifier (English-US) and "$" as the currency symbol results in [$$-409]. A list of available Language IDs known to the [AOo] application can be found at project util module tools in file tools/inc/lang.hxx. Format indices 12, 13, 14, 15, 17 with [$xxx-yyy] notation must use the xxx currency symbol that has the attribute usedInCompatibleFormatCodes="true" (see element <LC_CURRENCY> in the locale.dtd file).
    XCalendar

    The interface com.sun.star.i18n.XCalendar provides a general calendar service. All calendar implementations are managed by a class CalendarImpl, the front-end, which dynamically calls a language-specific implementation.

    Calendar_gregorian is a wrapper to ICU's Calendar class.

    If you need to implement a locale-specific calendar, you can choose to either derive your class from Calendar_gregorian or to write your own class.

    There are three steps needed to create a locale-specific calendar:

    1. Name your calendar <name> (for example, 'gengou' for Japanese Calendar) and add it to the locale data XML file with proper day/month/era names.
    2. Derive a class either from Calendar_gregorian or XCalendar, name it as Calendar_<name>, which will be loaded by CalendarImpl when the calendar is specified.
    3. Add your new calendar as a service in i18npool/source/registerservices/registerservices.cxx.

    If you plan to derive from the Gregorian calendar, you need to know the mapping between your new calendar and the Gregorian calendar. For example, the Japanese Emperor Era calendar has a starting year offset to Gregorian calendar for each era. You will need to override the method Calendar_gregorian::mapToGregorian() and Calendar_gregorian::mapFromGregorian() to map the Era/Year/Month/Day between the Gregorian calendar and the calendar for your language.

    XCharacterClassification

    The interface com.sun.star.i18n.XCharacterClassification provides toUpper(), toLower(), toTitle() and methods to get various character attributes defined by Unicode. These functions are implemented by the cclass_unicode class. If you need language specific requirements for these functions, you can derive a language specific class cclass_<locale_name> from cclass_unicode and overwrite the methods. In most cases, the attributes are well defined by Unicode, so you do not need to create your own class.

    The class also provides a generic parser. If a particular language needs special number parsing, detected non-ASCII numbers are fed to the com.sun.star.i18n.NativeNumberSupplier service to obtain the ASCII representation, which in turn is interpreted and converted to a double precision floating point value.

    A manager class CharacterClassificationImpl will handle the loading of language specific implementations of CharacterClassification on the fly. If no implementation is provided, the implementation defaults to class cclass_unicode.

    XBreakIterator

    The interface com.sun.star.i18n.XBreakIterator provides support for Character(Cell)/Word/Sentence/Line-break services. For example, BreakIterator provides the APIs to iterate a string by character, word, line and sentence. The interface is used by the Output layer for the following operations:

    • Cursor positioning and selection: Since a character or cell can take more than one code point, cursor movement cannot be done by simply incrementing or decrementing the index.
    • Complex Text Layout Languages (CTL): In CTL languages (such as Thai, Hebrew, Arabic and Indian), multiple characters can combine to form a display cell. Cursor movement must traverse a display cell instead of a single character.

    Line breaking must be highly configurable in desktop publishing applications. The line breaking algorithm should be able to find a line break with or without a hyphenator. Additionally, it should be able to parse special characters that are illegal if they occur at the end or beginning of a line.

    Both requirements are locale-sensitive.

    The BreakIterator components are managed by the class BreakIteratorImpl, which will load the language-specific component in service name BreakIterator_<language> dynamically.

    The base break iterator class BreakIterator_Unicode is a wrapper to the ICU BreakIterator class. While this class meets the requirements for western languages, it does not meet the requirements for other languages, such as those of South Asia (CJK) and South East Asia (Indian, Thai, Arabic), where enhanced functionality is required, as described previously.

    Thus the current BreakIterator base class has two derived classes, BreakIterator_CJK and BreakIterator_CTL. BreakIterator_CJK provides a dictionary based word break for Chinese and Japanese, and a forbidden rule driven line break for Chinese, Japanese and Korean. BreakIterator_CTL provides a more specific definition of character/cell/cluster grouping for languages like Thai and Arabic.

    Use the following steps to create a language-specific BreakIterator service:

    1. Derive a class either from BreakIterator_CJK or BreakIterator_CTL, name it as BreakIterator_<language>.
    2. Add new service in registerservices.cxx

    There are three methods for word breaking: nextWord(), previousWord(), getWordBoundary(). You can overwrite them with your own language rules.

    BreakIterator_CJK provides input string caching and dictionary searching for longest matching. You can provide a sorted dictionary (the encoding must be UTF-8) by creating the following file: i18npool/source/breakiterator/data/<language>.dict.

    The utility gendict will convert the file to C code, which will be compiled into a shared library for dynamic loading.

    All dictionary searching and loading is performed in the xdictionary class. The only thing you need to do is to derive your class from BreakIterator_CJK and create an instance of the xdictionary with the language name and pass it to the parent class.

    XCollator

    The interface com.sun.star.i18n.XCollator must be used to provide text collation for the new locale. There are two types of collations, single level and multiple level collation.

    Most European and English locales need multiple level collation. LibreOffice uses the ICU collator to cover these needs.

    Most CJK languages only require single level collation. There is a two step lookup table that performs the collation for these languages. If you have a new language or algorithm in this category, you can derive a new service from Collator_CJK and provide index and weight tables. Here is a sample implementation:

    #include <collator_CJK.hxx>
    static sal_uInt16 index[] = {
    ...
    };
    
    static sal_uInt16 weight[] = {
    ...
    };
    
    sal_Int32 SAL_CALL Collator_zh_CN_pinyin::compareSubstring(
            const ::rtl::OUString& str1, sal_Int32 off1, sal_Int32 len1,
            const ::rtl::OUString& str2, sal_Int32 off2, sal_Int32 len2)
    {
        return compare(str1, off1, len1, str2, off2, len2, index, weight);
    }
    
    sal_Int32 SAL_CALL Collator_zh_CN_pinyin::compareString(
            const ::rtl::OUString& str1,
            const ::rtl::OUString& str2)
    {
        return compare(str1, 0, str1.getLength(), str2, 0, str2.getLength(),
            index, weight);
    }

    Front end implementation Collator will load and cache the language-specific service on the name Collator_<locale> dynamically.

    The steps to add new services:

    1. Derive the new service from the above class
    2. Provide the index and weight tables
    3. Register the new service in registerservices.cxx
    4. Add the new service in the collation section in the locale data file.
    XTransliteration

    The interface com.sun.star.i18n.XTransliteration can be used for string conversion. The front end implementation TransliterationImpl will load and cache specific transliteration services by a predefined enum in com.sun.star.i18n.TransliterationModules orcom.sun.star.i18n.TransliterationModulesNew, or dynamically by implementation name.

    Transliterations have been defined in three categories: Ignore, OneToOne and Numeric. All of them are derived from transliteration_commonclass.

    Ignore services are for ignore case, half/full width, and Katakana/Hiragana. You can derive your new service from it, and overwrite folding/transliteration methods.

    OneToOne services are for one to one mapping, such as converting lowercase to uppercase. The class provides two more services, to take a mapping table or mapping function to do folding and transliteration. You can derive a class from it and provide a table or function for the parent class to do the transliteration.

    Numeric services are used to convert a number to a number string in specific languages. It can be used to format Date string and other types of strings.

    To add a new transliteration

    1. Derive a new class from the three classes previously mentioned.
    2. Overwrite folding/transliteration methods or provide a table for the parent to perform the transliteration.
    3. Register the new service in registerservices.cxx
    4. Add the new service in the transliteration section in the locale data file
    XTextConversion

    The interface com.sun.star.i18n.XTextConversion can be used for string conversion. The service com.sun.star.i18n.TextConversion implementing the interface provides a function to determine if the text conversion should be interactive or not along with functions that can be used for automatic and interactive conversion.

    It is possible to create conversion-dictionaries com.sun.star.linguistic2.XConversionDictionary, which are searched for entries to be used by the text conversion service, thus allowing the user to customize the text conversion.

    The following is an example:

    //***************************************************************************
    // comment: Step 1: get the Desktop object from the office
    // Step 2: open an empty text document
    // Step 3: insert a sample text
    // Step 4: convert sample text
    // Step 5: insert converted text
    //***************************************************************************
    import com.sun.star.uno.UnoRuntime;
    public class TextConversion {
    
        public static void main(String args[]) {
            // You need the desktop to create a document
            // The getDesktop method does the UNO bootstrapping, gets the
            // remote servie manager and the desktop object.
            com.sun.star.frame.XDesktop xDesktop = null;
            xDesktop = getDesktop();
    
            com.sun.star.text.XTextDocument xTextDocument =
                createTextdocument( xDesktop );
            com.sun.star.i18n.XTextConversion xTextConversion =
                getTextConversion();
            try {
                // Korean sample text
                String aHeader = "\u7b2c\u0020\u0031\u0020\u7ae0\u0020\ud55c\ubb38\uc758\u0020\uad6c\uc870\u0028\u69cb\u9020\u0029";
                String aText = "\uc6b0\uc120\u0020\ud55c\uc790\ub294\u0020\uc11c\uc220\uc5b4\u0020\u0028\u654d\u8ff0\u8a9e\u0029\uc758\u0020\uc704\uce58\uac00\u0020\uc6b0\ub9ac\ub9d0\uacfc\u0020\ub2e4\ub974\ub2e4\u002e";
    
                // access interfaces and cursor to be used
                com.sun.star.text.XText xText = (com.sun.star.text.XText)
                        UnoRuntime.queryInterface(
                            com.sun.star.text.XText.class, xTextDocument.getText());
                com.sun.star.text.XSimpleText xSimpleText = (com.sun.star.text.XSimpleText)
                        UnoRuntime.queryInterface(
                            com.sun.star.text.XSimpleText.class, xText);
                com.sun.star.text.XTextCursor xCursor = xText.createTextCursor();
                // set text properties (font, language) to be used for the sample
                com.sun.star.beans.XPropertySet xPS = (com.sun.star.beans.XPropertySet)
                        UnoRuntime.queryInterface(
                            com.sun.star.beans.XPropertySet.class, xCursor);
                com.sun.star.lang.Locale aKorean = new com.sun.star.lang.Locale( "ko", "KR", "");
                xPS.setPropertyValue( "CharFontNameAsian", "Gulim" );
                xPS.setPropertyValue( "CharLocaleAsian", aKorean );
                xPS.setPropertyValue( "CharHeightAsian", Integer.valueOf(24) );
                xPS.setPropertyValue( "CharHeight", Integer.valueOf(24) );
                // insert original text
                xSimpleText.insertString( xCursor, "Original text:", false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
                xSimpleText.insertString( xCursor, aHeader, false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
                xSimpleText.insertString( xCursor, aText, false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
    
                //
                // apply Hangul->Hanja conversion
                //
                short nConversionType = com.sun.star.i18n.TextConversionType.TO_HANJA;
                int nConversionOptions = com.sun.star.i18n.TextConversionOption.NONE;
                //
                // call to function for non-interactive text conversion
                // (usually not used for Hangul/Hanja conversion but here used
                // anyway for the examples sake)
                aHeader = xTextConversion.getConversion( aHeader, 0, aHeader.length(),
                              aKorean, nConversionType, nConversionOptions);
                //
                // sample for function calls in an interactive conversion
                StringBuffer aBuf = new StringBuffer( aText );
                int i = 0;
                boolean bFound = true;
                int nLen = aText.length();
                while (i < nLen - 1 && bFound) {
                    com.sun.star.i18n.TextConversionResult aResult =
                        xTextConversion.getConversions( aText, i, nLen - i,
                            aKorean, nConversionType, nConversionOptions);
    
                    // check if convertible text portion was found
                    bFound = !(aResult.Boundary.startPos == 0 && aResult.Boundary.endPos == 0);
                    if ( bFound ) {
                        String[] aCandidates = aResult.Candidates;
                        // let the user choose one candidate from the list
                        // (in this non-interactive example we'll always choose
                        // the first one)
                        String aChoosen = aCandidates[0];
                        aBuf.replace( aResult.Boundary.startPos,
                                      aResult.Boundary.endPos,
                                      aChoosen );
    
                        // continue with text after current converted
                        // text portion
                        if ( aResult.Boundary.endPos > i )
                          i = aResult.Boundary.endPos;
                        else {
                          // or advance at least one position
                          System.out.println("unexpected forced advance...");
                          ++i;
                        }
                    }
                }
                aText = aBuf.toString();
                // insert converted text
                xSimpleText.insertString( xCursor, "Converted text:", false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
                xSimpleText.insertString( xCursor, aHeader, false );
                xSimpleText.insertControlCharacter( xCursor, com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, false );
                xSimpleText.insertString( xCursor, aText, false );
            }
            catch( Exception e) {
                e.printStackTrace(System.err);
            }
    
            System.out.println("Done");
    
            System.exit(0);
        }
    
        public static com.sun.star.frame.XDesktop getDesktop() {
            com.sun.star.frame.XDesktop xDesktop = null;
            com.sun.star.lang.XMultiComponentFactory xMCF = null;
    
            try {
                com.sun.star.uno.XComponentContext xContext = null;
    
                // get the remote office component context
                xContext = com.sun.star.comp.helper.Bootstrap.bootstrap();
    
                // get the remote office service manager
                xMCF = xContext.getServiceManager();
                if( xMCF != null ) {
                    System.out.println("Connected to a running office ...");
                    Object oDesktop = xMCF.createInstanceWithContext(
                        "com.sun.star.frame.Desktop", xContext);
                    xDesktop = UnoRuntime.queryInterface(
                        com.sun.star.frame.XDesktop.class, oDesktop);
                    }
                    else
                        System.out.println( "Can't create a desktop. No connection, no remote office servicemanager available!" );
                }
                catch( Exception e) {
                    e.printStackTrace(System.err);
                    System.exit(1);
                }
    
                return xDesktop;
            }
    
            public static com.sun.star.i18n.XTextConversion getTextConversion() {
                com.sun.star.i18n.XTextConversion xTextConv = null;
                com.sun.star.lang.XMultiComponentFactory xMCF = null;
    
                try {
                    com.sun.star.uno.XComponentContext xContext = null;
    
                    // get the remote office component context
                    xContext = com.sun.star.comp.helper.Bootstrap.bootstrap();
    
                    // get the remote office service manager
                    xMCF = xContext.getServiceManager();
                    if( xMCF != null ) {
                        Object oObject = xMCF.createInstanceWithContext(
                            "com.sun.star.i18n.TextConversion", xContext);
                        xTextConv = UnoRuntime.queryInterface(
                            com.sun.star.i18n.XTextConversion.class, oObject);
                    }
                    else
                        System.out.println( "Can't create a text conversion service. No office servicemanager available!" );
                    if( xTextConv != null )
                        System.out.println( "Successfully instantiated text conversion service." );
                }
                catch( Exception e) {
                    e.printStackTrace(System.err);
                    System.exit(1);
                }
    
                return xTextConv;
            }
    
            public static com.sun.star.text.XTextDocument createTextdocument(
                com.sun.star.frame.XDesktop xDesktop )
            {
                com.sun.star.text.XTextDocument aTextDocument = null;
    
                try {
                    com.sun.star.lang.XComponent xComponent = CreateNewDocument(xDesktop,"swriter");
                    aTextDocument = (com.sun.star.text.XTextDocument)
                        UnoRuntime.queryInterface(
                            com.sun.star.text.XTextDocument.class, xComponent);
                }
                catch( Exception e) {
                    e.printStackTrace(System.err);
                }
    
                return aTextDocument;
            }
    
            protected static com.sun.star.lang.XComponent CreateNewDocument(
                com.sun.star.frame.XDesktop xDesktop,
                String sDocumentType )
            {
                String sURL = "private:factory/" + sDocumentType;
    
                com.sun.star.lang.XComponent xComponent = null;
                com.sun.star.frame.XComponentLoader xComponentLoader = null;
                com.sun.star.beans.PropertyValue xValues[] =
                    new com.sun.star.beans.PropertyValue[1];
                com.sun.star.beans.PropertyValue xEmptyArgs[] =
                    new com.sun.star.beans.PropertyValue[0];
    
                try {
                    xComponentLoader = (com.sun.star.frame.XComponentLoader)
                        UnoRuntime.queryInterface(
                            com.sun.star.frame.XComponentLoader.class, xDesktop);
    
                    xComponent = xComponentLoader.loadComponentFromURL(
                        sURL, "_blank", 0, xEmptyArgs);
                }
                catch( Exception e) {
                    e.printStackTrace(System.err);
                }
    
                return xComponent ;
        }
    }

    XNativeNumberSupplier

    The interface com.sun.star.i18n.XNativeNumberSupplier provides the functionality to convert between ASCII Arabic digit numbers and locale-dependent numeral representations. It performs the conversion by implementing special transliteration services. The interface also provides a mechanism to generate attributes to be stored in the XML file format (see the XML file format documentation, section "Common Data Style Attributes", "number:transliteration-..."), as well as a conversion of those XML attributes needed to map back to a specific representation style. If you add a number transliteration for a specific locale and reuse one of the com.sun.star.i18n.NativeNumberMode constants, please add the description to com.sun.star.i18n.NativeNumberMode if your changes are to be added back to the OpenOffice.org code repository.

    XIndexEntrySupplier

    The interface com.sun.star.i18n.XIndexEntrySupplier can be used to provide the functionality to generate index pages. The main method of this interface is getIndexCharacter(). Front end implementation IndexEntrySupplier will dynamically load and cache language specific service based on the name IndexEntrySupplier_<locale>.

    Languages to be indexed have been divided into two sets. The first set contains Latin1 languages, which can be covered by 256 Unicode code points. A one step lookup table is used to generate index characters. An alphabetic and numeric table has been generated, which covers most Latin1 languages. But if you need another algorithm or have a conflict with the table, you can create your own table and derive a new class from IndexEntrySupplier_Euro. Here is a sample implementation:

    #include <sal/types.h>
    #include <indexentrysupplier_euro.hxx>
    #include <indexdata_alphanumeric.h>
    
    OUString SAL_CALL i18n::IndexEntrySupplier_alphanumeric::getIndexCharacter(
            const OUString& rIndexEntry,
            const lang::Locale& rLocale, const OUString& rSortAlgorithm )
    {
        return getIndexString(rIndexEntry, idxStr);
    }

    where idxStr is the table.

    For the languages that could not be covered in the first set, such as CJK, a two step lookup table is used. Here is a sample implementation:

    #include <indexentrysupplier_cjk.hxx>
    #include <indexdata_zh_pinyin.h>
    
    OUString SAL_CALL i18n::IndexEntrySupplier_zh_pinyin::getIndexCharacter(
            const OUString& rIndexEntry,
            const lang::Locale& rLocale, const OUString& rSortAlgorithm )
    {
        return getIndexString(rIndexEntry, idxStr, idx1, idx2);
    }

    where idx1 and idx2 are two step tables and idxStr contains all the index keys that will be returned. If you have a new language or algorithm, you can derive a new service from IndexEntrySupplier_CJK and provide tables for the parent class to generate the index.

    Note that the index depends on collation, therefore, each index algorithm should have a collation algorithm to support it.

    To add new service:

    1. Derive the new service from IndexEntrySupplier_Euro.
    2. Provide a table for the lookup
    3. Register new service in registerservices.cxx
    A Comment on Search and Replace

    Search and replace is also locale-dependent because there may be special search options that are only available for a particular locale. For instance, if the Asian languages support is enabled, you'll see an additional option for "Sounds like (Japanese)" in the Edit ▸ Find & Replace dialog box. With this option, you can turn on or off certain options specific to Japanese in the search and replace process.

    Search and replace relies on the transliteration modules for various search options. The transliteration modules are loaded, and the search string is converted before the search process.

    Linguistics

    The Linguistic API provides a set of UNO services used for spell checking, hyphenation or accessing a thesaurus. Through the Linguistic API, developers add new implementations and integrate them into LibreOffice. Users of the Linguistic API call its methods Usually this functionality is used by one or more clients, that is, applications or components, to process documents such as text documents or spreadsheets.

    Services Overview

    The services provided by the Linguistic API are:

    Also there is at least one or more implementation for each of the following services:

    The service implementations for spell checker, thesaurus and hyphenator supply the respective functionality. Each of the implementations support a different set of languages. Refer to com.sun.star.linguistic2.XSupportedLocales.

    For example, there could be two implementations for a spell checker, usually from different supporting parties: the first supporting English, French and German, and the second supporting Russian and English. Similar settings occur for the hyphenator and thesaurus.

    It is not convenient for each application or component to know all these implementations and to choose the appropriate implementation for the specific purpose and language, therefore a mediating instance is required.

    This instance is the LinguServiceManager. Spell checking, hyphenation and thesaurus functionality is accessed from a client by using the respective interfaces from the LinguServiceManager.

    The LinguServiceManager dispatches the interface calls from the client to a specific service implementation, if any, of the respective type that supports the required language. For example, if the client requires spell checking of a French word, the first spell checker implementations from those mentioned above are called.

    If there is more than one spell checker available for one language, as in the above example for the English language, the LinguServiceManager starts with the first one that was supplied in the setConfiguredServices() method of its interface. The thesaurus behaves similarly. For more details, refer to the interface description com.sun.star.linguistic2.XLinguServiceManager.

    The LinguProperties service provides, among others, properties that are required by the spell checker, hyphenator and thesaurus that are modified by the client. Refer to the com.sun.star.linguistic2.LinguProperties.

    The DictionaryList (see com.sun.star.linguistic2.DictionaryList) provides a set of user defined or predefined dictionaries for languages that are activated and deactivated. If they are active, they are used by the spell checker and hyphenator. These are used by the user to override results from the spell checker and hyphenator implementations, thus allowing the user to customize spell checking and hyphenation.

    In the code snippets and examples in the following chapters, we will use the following members and interfaces:

    // used interfaces
    import com.sun.star.lang.XMultiServiceFactory;
    import com.sun.star.linguistic2.XLinguServiceManager;
    import com.sun.star.linguistic2.XSpellChecker;
    import com.sun.star.linguistic2.XHyphenator;
    import com.sun.star.linguistic2.XThesaurus;
    import com.sun.star.linguistic2.XSpellAlternatives;
    import com.sun.star.linguistic2.XHyphenatedWord;
    import com.sun.star.linguistic2.XPossibleHyphens;
    import com.sun.star.linguistic2.XMeaning;
    import com.sun.star.linguistic2.XSearchableDictionaryList;
    import com.sun.star.linguistic2.XLinguServiceEventListener;
    import com.sun.star.linguistic2.LinguServiceEvent;
    import com.sun.star.beans.XPropertySet;
    import com.sun.star.beans.PropertyValue;
    import com.sun.star.uno.XComponentContext;
    import com.sun.star.uno.XNamingService;
    import com.sun.star.lang.XMultiComponentFactory;
    import com.sun.star.lang.EventObject;
    import com.sun.star.lang.Locale;
    import com.sun.star.bridge.XUnoUrlResolver;
    import com.sun.star.uno.UnoRuntime;
    import com.sun.star.uno.Any;
    import com.sun.star.lang.XComponent;
    
    //
    // members for commonly used interfaces
    //
    
    // The MultiServiceFactory interface of the Office
    protected XMultiServiceFactory mxFactory = null;
    
    // The LinguServiceManager interface
    protected XLinguServiceManager mxLinguSvcMgr = null;
    
    // The SpellChecker interface
    protected XSpellChecker mxSpell = null;
    
    // The Hyphenator interface
    protected XHyphenator mxHyph = null;
    
    // The Thesaurus interface
    protected XThesaurus mxThes = null;
    
    // The DictionaryList interface
    protected XSearchableDictionaryList mxDicList = null;
    
    // The LinguProperties interface
    protected XPropertySet mxLinguProps = null;

    To establish a connection to the office and have our mxFactory object initialized with its XMultiServiceFactory, the following code is used:

    public void Connect( String sConnection )
        throws com.sun.star.uno.Exception,
        com.sun.star.uno.RuntimeException,
        Exception
    {
        XComponentContext xContext =
            com.sun.star.comp.helper.Bootstrap.createInitialComponentContext( null
    );
        XMultiComponentFactory xLocalServiceManager = xContext.getServiceManager();
    
        Object xUrlResolver = xLocalServiceManager.createInstanceWithContext(
            "com.sun.star.bridge.UnoUrlResolver", xContext );
        XUnoUrlResolver urlResolver = UnoRuntime.queryInterface(
            XUnoUrlResolver.class, xUrlResolver );
        Object rInitialObject = urlResolver.resolve( "uno:" + sConnection +
            ";urp;StarOffice.NamingService" );
        XNamingService rName = UnoRuntime.queryInterface(XNamingService.class,
            rInitialObject );
        if( rName != null )
        {
            Object rXsmgr = rName.getRegisteredObject( "StarOffice.ServiceManager" );
            mxFactory = (XMultiServiceFactory)
                UnoRuntime.queryInterface( XMultiServiceFactory.class, rXsmgr );
        }
    }

    And the LinguServiceManager object mxLinguSvcMgr is initialized like in the following snippet:

    /** Get the LinguServiceManager to be used. For example to access spell checker,
        thesaurus and hyphenator, also the component may choose to register itself
        as listener to it in order to get notified of relevant events. */
    public boolean GetLinguSvcMgr()
        throws com.sun.star.uno.Exception
    {
        if (mxFactory != null) {
            Object aObj = mxFactory.createInstance(
                "com.sun.star.linguistic2.LinguServiceManager" );
            mxLinguSvcMgr = (XLinguServiceManager)
                UnoRuntime.queryInterface(XLinguServiceManager.class, aObj);
        }
        return mxLinguSvcMgr != null;
    }

    The empty list of temporary property values used for the current function call only and the language used may look like the following:

    // list of property values to used in function calls below.
    // Only properties with values different from the (default) values
    // in the LinguProperties property set need to be supplied.
    // Thus we may stay with an empty list in order to use the ones
    // form the property set.
    PropertyValue[] aEmptyProps = new PropertyValue[0];
    
    // use american english as language
    Locale aLocale = new Locale("en","US","");

    Using temporary property values:

    To change a value for the example IsGermanPreReform to a different value for one or a limited number of calls without modifying the default values, provide this value as a member of the last function argument used in the examples below before calling the respective functions.

    // another list of property values to used in function calls below.
    // Only properties with values different from the (default) values
    // in the LinguProperties property set need to be supplied.
    PropertyValue[] aProps = new PropertyValue[1];
    aProps[0] = new PropertyValue();
    aProps[0].Name = "IsGermanPreReform";
    aProps[0].Value = Boolean.TRUE;

    Replace the aEmptyProps argument in the function calls with aProps to override the value of IsGermanPreReform from the LinguProperties. Other properties are overridden by adding them to the aProps object.

    Using Spellchecker

    The interface used for spell checking is com.sun.star.linguistic2.XSpellChecker. Accessing the spellchecker through the LinguServiceManager and initializing the mxSpell object is done by:

    /** Get the SpellChecker to be used.
      */
    public boolean GetSpell()
        throws com.sun.star.uno.RuntimeException
    {
        if (mxLinguSvcMgr != null)
            mxSpell = mxLinguSvcMgr.getSpellChecker();
        return mxSpell != null;
    }

    Relevant properties

    The properties of the LinguProperties service evaluated by the spell checker are:

    Spell-checking Properties of com.sun.star.linguistic2.LinguProperties Description
    IsIgnoreControlCharacters Defines if control characters should be ignored or not.
    IsUseDictionaryList Defines if the dictionary-list should be used or not.
    IsGermanPreReform Defines if the new German spelling rules should be used for German language text or not.
    IsSpellUpperCase Defines if words with only uppercase letters should be subject to spellchecking or not.
    IsSpellWithDigits Defines if words containing digits or numbers should be subject to spellchecking or not.
    IsSpellCapitalization Defines if the capitalization of words should be checked or not.

    Changing the values of these properties in the LinguProperties affect all subsequent calls to the spell checker. Instantiate a com.sun.star.linguistic2.LinguProperties instance and change it by calling setPropertyValue(). The changes affect the whole office unless another modifies the properties again. This is done implicitly when changing the linguistic settings through Tools ▸ Options ▸ Language Settings ▸ Writing Aids.

    The following example shows verifying single words:

    // test with correct word
    String aWord = "horseback";
    boolean bIsCorrect = mxSpell.isValid( aWord, aLocale, aEmptyProps );
    System.out.println( aWord + ": " + bIsCorrect );
    
    // test with incorrect word
    aWord = "course";
    bIsCorrect = mxSpell.isValid( aWord, aLocale , aEmptyProps );
    System.out.println( aWord + ": " + bIsCorrect );

    The following example shows spelling a single word and retrieving possible corrections:

    aWord = "house";
    XSpellAlternatives xAlt = mxSpell.spell( aWord, aLocale, aEmptyProps );
    if (xAlt == null)
        System.out.println( aWord + " is correct." );
    else
    {
        System.out.println( aWord + " is not correct. A list of proposals follows." );
        String[] aAlternatives = xAlt.getAlternatives();
        if (aAlternatives.length == 0)
            System.out.println( "no proposal found." );
        else
        {
            for (int i = 0; i < aAlternatives.length; ++i)
                System.out.println( aAlternatives[i] );
        }
    }

    For a description of the return types interface, refer to com.sun.star.linguistic2.XSpellAlternatives.

    Using Hyphenator

    The interface used for hyphenation is com.sun.star.linguistic2.XHyphenator. Accessing the hyphenator through the LinguServiceManager and initializing the mxHyph object is done by:

    /** Get the Hyphenator to be used.
      */
    public boolean GetHyph()
        throws com.sun.star.uno.RuntimeException
    {
        if (mxLinguSvcMgr != null)
            mxHyph = mxLinguSvcMgr.getHyphenator();
        return mxHyph != null;
    }

    Relevant properties

    The properties of the LinguProperties service evaluated by the hyphenator are:

    Hyphenating Properties of com.sun.star.linguistic2.LinguProperties
    IsIgnoreControlCharacters Defines if control characters should be ignored or not.
    IsUseDictionaryList Defines if the dictionary-list should be used or not.
    IsGermanPreReform Defines if the new German spelling rules should be used for German language text or not.
    HyphMinLeading The minimum number of characters of a hyphenated word to remain before the hyphenation character.
    HyphMinTrailing The minimum number of characters of a hyphenated word to remain after the hyphenation character.
    HyphMinWordLength The minimum length of a word to be hyphenated.

    Changing the values of these properties in the Lingu-Properties affect all subsequent calls to the hyphenator.

    A valid hyphenation position is a possible one that meets the restrictions given by the HyphMinLeading, HyphMinTrailing and HyphMinWordLength values.

    For example, if HyphMinWordLength is 7, "remove" does not have a valid hyphenation position. Also, this is the case when HyphMinLeading is 3 or HyphMinTrailing is 5.

    The following example shows a word hypenated:

    // maximum number of characters to remain before the hyphen
    // character in the resulting word of the hyphenation
    short nMaxLeading = 6;
    
    XHyphenatedWord xHyphWord = mxHyph.hyphenate( "horseback", aLocale, nMaxLeading , aEmptyProps );
    if (xHyphWord == null)
        System.out.println( "no valid hyphenation position found" );
    else
    {
        System.out.println( "valid hyphenation pos found at " + xHyphWord.getHyphenationPos()
                + " in " + xHyphWord.getWord() );
        System.out.println( "hyphenation char will be after char " + xHyphWord.getHyphenPos()
                + " in " + xHyphWord.getHyphenatedWord() );
    }

    If the hyphenator implementation is working correctly, it reports a valid hyphenation position of 4 that is after the 'horse' part. Experiment with other values for nMaxLeading and other words. For example, if you set it to 4, no valid hyphenation position is found since there is no hyphenation position in the word 'horseback' before and including the 's'.

    For a description of the return types interface, refer to com.sun.star.linguistic2.XHyphenatedWord.

    The example below shows querying for an alternative spelling. In some languages, for example German in the old (pre-reform) spelling, there are words where the spelling of changes when they are hyphenated at specific positions. To inquire about the existence of alternative spellings, the queryAlternativeSpelling() function is used:

    //! Note: 'aProps' needs to have set 'IsGermanPreReform' to true!
    xHyphWord = mxHyph.queryAlternativeSpelling( "Schiffahrt",
                        new Locale("de","DE",""), (short)4, aProps );
    if (xHyphWord == null)
        System.out.println( "no alternative spelling found at specified position." );
    else
    {
        if (xHyphWord.isAlternativeSpelling())
            System.out.println( "alternative spelling detectetd!" );
        System.out.println( "valid hyphenation pos found at " + xHyphWord.getHyphenationPos()
                + " in " + xHyphWord.getWord() );
        System.out.println( "hyphenation char will be after char " + xHyphWord.getHyphenPos()
                + " in " + xHyphWord.getHyphenatedWord() );
    }

    The return types interface is the same as in the above example (com.sun.star.linguistic2.XHyphenatedWord).

    The next example demonstrates getting possible hyphenation positions. To determine all possible hyphenation positions in a word, do this:

    XPossibleHyphens xPossHyph = mxHyph.createPossibleHyphens( "waterfall", aLocale, aEmptyProps );
    if (xPossHyph == null)
        System.out.println( "no hyphenation positions found." );
    else
        System.out.println( xPossHyph.getPossibleHyphens() );

    For a description of the return types interface, refer to com.sun.star.linguistic2.XPossibleHyphens.

    Using Thesaurus

    The interface used for the thesaurus is com.sun.star.linguistic2.XThesaurus. Accessing the thesaurus through the LinguServiceManager and initializing the mxThes object is done by:

    /** Get the Thesaurus to be used.
      */
    public boolean GetThes()
        throws com.sun.star.uno.RuntimeException
    {
        if (mxLinguSvcMgr != null)
            mxThes = mxLinguSvcMgr.getThesaurus();
        return mxThes != null;
    }

    The properties of the LinguProperties service evaluated by the thesaurus are:

    Thesaurus-related Properties of com.sun.star.linguistic2.LinguProperties
    IsIgnoreControlCharacters Defines if control characters should be ignored or not.
    IsGermanPreReform Defines if the new German spelling rules should be used for German language text or not.

    Changing the values of these properties in the LinguProperties affect all subsequent calls to the thesaurus. The following example about retrieving synonyms shows this:

    XMeaning[] xMeanings = mxThes.queryMeanings( "house", aLocale, aEmptyProps );
    if (xMeanings == null)
        System.out.println( "nothing found." );
    else
    {
        for (int i = 0; i < xMeanings.length; ++i)
        {
            System.out.println( "Meaning: " + xMeanings[i].getMeaning() );
            String[] aSynonyms = xMeanings[i].querySynonyms();
            for (int k = 0; k < aSynonyms.length; ++k)
                System.out.println( " Synonym: " + aSynonyms[k] );
          }
    }

    The reason to subdivide synonyms into different meanings is because there are different synonyms for some words that are not even closely related. For example, the word 'house' has the synonyms 'home', 'place', 'dwelling', 'family', 'clan', 'kindred', 'room', 'board', and 'put up'.

    The first three in the above list have the meaning of 'building where one lives' where the next three mean that of 'a group of people sharing common ancestry' and the last three means that of 'to provide with lodging'. Thus, having meanings is a way to group large sets of synonyms into smaller ones with approximately the same definition.

    Events

    There are several types of events. For example, all user dictionaries com.sun.star.linguistic2.XDictionary report their status changes as events com.sun.star.linguistic2.DictionaryEvent to the DictionaryList, which collects and transforms their information into DictionaryList events com.sun.star.linguistic2.DictionaryListEvent, and passes those on to its own listeners.

    Thus, it is possible to register to the DictionaryList as a listener to be informed about relevant changes in the dictionaries. There is no need to register as a listener for each dictionary.

    The spell checker and hyphenator implementations monitor the changes in the LinguProperties for changes of their relevant properties. If such a property changes its value, the implementation launches an event com.sun.star.linguistic2.LinguServiceEvent that hints to its listeners that spelling or hyphenation should be reevaluated. For this purpose, those implementations support the com.sun.star.linguistic2.XLinguServiceEventBroadcaster interface.

    The LinguServiceManager acts as a listener for com.sun.star.linguistic2.DictionaryListEvent and com.sun.star.linguistic2.LinguServiceEvent events. The respective interfaces are com.sun.star.linguistic2.XDictionaryListEventListener and com.sun.star.linguistic2.XLinguServiceEventListener. The events from the DictionaryList are transformed into com.sun.star.linguistic2.LinguServiceEvent events and passed to the listeners of the LinguServiceManager, along with the received events from the spell checkers and hyphenators.

    Therefore, a client that wants to be notified when spell checking or hyphenation changes, for example, when it features automatic spell checking or automatic hyphenation, needs to be registered as com.sun.star.linguistic2.XLinguServiceEventListener to the LinguServiceManager only.

    Implementing the com.sun.star.linguistic2.XLinguServiceEventListener interface is similar to the following snippet:

    /** simple sample implementation of a clients XLinguServiceEventListener
      * interface implementation
      */
    public class Client
            implements XLinguServiceEventListener
    {
        public void disposing ( EventObject aEventObj )
        {
            //! any references to the EventObjects source have to be
            //! released here now!
    
            System.out.println("object listened to will be disposed");
        }
    
        public void processLinguServiceEvent( LinguServiceEvent aServiceEvent )
        {
            //! do here whatever you think needs to be done depending
            //! on the event recieved (e.g. trigger background spellchecking
            //! or hyphenation again.)
    
            System.out.println("Listener called");
        }
    };

    After the client has been instantiated, it needs to register as com.sun.star.linguistic2.XLinguServiceEventListener. For the sample client above, this looks like:

    XLinguServiceEventListener aClient = new Client();
    
    // now add the client as listener to the service manager to
    // get informed when spellchecking or hyphenation may produce
    // different results than before.
    mxLinguSvcMgr.addLinguServiceManagerListener( aClient );

    This enables the sample client to receive com.sun.star.linguistic2.LinguServiceEvents and act accordingly. Before the sample client terminates, it has to stop listening for events from the LinguServiceManager:

    //! remove listener before programm termination.
    //! should not be omitted.
    mxLinguSvcMgr.removeLinguServiceManagerListener(aClient);

    In the LinguisticExamples.java sample, a property is modified for the listener to be called.

    Implementing a Spell Checker

    A sample implementation of a spell checker is found in the examples for linguistics.

    The spell checker implements the following interfaces:

    and

    To implement a spell checker of your own, modify the sample in the following ways:

    Choose a unique service implementation name to distinguish your service implementation from any other. To do this, edit the string in the line

    >
    public static String _aSvcImplName = "com.sun.star.linguistic2.JavaSamples.SampleSpellChecker";

    Then, specify the list of languages supported by your service. Edit the

    public Locale[] getLocales()

    function and modify the

    public boolean hasLocale( Locale aLocale )

    function accordingly. The next step is to change the

    private short GetSpellFailure(...)

    as required. This function determines if a word is spelled correctly in a given language. If the word is OK return -1, otherwise return an appropriate value of the type com.sun.star.linguistic2.SpellFailure.

    Check if you need to edit or remove the

    private boolean IsUpper(...)

    and

    private boolean HasDigits(...)

    functions. Consider this only if you are planning to support non-western languages and need sophisticated versions of those, or do not need them at all. Do not forget to change the code at the end of

    public boolean isValid(...)

    accordingly.

    Supply your own version of

    private XSpellAlternatives GetProposals(...)

    It provides the return value for the

    public XSpellAlternatives spell(...)

    function call if the word was found to be incorrect. The main purpose is to provide proposals for how the word might be written correctly. Note the list may be empty.

    Next, edit the text in

    public String getServiceDisplayName(...)

    It should be unique but it is not necessary. If you are developing a set of services, that is, spellchecker, hyphenator and thesaurus, it should be the same for all of them. This text is displayed in dialogs to show a more meaningful text than the service implementation name.

    Now, have a look in the constructor

    public SampleSpellChecker()

    at the property names. Remove the entries for the properties that are not relevant to your service implementation. If you make modification, also look in the file PropChgHelper_Spell.java in the function

    public void propertyChange(...)

    and change it accordingly.

    Set the values of bSCWA and bSWWA to true only for those properties that are relevant to your implementation, thus avoiding sending unnecessary com.sun.star.linguistic2.LinguServiceEvent events, that is, avoid triggering spell-checking in clients if there is no requirement.

    Finally, after registration of the service (see Deployment Options for Components) it has to be activated to be used by the LinguServiceManager. After restarting LibreOffice, this is done in the following manner:

    Open the dialog Tools ▸ Options ▸ Language Settings ▸ Writing Aids. In the section Writing Aids, in the box Available Language Modules, a new entry with text of the Service Display Name that you chose is displayed in the implementation. Check the empty checkbox to the left of that entry. If you want to use your module, uncheck any other listed entry. If you want to make more specific settings per language, press the Edit button next to the modules box and use that dialog.

    The Context menu of the Writer that pops up when pressing the right-mouse button over an incorrectly spelled word currently has a bug that may crash the program when the Java implementation of a spell checker is used. The spell check dialog is functioning.

    Implementing a Hyphenator

    A sample implementation of a hyphenator is found in the examples for linguistic.

    The hyphenator implements the following interfaces:

    and

    Aside from choosing a new service implementation name, the process of implementing the hyphenator is the same as implementing the spell checker, except that you need to implement the com.sun.star.linguistic2.XHyphenator interface instead of the com.sun.star.linguistic2.XSpellChecker interface.

    You can choose a different set of languages to be supported. When editing the sample code, modify the hasLocale() and getLocales() methods to reflect the set of languages your implementation supports.

    To implement the com.sun.star.linguistic2.XHyphenator interface, modify the functions

    public XHyphenatedWord hyphenate(...)
    public XHyphenatedWord queryAlternativeSpelling(...)
    public XPossibleHyphens createPossibleHyphens(...)<

    in the sample hyphenator source file at the stated positions.

    Look in the constructor

    public SampleHyphenator()

    at the relevant properties and modify the

    public void propertyChange(...)

    function accordingly.

    The rest, registration and activation is again the same as for the spell checker.

    Implementing a Thesaurus

    A sample implementation of a thesaurus is found in the examples for linguistic.

    The thesaurus implements the following interfaces:

    and

    For the implementation of the thesaurus, modify the sample thesaurus by following the same procedure as for the spell checker and thesaurus:

    Choose a different implementation name for the service and modify the

    public Locale[] getLocales()

    and

    public boolean hasLocale(...)

    functions.

    The only function to be modified at the stated position to implement the com.sun.star.linguistic2.XThesaurus interface is

    public XMeaning[] queryMeanings(...)

    Look in the constructor

    public SampleThesaurus()

    to see if there are properties you do not require.

    Registration and activation is the same as for the spell checker and hyphenator.

    Integrating Import and Export Filters

    LibreOffice provides several implementations for objects that can be displayed in a task window. In the context of the Component Framework they are called Office Components. These components can be created from a content, e.g. stored in a file on disk. Most of the time this will be done by creating a document and loading the content into it using a filter. This section explains the implementation of LibreOffice import and export filter components. LibreOffice also allows to load content into a component directly by using a frame loader, but this is described in this section only briefly.

    Introduction

    Inside LibreOffice a document is represented by its document service, called model. For a list of available document services, refer to the section Document Specific Features. On disk, the same document is represented as a file or possibly as a dynamically generated output, for example, of a database statement. To generalize this and abstract from single disk files we just call it "content". The content is a serialization of a model, e.g. the ODF or the Word model. A filter component is used to convert between this model and the internal model defined by the document core model as shown in the following diagram.

    Import/Export Filter Process

    In our API the three entities in the above diagram, content, model, and filter, are defined as UNO services. The services consist of several interfaces that map to a specific implementation, for example, using C++ or Java.

    The filter implementer has to develop a class that implements the com.sun.star.document.ExportFilter or com.sun.star.document.ImportFilter service, or both in case the filter should support import and export. The filter will get a com.sun.star.document.MediaDescriptor that defines the stream the filter must use for its input or output.

    Approaches

    To implement said filter class, a developer can

    • link against the application core
    • use the document API
    • use the XML based techniques (sax or xslt)

    Each method has unique advantages and disadvantages, that are summarized briefly:

    Using the core data structure and linking against the application core is the way how all elder filters (originating from the "pre-UNO" area) are implemented in LibreOffice. As the disadvantages are huge (maintenance nightmare when core data structures or interfaces change), this approach is not recommended for new filter development in general.

    Using the LibreOffice API based on UNO is more advantageous, since it solves the technical problems indicated in the above paragraph. The idea is to read data from a file on loading and build up a document using the LibreOffice API, and to iterate over a document model and write the corresponding data to a file on storing. The UNO component technology insulates the filter from binary layout, and other compiler and version dependent issues. Additionally, the API is expected to be more stable than the core interfaces, and provides an abstraction from the core applications. The developer creating an API based filter will directly provide a filter class implementing the service com.sun.star.document.ImportFilter and/or com.sun.star.document.ExportFilter

    The third is to import and export documents using the XML-based file format. UNO-based XML import and export components have all the advantages of the previous method, but they have the additional advantage that the filter logic builds upon the ODF model that is not bound to LibreOffice as the document API is and so theoretically can be used in other applications also (the filter logic, not the filter as a whole). A disadvantage may be that conversions based on the ODF format can become a little bit more complicated and also can be worse than conversions based on a document API if they require access to layout information in the source or the target format.

    The developer creating an XML based filter will not directly provide a filter class but use a generic filter class provided by LibreOffice. This filter class is the XMLFilterAdaptor of the document it works on. The filter adaptor service expects an XML based importer or exporter UNO service to be provided by the developer. LibreOffice provides generic importer and exporter services that allow to plug in a xslt to carry out a transformation that the importer or exporter feeds into the XMLFilterAdaptor.

    In addition to the filter itself the developer must provide some information about it to enable LibreOffice to integrate it into the application framework. This information is provided as a configuration file. To understand better what needs to be in this file let's have a look on how LibreOffice deals with filters.

    Checklist for filter developers

    Integrating a filter into LibreOffice requires the following steps that will be explained in the following sections:

    1. Implement a filter (required).
    2. Implement an com.sun.star.document.ExtendedTypeDetection service to support detection by content (optional).
    3. Implement a filter options dialog if the implemented filter requires additional parameters (optional).
    4. Register the component libraries as UNO services (required). If the filter is deployed as an extension this is a part of it. If the filter will become a part of the LibreOffice installation, the registration must be done as described in the chapter Deployment Options for Components.
    5. Add configuration information to the org.openoffice.TypeDetection node of the configuration (required). If the filter is deployed as an extension, the extension will contain a configuration file. LibreOffice will access it as part of the extensions layer of the Configuration Manager. If the filter will become a part of the LibreOffice installation, the configuration must be integrated into the build process.

    It is recommended to read the following chapters before carrying out any of these steps.

    Filtering Process

    In LibreOffice the whole process of loading or saving content is a modular system based on UNO services. Some of them are abstract (like e.g. the com.sun.star.document.ExtendedTypeDetection and the filter services) and so allow to bind extendable sets of instances implementing them, others (like e.g. the com.sun.star.document.TypeDetection service) are those that define the work flow. As they are exchangeable like any UNO service the whole process of finding and using filters can be changed without any need to change other involved components.

    Loading content

    The most general way to load content into LibreOffice is calling the loadComponentFromURL() method of a suitable object. Such object may be the com.sun.star.frame.Desktop object or any instance of the com.sun.star.frame.Frame service. Content loaded this way will end up in a frame object always, if called at the desktop the method will find or create this frame using some of the passed arguments as described in the API documentation linked above. Then it will forward the call to this frame. Here's a diagram showing the workflow that will be explained in the following paragraphs.

    General Filtering Process

    The content will be passed to the loadComponentFromURL() call as a com.sun.star.document.MediaDescriptor service that here is implemented as a Sequence of com.sun.star.beans.PropertyValue. In most cases it will contain several properties that allow to create an object implementing com.sun.star.io.XStream or com.sun.star.io.XInputStream that can be used to read the content. It also may contain some properties that the code of other objects (filter, model, controller, frame etc.) can use to steer the loading process. If no properties shall be handed over and the file content is specified by a URL only, the URL can be passed as an explicit argument and the MediaDescriptor can stay empty. To understand how to work with the MediaDescriptor in the implementation of a filter or elsewhere, especially how to retrieve a stream from it, see the documentation of it in the chapter about loading documents.

    The component loader uses instances of the com.sun.star.frame.FrameLoader or com.sun.star.frame.SynchronousFrameLoader services. Which frame loader instance will be used depends on the type of the content. This type must be detected first (see below) based on the TypeDetection configuration that allows to register filters or frame loaders for a particular type. LibreOffice has a generic Frame Loader service that is used when the detected type has no own frame loader registered but filters. If a custom frame loader is registered for a particular type, it's up to that implementation how the content loading process is carried out and if it uses filters or not. As the current topic is "filters", we will concentrate on the generic frame loader here.

    Note pin.svg

    Note:
    The com.sun.star.frame.FrameLoader service is deprecated. If a custom frame loader is registered, it should be a com.sun.star.frame.SynchronousFrameLoader service.

    To load content based on a filter first it must be detected which filter is the right one to use and which document type must be used for this filter to work properly. As basically any content type may be loaded into any available document type and even the same type could be loaded into the same document type in different ways, we could find many registered filters for a particular content type. Finding the right one by evaluating what is passed in the MediaDescriptor is the job of the com.sun.star.document.TypeDetection service. The result of this detection will be the name of the content type, the name of the wanted filter and the service name of the document model that shall be the target of the loading process. These results will be placed into the MediaDescriptor so that any code in other objects called later can use that information. By providing either the type name of the content or the document service name in the MediaDescriptor handed over to the component loader the search for a filter can be narrowed down to a subset of filters that match these criteria. By providing a filter name in the MediaDescriptor the detection can even be bypassed completely (the component loader will add the matching type and document service names to the MediaDescriptor though). As the whole process of the Type Detection is completely based on the configuration, it will be described in the chapter about the TypeDetection configuration.

    The next steps will be managed by the generic com.sun.star.frame.SynchronousFrameLoader service and hands the target frame over to it. The Frame Loader will create the document of the wanted type using the document service name found in the MediaDescriptor. It will also take the detected filter name and ask the com.sun.star.document.FilterFactory service to create the filter and perhaps initialize it with some necessary parameters and ask it for importing the content into the new document (this is described in the chapter about filters). If all of this went fine, it will attach the document to the target frame by creating a Controller object for the document model.

    Storing content

    A MediaDescriptor is passed to storeToURL() or storeAsURL() in the interface com.sun.star.frame.XStorable, implemented by office documents. It will contain several properties that allow to create an object implementing com.sun.star.io.XStream or com.sun.star.io.XOutputStream that can be used to store the content. It also may contain some properties that give more information about how the storing process should be done. If no properties shall be handed over and the target file is specified by a URL only, the URL can be passed as an explicit argument and the MediaDescriptor can stay empty. To understand how to work with the MediaDescriptor in the implementation of a filter or elsewhere, especially how to retrieve a stream from it, see the documentation about it in the chapter about loading documents.

    If the MediaDescriptor contains a type name or a filter name, the suitable export filter will be created using the FilterFactory. If neither of them is provided, the document will be stored with the latest ODF filter.

    Filters

    As described in the previous chapter, filters are objects that can be used to import or export content into or from LibreOffice documents. The API defines the two services com.sun.star.document.ImportFilter and com.sun.star.document.ExportFilter. A filter implementation can support one or both of them. If a particular filter that only supports import is used, the imported document is modified by the user and then the user presses the "Save" button, a "Save As" operation will be carried out instead as LibreOffice must assume that the filter component is "import only" and so storing must be done in a different format. It doesn't help to have a different export filter for the same content. If a direct "Save" operation should be possible, both filter services must be supported at the same filter object.

    A filter is created from the factory service com.sun.star.document.FilterFactory. This service also provides a low-level access to the configuration that knows all registered filters of LibreOffice and their properties, supports search and query functionality, and creates and initializes filter components. As an example, if the type name of a content is known, a query at the FilterFactory can be used to retrieve one or more internal filter names of possible filters and after choosing one of them the filter can be created using this name.

    Note pin.svg

    Note:
    Many existing filters are legacy filters. The component loader or XStorable implementation does not use the FilterFactory to create them, but triggers filtering by internal C++ calls. So asking the FilterFactory to create such filter is not possible.

    Filters can be initialized if they implement the interface com.sun.star.lang.XInitialization. The method initialize() is used by the filter factory service directly after creation of the filter object, before the filter is returned to the code that requested the filter. It passes the configuration data of the filter and all parameters and options that have been specified by the creation request to the factory. These properties usually originate from the "FilterOptions" property of the MediaDescriptor and have been put there either by the code requesting the loading or storing or by user input in a filter options dialog. How such filter dialog can be implemented is explained in the chapter about filter options.

    The parameter list of initialize() uses the following protocol:

    Warning.svg

    Warning:
    The interface provides functionality for reading and writing of this name. It is not allowed to change an internal filter name during runtime of LibreOffice, because all filter names must be unique, and it is not possible for a filter instance to alter its name. Calls to setName() should be ignored or forwarded to the FilterFactory service, which knows all unique names and can solve ambiguities!


    The fact that a filter gets its own name passed as an argument can be used to use one filter implementation to act as several filters in the configuration. This is shown in the following code snippet of the implementation of a filter initialization:

    private String m_sInternalName;
    public void initialize( Object[] lArguments )
            throws com.sun.star.uno.Exception
    {
            // no arguments - no initialization
            if (lArguments.length<1)
                    return;
            // Arguments[0] = own configuration data
            com.sun.star.beans.PropertyValue[] lConfig =
                    (com.sun.star.beans.PropertyValue[])lArguments[0];
    
            // Arguments[1..n] = optional arguments of create request
            for (int n=1; n<lArguments.length; ++n)
            {
                    ...
            }
    
            // analyze own configuration data for our own internal
            // filter name! Important for generic filter services,
            // which are registered more then once. They can use this
            // information to find out, which specialization of it
            // is required.
            for (int i=0; i<lConfig.length; ++i)
            {
                    if (lConfig[i].Name.equals("Name"))
                    {
                            m_sInternalName =
                                    AnyConverter.toString(lConfig[i].Value);
    
                            // Tip: A generic filter implementation can use this internal
                            // name at runtime, to detect which specialization of it is required.
                            if (m_sInternalName =="filter_format_1")
                                    m_eHandle = E_FORMAT_1;
                            else
                            if (m_sInternalName =="filter_format_2")
                                    ...
                    }
            }
    }

    In one single workflow filters can act as an import or an export filter. If content is loaded, the creator of the filter will use the com.sun.star.document.XImporter interface and its method setTargetDocument() to bind the filter to the document it should import into. If content is stored, the interface com.sun.star.document.XExporter and its method setSourceDocument() will bind the filter to the document it shall get data from. The filtering process is done by the same method in both cases: it's the filter() method of the com.sun.star.document.XFilter interface that both kinds of filters must support. Here the MediaDescriptor is passed to the filter.

    Tip.svg

    Tip:
    In an object that implements both import and export it is necessary to decide whether importing or exporting is asked for when the filter() method is called. The differentiator is whether setTargetDocument or setSourceDocument has been called before (see sample code below). The user of a filter is responsible to make this call in a valid order.


    This example code shows how the required filter operation can be tracked inside the filter implementation easily:

    private boolean m_bImport;
    
    // used to tell us: "you will be used for import"
    public void setTargetDocument(
      com.sun.star.lang.XComponent xDocument )
        throws com.sun.star.lang.IllegalArgumentException
    {
        m_bImport = true;
    }
    
    // used to tell us: "you will be used for export"
    public void setSourceDocument(
      com.sun.star.lang.XComponent xDocument )
        throws com.sun.star.lang.IllegalArgumentException
    {
        m_bImport = false;
    }
    
    // detect required type of filter operation
    public boolean filter(
      com.sun.star.beans.PropertyValue[] lDescriptor )
    {
          boolean bState;
          if (m_bImport)
            bState = impl_import( lDescriptor );
          else
            bState = impl_export( lDescriptor );
          return bState;
    }

    Type Detection and its Configuration

    Structure of the configuration

    As described previously, detecting types and finding filters in LibreOffice is carried out by the com.sun.star.document.TypeDetection service that uses configuration data as input. The configuration node that contains all this information is org.openoffice.TypeDetection. Here's the basic structure of it:

    Structure of org.openoffice.Office.TypeDetection Configuration Branch

    As shown on the left, the node consists of structures that in the terminology of the Configuration Manager are called sets. As opposed to configuration lists, sets are extendable configuration nodes and this allows the Configuration Manager to merge several files containing the same node together and presenting all set elements found in any of the merged files as part of a common set. This is different to lists: if the same list is found in several configuration files, one of them will overwrite the others. The ability to merge configuration nodes enables the deployment of filter configuration data (and so the deployment of filters) in extensions. Without it all filter configuration data had to be defined in the LibreOffice installation.

    There are three lists: types, filters and frame loaders. A type describes a content, while filters or frame loaders describe objects that can be used to load such content into an LibreOffice document. Arrows in the picture point to structures on the right side. They show the content (properties) of different list elements. Similar to 1:n relations in a database, every filter or frame loader is registered for one or multiple types.

    Warning.svg

    Warning:
    If you want to add filters to the configuration, it is not a good idea to edit the installed configuration files of LibreOffice directly. It would be better to provide the data as an extension and install this extension for a single or all users.


    TypeDetection

    Before the properties of types, filters and frame loaders will be described in close detail, let's have a look on how the Type Detection uses them to detect types and filters. The com.sun.star.document.TypeDetection service can be used to just detect the type of a particular content. While a type is detected, it is possible that some information about a possible filter for that type already may have accrued. In case the TypeDetection is part of a loading process where not only a type but also a filter needs to be detected, this suggestion can be used to save an extra filter detection step. This detection otherwise had to be carried out by the generic frame loader by accessing the filter configuration data through the com.sun.star.document.FilterFactory service.

    When the TypeDetection receives a URL or a MediaDescriptor, it will first check some "external" attributes of the content specified this way. This could be a file extension, a URL pattern or other properties in the MediaDescriptor. If the MediaDescriptor does not already contain the name of the content type, the best match of the data in the "Types" part of the TypeDetection configuration to these attributes is sought. See the chapter about the type properties what kind of attributes are available and how they are used.

    If a type has been detected based on these attributes, LibreOffice can verify this detection based on real code that checks the content, not only its external attributes. For this purpose each type may have an attribute "DetectService". It is an implementation or service name of an object that implements the abstract service com.sun.star.document.ExtendedTypeDetection. This object will examine the content. It will get a MediaDescriptor containing the name of the type to confirm and it will return this name in case it matches the content. It is allowed to return another type name if the DetectService knows that this type matches better even if the external attributes may not have selected it in the first place.

    If the external attributes didn't help LibreOffice to find a type, it will instantiate all registered DetectServices and ask them to check the content until any of them returns a valid type name. The called DetectService can detect that it is called for "guessing", not for confirmation as in this case no type name is passed to it in the MediaDescriptor.

    The next step is to check if a frame loader is registered for the detected type. If no frame loader is found, the generic frame loader implementation of LibreOffice is used. As mentioned above, this service will detect a filter in case the TypeDetection service not already has given this information. This detection is easily done by using a filter query at the com.sun.star.document.FilterFactory service. This query encapsulates the algorithm how LibreOffice assigns a filter to a type. The result of this query will be the internal filter name of the desired filter and the FilterFactory then can be asked to create the filter. Note: filter queries can return more than one filter name, depending on the input. If no preferences have been given, the first one in the returned sequence will win.

    The most important external attribute of a content is a file extension and often just this one is used. As these extensions don't need to be unique, LibreOffice may find several possible types for an extension. While there is a preferred type (or at least there should be one), it is possible for API programmers to override this by a type preselection. It is also possible to use a filter preselection or a document type preselection. The latter can be seen as a suggestion to LibreOffice to load a content with a particular LibreOffice application. If this is possible, LibreOffice will do that, otherwise it will proceed as usual. One of the most common use cases is to load a html file by Calc from the command line. By using "soffice -Calc $FILENAME" instead of just "soffice $FILENAME" a document type preselection is triggered.

    Properties of a Type

    Every type inside LibreOffice is specified by the properties shown in the table below. These values are accessible at the previously mentioned service com.sun.star.document.TypeDetection using the interface com.sun.star.container.XNameAccess. Write access is not available here. All types are addressed by their internal names. The property names are identical to the configuration property names.

    Warning.svg

    Warning:
    The documentation of the TypeDetection service currently is outdated. In case of conflicting information about the properties this page is right. An update will follow soon.


    Properties of a Document Type, available at TypeDetection
    Name string. The internal name of the type. This is only an API property, not a configuration property. In the configuration this is the name of the configuration node containing all the other properties. To avoid name clashes with other node names it should follow the rules outlined for extension identifiers.
    UIName string. User friendly name of the type. It may be localized using the localization support of the configuration. All Unicode characters are permitted here. Currently, this UI name is not used in LibreOffice itself, but it is available for e.g. extensions.
    MediaType string. Describes the MIME type of the Type.
    ClipboardFormat string. The property name is misleading and has historical reasons. This property contains a format name that is used somewhere in the content itself to enable filters loading it to verify that the content has the right type. It may be the "doctype" in a XML file, the storage format identifier in an OLE storage or the MIMEType of and ODF document. This name can be used by a generic ExtendedTypeDetection service that is used for more than one format.
    URLPattern sequence<string>. Enables the support of own URL schemata, that always shall be handled by the same filter(s). For example, in LibreOffice "private:factory/swriter" for opening an empty text document. The wildcards '*' or '?' are supported here.
    Extensions sequence<string>. All extensions that this type is expected to have usually. As always in the configuration the default separator character is SPACE.
    Preferred boolean. Several file types may use the same extension; this property tells LibreOffice that a particular type should get preference over others. Of course that only works if not every type uses that property. If no flag is set, the type found first in the Types set wins.
    DetectService string. LibreOffice uses the properties described above of all types (like extension or MediaType) to guess the type of a particular file. Once a possible type is found, it is possible to call some code that examines the file more closely to verify the simple detection ("flat" and "deep" detection). The DetectService property is the service or implementation name of the UNO service that can do this task.
    PreferredFilter After a successful type detection LibreOffice will look for a filter that can load this type. It is possible that more than one filter is present. This property tells which filter should be the preferred one. This selection can be overridden by explicit preselection of a different filter (by API or by user interaction in the file dialog).
    DocumentIconID int. Deprecated. Please don't use.

    Properties of a Filter

    Every filter inside LibreOffice is specified by the properties shown in the table below. These values are accessible at the previously mentioned service com.sun.star.document.FilterFactory using the interface com.sun.star.container.XNameAccess. Write access is not available here. All types are addressed by their internal names. The property names are identical to the configuration property names.

    Warning.svg

    Warning:
    The documentation of the FilterFactory service currently is outdated. In case of conflicting information about the properties this page is right. An update will follow soon.


    A filter always is registered for only one type. A single filter implementation can handle several types but then must be registered multiple times. One type may handled by more than one filter.

    Property Name Description
    Name string. The internal name of the filter. This is only an API property, not a configuration property. In the configuration this is the name of the configuration node containing all the other properties. To avoid name clashes with other node names it should follow the rules outlined for extension identifiers.
    UIName string. User friendly name of the type. It may be localized using the localization support of the configuration. All Unicode characters are permitted here. The UI names of filters are used in the various file dialog.
    Type string. A filter is registered for the type it can handle. Multiple assignments are not allowed. Use multiple configuration entries to support more than one type with a single filter implementation.
    DocumentService string. UNO service name describing the component on which the filter can operate. As an example, "com.sun.star.text.TextDocument" specifies the filter as working with text documents. An object of this type will be passed to the filter in its setTargetDocument() or setSourceDocument() methods.
    FilterService string. This is the UNO service or implementation name of the filter. Of course a service name can be used only if it is not the generic com.sun.star.document.ImportFilter or com.sun.star.document.ExportFilter. In case of XML based filters the service name always must be com.sun.star.comp.Writer.XMLFilterAdaptor. This filter will use the "UserData" property to find the XMLImporter or XMLExporter service it shall instantiate.
    UIComponent string. UNO service or implementation name of a UI component (usually a dialog) where users can parameterize the filtering process. As an example, the "Text - txt - csv (StarCalc)" filter needs information about the used column separators to load data. More about that in the chapter about filter options.
    Flags
    • XML : string list. Some boolean attributes of a filter.
    Filter flags
    3RDPARTYFILTER

    0x00080000 h

    The filter is a UNO component filter, as opposed to the internal C++ filters. This is an artefact that will vanish over time.
    ALIEN

    0x00000040 h

    The filter may lose some information upon saving.
    DEFAULT

    0x00000100 h

    This is the "best" filter for the document type it works on that is guaranteed not so lose any data on export. By default this filter will be used or suggested for every storing process unless the user has chosen a different default filter in the options dialog.
    EXPORT

    0x00000002 h

    The filter supports the service com.sun.star.document.ExportFilter. It will be shown in the dialog "File-Export". If the filter also has the "IMPORT" flag set, it will be shown in the dialog "File-Save". This makes sure that a format that a user chooses in the save dialog can be loaded again. The same is not guaranteed for a format chosen in "File-Export".
    IMPORT

    0x00000001 h

    The filter supports service com.sun.star.document.ImportFilter. The filter will be shown in the dialog "File-Open".
    INTERNAL

    0x00000008 h

    This filter is used only for internal purposes and so can be used only in API calls. Users won't see it ever.
    NOTINCHOOSER

    0x00002000 h

    This filter will not be shown in the dialog box for chosing a filter in case LibreOffice was not able to detect one
    NOTINFILEDIALOG

    0x00001000 h

    This filter will not be shown in the file dialog's filter list
    OWN

    0x00000020 h

    The filter is a native LibreOffice format (ODF or otherwise).
    PREFERRED

    0x10000000 h

    The filter is preferred in case of multiple filters for the same file type exist in the configuration
    READONLY

    0x00010000 h

    All documents imported by this filter will automatically be in read-only state
    SUPPORTSSELECTION

    0x00000400 h

    Filter can export only the selected part of a document. This information enables LibreOffice to enable a corresponding check box in the "File-Export" dialog.
    TEMPLATE

    0x00000004 h

    Filter denotes a template filter (means, by default all documents opened by it become an "untitled" one)
    TEMPLATEPATH

    0x00000010 h

    Must always be set together with "TEMPLATE" to make this feature flag work; soon becoming deprecated
    UserData sequence<string>. Some filters need to store more configuration to work properly. The format of the string list is not restricted. An important example is the filter that implements the XMLFilterAdaptor service. It will get the service name of the XML based filter it must instantiate from the user data. In case this XML based filter is one of the XML adaptors for xslt files, the user data also must contain at least an absolute or relative file path point to the xslt file carrying out the transformation.
    FileFormatVersion int. Indicates that a filter handles only a particular format version of the content type. If a filter implementation can handle several versions of a filter, several configuration entries have to be created. This is not necessary if the filter handles all possible versions and the different versions don't need to appear in the user interface.
    TemplateName string. The name of a template file for styles the target document of an import filter shall use. If this property is set, the document merges the styles of this template with its default styles before the import starts.
    Warning.svg

    Warning:
    Besides these filter flags there are other flags existing that are used currently, but are not documented here. Use documented flags only.


    Sample configuration for an API based filter

    <!-- Filter section -->
    <node oor:name="Text (encoded)" oor:op="replace">
        <prop oor:name="UIName">
            <value>Text (encoded)</value>
        </prop>
        <prop oor:name="Flags"><value>IMPORT EXPORT ALIEN</value></prop>
        <prop oor:name="UIComponent"><value>com.sun.star.comp.Writer.FilterOptionsDialog</value></prop>
        <prop oor:name="FilterService"/>
        <prop oor:name="UserData"><value>TEXT_DLG</value></prop>
        <prop oor:name="FileFormatVersion"><value>0</value></prop>
        <prop oor:name="Type"><value>writer_Text_encoded</value></prop>
        <prop oor:name="TemplateName"/>
        <prop oor:name="DocumentService"><value>com.sun.star.text.TextDocument</value></prop>
    </node>

    Note pin.svg

    Note:
    This filter has a UIComponent that shows that this filter needs additional information to load a file properly (the encoding of it in case it can't be detected). It does not have a "FilterService" property because it is an internal, C++ based filter.

    Sample configuration for an XML based filter

    <!-- Filter section -->
    <node oor:name="devguide_FlatXMLFilter_Java_writer" oor:op="replace">
        <prop oor:name="UIName">
            <value>DevGuide FlatXML Writer</value>
        </prop>
        <prop oor:name="Type"><value>devguide_FlatXMLType_Cpp_writer</value></prop>
        <prop oor:name="FileFormatVersion"><value>0</value></prop>
        <prop oor:name="DocumentService"><value>com.sun.star.text.TextDocument</value></prop>
        <prop oor:name="FilterService"><value>com.sun.star.comp.Writer.XmlFilterAdaptor</value></prop>
        <prop oor:name="UIComponent"/>
        <prop oor:name="UserData"><value oor:separator=",">devguide.officedev.samples.filter.FlatXmlJava, ,com.sun.star.comp.Writer.XMLOasisImporter,com.sun.star.comp.Writer.XMLOasisExporter</value></prop>
        <prop oor:name="TemplateName"/>
        <prop oor:name="Flags"><value>IMPORT EXPORT ALIEN 3RDPARTYFILTER</value></prop>
    </node>

    Note pin.svg

    Note:
    This filter uses the XMLFilterAdaptor service. The "UserData" property is a space-separated list where the first list element denotes the XMLImportFilter to be instantiated and the third one and fourth one are service names of DocumentHandlers for import and export. Further parameters depend on the XMLImportFilter used.

    Sample configuration for an xslt based filter

    <!-- Filter section -->
    <node oor:name="MediaWiki" oor:op="replace">
        <prop oor:name="FileFormatVersion"><value>0</value></prop>
            <prop oor:name="Type"><value>MediaWiki</value></prop>
            <prop oor:name="DocumentService"><value>com.sun.star.text.TextDocument</value></prop>
            <prop oor:name="UIComponent"/>
            <prop oor:name="UserData"><value oor:separator=",">com.sun.star.documentconversion.XSLTFilter,,,com.sun.star.comp.Writer.XMLOasisExporter,,../share/xslt/wiki/odt2mediawiki.xsl</value></prop>
            <prop oor:name="FilterService"><value>com.sun.star.comp.Writer.XmlFilterAdaptor</value></prop>
            <prop oor:name="UIName">
                <value xml:lang="x-default">MediaWiki</value>
            </prop>
            <prop oor:name="Flags"><value>EXPORT ALIEN 3RDPARTYFILTER</value></prop>
          </node>

    Note pin.svg

    Note:
    This filter uses the XMLFilterAdaptor service. The "UserData" property is a space-separated list where the first list element denotes the XMLImportFilter to be instantiated and the third one and fourth one are service names of DocumentHandlers for import and export. Further parameters depend on the XMLImportFilter used.

    Here the XMLFilter used is the com.sun.star.documentconversion.XSLTFilter that bridges to xslt files and basically can import and export if a suitable xslt is provided. It expects the following parameters in the user data ($(APP) stands for Writer,Calc,Draw,Impress,Chart,Math):

    (1) service name of the XSLTFilter
    (3) implementation/service name of an importer class (com.sun.star.comp.$(APP).XMLOasisImporter)
    (4) implementation/service name of an exporter class (com.sun.star.comp.$(APP).XMLOasisExporter)
    (5) relative or absolute path name to an importing style sheet
    (6) relative or absolute path name to an importing style sheet
    
    Note pin.svg

    Note:
    Older xslt based filters might have used the pre-ODF file format of OpenOffice.org. They used importer and exporter class without "Oasis" in their names.

    Filter Options

    A filter may need some additional information before it can import or export properly. As an example, the LibreOffice filter "Text - txt - csv (StarCalc)" needs a separator used to detect columns. This information is transported as a property FilterData inside the MediaDescriptor. The value depends on the filter implementation and is not specified (type any). It's up to the filter to deal with it and handle it properly. The MediaDescriptor may contain another property of type string named FilterOptions. It can be used if the flexibility of an any is not required and the small overhead to retrieve the string from the any is unwanted. The filter must document which of these properties it uses and how the information transported by it must be shaped.

    Warning.svg

    Warning:
    For historical reasons, a third string property FilterFlags exists. It is deprecated, so don't use it.


    There are two possible origins of the FilterData or FilterOptions property:

    • The loading or storing process is triggered from code that has the necessary information and provides it in the code.
    • A user has entered the data into a dialog that LibreOffice has presented to him and retrieved it from the dialog after the user closed it. The dialog is shown by a UNO service that is specified by the UIComponent property of the filter configuration. LibreOffice will not show the dialog if the MediaDescriptor already contains one of the named properties.

    The service specified by the UIComponent property is the only way how a filter can retrieve parameters interactively. The filter code itself must not do that because it may be used in an environment that does not allow to show any UI elements, especially modal ones. Here providing filter options by API calls is the only way to provide them to the filter.

    The UIComponent is an object implementing the com.sun.star.ui.dialogs.FilterOptionsDialog. It will be called in a modal way, means with an execute() call that must guarantee that when it returns, all parameters can be retrieved from the UIComponent by its interface com.sun.star.beans.XPropertyAccess. The same interface is used to transfer the current MediaDescriptor to the UIComponent before executing it. In the execute() call the implementer can use any UI elements, usually a dialog will be used here. The UIComponent must provide the filter options with the correct property names and the value of the property must be as the filter implementation understands it. The framework code will copy the retrieved properties to the MediaDescriptor without any change.

    If a filter doesn't get any FilterOptions or FilterData though it needs some, it may use default values for them, if possible. Usually this is always possible for export filters, but not always for import filters. The filter implementation should use a configuration file to retrieve default values instead of providing them in the code, so that they can be changed easily. Whether the filter updates the configuration values to the last recently used values passed by MediaDescriptor or if it never changes them itself is up to the filter developer.

    Properties of a FrameLoader

    LibreOffice knows asynchronous (com.sun.star.frame.FrameLoader) and synchronous (com.sun.star.frame.SynchronousFrameLoader) frame loader implementations. They are not distinguished by the configuration data but detected at runtime, synchronous loaders are preferred.

    When a document is loaded using a loadComponentFromURL() call, a frame loader will always be used to load the "component" (in most cases a document model) into a frame. If no frame is registered for the type to be loaded, the generic frame loader of LibreOffice will be used that will proceed with selecting a filter and using it. Custom frame loaders are not obliged to use filters and can do anything else instead. An expected use case is when the component isn't a model but e.g. a controller or window object.

    Every FrameLoader inside LibreOffice is specified by the properties shown in the table below. These values are accessible at the service com.sun.star.frame.FrameLoaderFactory using the interface com.sun.star.container.XNameAccess. Write access is not available here. All types are addressed by their internal names. The property names are identical to the configuration property names.

    Properties of a FrameLoader
    Name string. The internal name of the loader. This is only an API property, not a configuration property. In the configuration, this is the name of the configuration node containing all the other properties. To avoid name clashes with other node names it should follow the rules outlined for extension identifiers.
    UIName string. User-friendly name of the type. It may be localized using the localization support of the configuration. All Unicode characters are permitted here. Currently, this UI name is not used in LibreOffice itself, but it is available for e.g. extensions.
    Types sequence<string>. A list of internal type names that this service can handle.

    Document API Filter Development

    TBD: Examples.

    XML Based Filter Development

    Introduction

    This chapter outlines the development of XML based filtering components that use the XML filter adaptor framework. Further information is also available at https://www.openoffice.org/xml/filter/. The XML filter adaptor is a generic com.sun.star.document.XFilter implementation. It has been designed to be reusable, and to supply a standard method of designing and referencing XML based import and export filters. The XML filter adaptor does not perform any of the filtering functionality itself, but instead is used to instantiate a filtering component.

    The advantage of the XML filter adapter framework is that you do not have to work with document models to create a document from an import file, nor do you have to iterate over a document model to export it to a different file format. Rather, you can use the LibreOffice XML file format to import and export. When importing, you parse your import file and send LibreOffice XML to the filter adaptor, which creates a document for you in the GUI. When exporting, the office sends a description of the current document as LibreOffice XML, so that you can export without having to iterate over a document model.

    As described in the chapter about filters a filter works through its method filter(). In case of XML based filters this method is implemented by the XML filter adaptor. Based on its "UserData" property it instantiates an XML import filter and uses its method importer() to pass a MediaDescriptor for the source, a specialized XML document handler for LibreOffice XML, and user data. The import filter must read the import source and deliver LibreOffice XML to the document handler received in the call to importer(), emulating a SAX parser that calls the parser callback functions.

    In case of export filters the same filter() call will use the "UserData" property to instantiate an XML export filter and use its method exporter() to pass a target location and user data. In this case, the office expects the export filter to be a com.sun.star.xml.sax.XDocumentHandler, which is able to handle LibreOffice XML. The office creates an export stream with LibreOffice XML, and parses this XML so that the export filter receives the SAX callbacks and can translate them to whatever is necessary, writing the result to the target received in the call to exporter().

    Sample implementations

    There are Java and a C++ implementations of a Flat LibreOffice XML reader and writer which use the XML filter adapter. These are intended to be sample filter component implementations, and offer a skeleton filter component that can be expanded upon by developers wishing to create their own filtering components. These are hosted in git at

     odk/examples/DevelopersGuide/OfficeDev/FilterDevelopment/FlatXmlFilter_java
    

    and

     odk/examples/DevelopersGuide/OfficeDev/FilterDevelopment/FlatXmlFilter_cpp
    
    Writing the Filtering Component

    The filtering component must implement the following interfaces as described by the com.sun.star.xml.ImportFilter service and the com.sun.star.xml.ExportFilter service:

    Importer:

    com.sun.star.xml.XMLImportFilter

    Exporter:

    com.sun.star.xml.XMLExportFilter and com.sun.star.xml.sax.XDocumentHandler
    XImportFilter

    The service com.sun.star.xml.XMLImportFilter defines an interface with the following method:

    boolean importer(
    [in] sequence< com::sun::star::beans::PropertyValue > aSourceData,
    [in] com::sun::star::xml::sax::XDocumentHandler xDocHandler,
    [in] sequence< string > msUserData )

    aSourceData is a MediaDescriptor, which can be used to obtain the following information:

    • An XInputStream
    This is a stream that is attached to the source to be read. This can be a file, or some other data source.
    • Filename
    This is the name of the file on the disk, that the input stream comes from.
    • Url
    This is an URL describing the location being read.

    xDocHandler is a SAX event handler that can be used when parsing an XInputStream, which may or may not contain LibreOffice XML. Before this stream can be read by LibreOffice, it will need to be transformed into LibreOffice XML.

    msUserData is an array of Strings, that contains the information supplied in the UserData section of the Filter definition in the TypeDetection.xcu file.

    XExportFilter

    The com.sun.star.xml.XExportFilter defines an interface with the following method:

    boolean exporter(
    [in] sequence< com::sun::star::beans::PropertyValue > aSourceData,
    [in] sequence< string > msUserData )

    aSourceData and msUserData contain the same type of information as in the importer, except that the MediaDescriptor contains an XOutputStream, which can be used to write to.

    XDocumentHandler

    When the export takes place, the new Filtering component must also be an XDocumentHandler, to allow the output based on SAX events to be filtered, if required. For this reason, an XDocumentHandler is not passed to the exporter, and any exporter that is used by the XML filter adaptor must implement the com.sun.star.xml.sax.XDocumentHandler interface.

    Note pin.svg

    Note:
    In order for Java based components to operate effectively, a set of wrapper classes have been added to the javaunohelper package. These files allow for an XInputStream or an XOutputStream to be accessed using the same methods as a normal Java InputStream or OutputStream. These classes are located in the javaunohelper package at

    • com.sun.star.lib.uno.adapter.XInputStreamToInputStreamAdapter
    • com.sun.star.lib.uno.adapter.XOutputStreamToOutputStreamAdapter

    For more information on the use of these helper classes, see the flatxmljava example.

    The Importer
    Evaluating XImportFilter Parameters

    The writing of an importer usually starts with extracting the required variables from the MediaDescriptor and the userData. These variables are required for the filtering component to operate correctly. Depending on the requirements of the individual filter, the first thing to do is to extract the information from the MediaDescriptor, referred to as aSourceData in the interface definition. This can be achieved as follows:

    Get the number of elements in the MediaDescriptor

    sal_Int32 nLength = aSourceData.getLength();

    Iterate through the MediaDescriptor to find the information needed: an input stream, a file name, or a URL.

    for (sal_Int32 i = 0; i < nLength; i++) {
            if (pValue[i].Name == "InputStream")
                    pValue[i].Value >>= xInputStream;
            else if (pValue[i].Name == "FileName")
                    pValue[i].Value >>= sFileName;
            else if (pValue[i].Name == "URL")
                    pValue[i].Value >>= sURL;
    }

    The msUserData parameter passed to importer() contains information that defines how the filter operates, so this information must be referenced as required.

    Importer Filtering

    An XInputStream implementation has now been obtained that contains all the information you want to process. From the filtering perspective, you can just read from this stream and carry out whatever processing is required in order for the input to be transformed into LibreOffice XML. Once this has been done, however, you need to write the result to where it can be parsed into LibreOffice's internal format. A Pipe can be used to achieve this. A Pipe is a form of buffer that can be written to and read from. For the importer, read from the XInputStream that was extracted from the MediaDescriptor, and once the filtering has taken place, write to a Pipe that has been created. This Pipe can be read from when it comes to parsing. This is how the Pipe is created:

    Reference <XInterface> xPipe;
    
    // We Create our pipe
    xPipe= XflatXml::xMSF->createInstance(OUString::createFromAscii("com.sun.star.io.Pipe"));
    
    // We get an inputStream to our Pipe
    Reference< com::sun::star::io::XInputStream > xPipeInput (xPipe,UNO_QUERY);
    
    // We get an OutputStream to our Pipe
    Reference< com::sun::star::io::XOutputStream > xTmpOutputStream (xPipe,UNO_QUERY);

    The XInputStream can be read from, and the XOutputstream can be written to.

    Parsing the Result

    Once the desired LibreOffice XML has been produced and written to the XOutputStream of the Pipe, the XinputStream of the Pipe can be parsed with the aid of the XDocumentHandler.

    // Create a Parser
    const OUString sSaxParser("com.sun.star.xml.sax.Parser");
    Reference < com::sun::star::xml::sax::XParser > xSaxParser(xMSF->createInstance(sSaxParser), UNO_QUERY);
    
    // Create an InputSource using the Pipe
    com::sun::star::xml::sax::InputSource aInput;
    aInput.sSystemId = sFileName;           // File Name
    aInput.aInputStream = xPipeInput;       // Pipe InputStream
    
    // Set the SAX Event Handler
    xSaxParser->setDocumentHandler(xHandler);
    
    // Parse the result
    try {
      xSaxParser->parseStream(aInput);
    }
    catch (Exception &exc) {
      // Perform exception handling
    }

    Assuming that the XML was valid, no exceptions will be thrown and the importer will return true. At this stage, the filtering is complete and the imported document will be displayed.

    The Exporter
    Evaluating XExportFilter Parameters

    The exporter() method operates in much the same way as importer(), except that instead of the exporter using a provided XDocumentHandler, it is itself a com.sun.star.xml.sax.XDocumentHandler implementation.

    When the exporter() method is invoked, the necessary variables need to be extracted for use by the filter. This is the same thing that happens with the importer, except that the MediaDescriptor contains an XOutputStream, instead of the importer's XInputStream. Once the variables have been extracted (and - in some cases - a Pipe has been created) the exporter() method returns. It does not carry out the filtering at this stage.

    Note pin.svg

    Note:
    The pipe is only necessary if the output needs to be processed further after being processed by the XDocumentHandler. Otherwise, the result from the XDocumentHandler implementation can be written directly to the XOutputStream provided. For instance, this is the case with a FlatXML filter.

    Exporter Filtering

    After the exporter() method returns, the XML filter adapter then invokes the com.sun.star.xml.sax.XDocumentHandler methods to parse the XML output.

    For the filtering, the com.sun.star.xml.sax.XDocumentHandler implementation is used. This consists of a set of SAX event handling methods, which define how particular XML tags are handled. These methods are:

    startDocument() {
    }
    endDocument() {
    }
    startElement() {
    }
    endElement() {
    }
    charactors() {
    }
    ignorableWhitespace() {
    }
    processingInstruction() {
    }
    setDocumentLocator() {
    }

    The result of this event handling can be processed and written to the XOutputStream that was extracted from the MediaDescriptor.

    XML Filter Detection

    The number of XML files that conform to differing DTD specifications means that a single filter and file type definition is insufficient to handle all of the possible formats available. In order to allow LibreOffice to handle multiple filter definitions and implementations, it is necessary to implement an additional filter detection module that is capable of determining the type of XML file being read, based on its DocType declaration.

    To accomplish this, a filter detection service com.sun.star.document.ExtendedTypeDetection can be implemented, which is capable of handling and distinguishing between many different XML based file formats. This type of service supersedes the basic flat detection, which uses the file's suffix to determine the Type, and instead, carries out a deep detection which uses the file's internal structure and content to detect its true type.

    Requirements for Deep Detection

    There are three requirements for implementing a deep detection module that is capable of identifying one or more unique XML types. These include:

    • An extended type definition for describing the format in more detail (TypeDetection.xcu).
    • A DetectService implementation.
    • A DetectService definition (TypeDetection.xcu).
    Extending the File Type Definition

    Since many different XML files can conform to different DTDs, the type definition of a particular XML file needs to be extended. To do this, some or all of the DocType information can be contained as part of the file type definition. This information is held as part of the ClipboardFormat property of the type node. A unique namespace or preface identifies the string at this point in the sequence as being a DocType declaration.

    Sample Type definition:

    <node oor:name="writer_DocBook_File" oor:op="replace">
        <prop oor:name="UIName">
            <value XML:lang="en-US">DocBook</value>
        </prop>
        <prop oor:name="Data">
            <value>0,,doctype:-//OASIS//DTD DocBook XML V4.1.2//EN,,XML,20002,</value>
        </prop>
    </node>

    The ExtendedTypeDetection Service Implementation

    In order for the type detection code to function as an ExtendedTypeDetection service, you must implement the detect() method as defined by the com.sun.star.document.XExtendedFilterDetection interface definition:

    string detect( [inout]sequence<com::sun::star::beans::PropertyValue > Descriptor );

    This method supplies you with a sequence of PropertyValues from which you can use to extract the current TypeName and the URL of the file being loaded:

    ::rtl::OUString SAL_CALL FilterDetect::detect(com::sun::star::uno::Sequence< com::sun::star::beans::PropertyValue >& aArguments )
    {
    const PropertyValue * pValue = aArguments.getConstArray();
    sal_Int32 nLength;
    ::rtl::OString resultString;
    nLength = aArguments.getLength();
    for (sal_Int32 i = 0; i < nLength; i++) {
            if (pValue[i].Name == "TypeName") {
            }
            else if (pValue[i].Name == "URL") {
                    pValue[i].Value >>= sUrl;
            }
    }

    Once you have the URL of the file, you can then use it to create a ::ucb::Content from which you can open an XInputStream to the file:

    Reference< com::sun::star::ucb::XCommandEnvironment > xEnv;
    ::ucb::Content aContent(sUrl,xEnv);
    xInStream = aContent.openStream();

    You can now use this XInputStream to read the header of the file being loaded. Because the exact location of the DocType information within the file is not known, the first 1000 bytes of information will be read:

    ::rtl::OString resultString;
    com::sun::star::uno::Sequence< sal_Int8 > aData;
    long bytestRead = xInStream->readBytes (aData, 1000);
    resultString = ::rtl::OString( (const char *)aData.getConstArray(), bytestRead);

    Once you have this information, you can start looking for a type that describes the file being loaded. In order to do this, you need to get a list of the types currently supported:

    Reference <XNameAccess> xTypeCont(mxMSF->createInstance(OUString::createFromAscii(
                                    "com.sun.star.document.TypeDetection" )),UNO_QUERY);
    Sequence <::rtl::OUString> myTypes= xTypeCont->getElementNames();
    nLength = myTypes.getLength();

    For each of these types, you must first determine whether the ClipboardFormat property contains a DocType:

    Loc_of_ClipboardFormat=...;
    Sequence<::rtl::OUString> ClipboardFormatSeq;
    Type_Props[Loc_of_ClipboardFormat].Value >>=ClipboardFormatSeq ;
    while() {
            if(ClipboardFormatSeq.match(OUString::createFromAscii("doctype:") {
                        //if it contains a DocType, start to compare to header
            }
    }

    All the possible DocType declarations of the file types can be checked to determine a match. If a match is found, the type corresponding to the match is returned. If no match is found, an empty string is returned. This will force LibreOffice into flat detection mode.

    TypeDetection.xcu DetectServices Entry

    Now that you have created the ExtendedTypeDetection service implementation, you need to tell LibreOffice when to use this service.

    First create a DetectServices node, unless one already exists, and then add the information specific to the detection service that has been implemented, that is, the name of the service and the file types that use it.

    <node oor:name="DetectServices">
    <node oor:name="com.sun.star.comp.filters.XMLDetect" oor:op="replace">
            <prop oor:name="ServiceName">
                    <value XML:lang="en-US">com.sun.star.comp.filters.XMLDetect</value>
            </prop>
            <prop oor:name="Types">
                    <value>writer_DocBook_File</value>
                    <value>writer_Flat_XML_File</value>
            </prop>
    </node>
    </node>

    Number Formats

    Number formats are template strings consisting of format codes defining how numbers or text appear, for example, whether or not to display trailing zeros, group by thousands, separators, colors, and how many decimals are displayed. This does not include any font attributes, except for colors. They are found wherever number formats are applied, for example, on the Numbers tab of the Format ▸ Cells dialog in spreadsheets.

    Number formats are defined on the document level. A document displaying formatted values has a collection of number formats, each with a unique index key within that document. Identical formats are not necessarily represented by the same index key in different documents.

    Managing Number Formats

    Documents provide their formats through the interface com.sun.star.util.XNumberFormatsSupplier that has one method getNumberFormats() that returns com.sun.star.util.NumberFormats. Using NumberFormats, developers can read and modify number formats in documents, and also add new formats.

    You have to retrieve the NumberFormatsSupplier as a property at a few objects from their com.sun.star.beans.XPropertySet interface, for example, from data sources supporting the com.sun.star.sdb.DataSource service and from database connections supporting the service com.sun.star.sdb.DatabaseEnvironment, or com.sun.star.sdb.DatabaseAccess. In addition, all UNO controls offering the service com.sun.star.awt.UnoControlFormattedFieldModel have a NumberFormatsSupplier property.

    NumberFormats Service

    The com.sun.star.util.NumberFormats service specifies a container of number formats and implements the interfaces com.sun.star.util.XNumberFormatTypes and com.sun.star.util.XNumberFormats.

    XNumberFormats

    NumberFormats supports the interface com.sun.star.util.XNumberFormats. This interface provides access to the number formats of a container. It is used to query the properties of a number format by an index key, retrieve a list of available number format keys of a given type for a given locale, query the key for a user-defined format string, or add new format codes into the list or to remove formats.

    com::sun::star::beans::XPropertySet getByKey ( [in] long nKey )
    
    sequence< long > queryKeys ( [in] short nType,
                    [in] com::sun::star::lang::Locale nLocale,
                    [in] boolean bCreate )
    long queryKey ( [in] string aFormat,
                    [in] com::sun::star::lang::Locale nLocale,
                    [in] boolean bScan )
    
    long addNew ( [in] string aFormat, [in] com::sun::star::lang::Locale nLocale )
    long addNewConverted ( [in] string aFormat, [in] com::sun::star::lang::Locale nLocale,
                    [in] com::sun::star::lang::Locale nNewLocale )
    
    void removeByKey ( [in] long nKey )
    
    string generateFormat ( [in] long nBaseKey, [in] com::sun::star::lang::Locale nLocale,
                    [in] boolean bThousands, [in] boolean bRed, [in] short nDecimals, [in] short nLeading )

    The important methods are probably queryKey() and addNew(). The method queryKey() finds the key for a given format string and locale, whereas addNew() creates a new format in the container and returns its key for immediate use. The bScan is reserved for future use and should be set to false.

    The properties of a single number format are obtained by a call to getByKey() which returns a com.sun.star.util.NumberFormatProperties service for the given index key.

    XNumberFormatTypes

    The interface com.sun.star.util.XNumberFormatTypes offers functions to retrieve the index keys of specific predefined number format types. The predefined types are addressed by constants from com.sun.star.util.NumberFormat. The NumberFormat contains values for predefined format types, such as PERCENT, TIME, CURRENCY, and TEXT.

    long getStandardIndex ( [in] com::sun::star::lang::Locale nLocale )
    long getStandardFormat ( [in] short nType,
                  [in] com::sun::star::lang::Locale nLocale )
    long getFormatIndex ( [in] short nIndex,
                  [in] com::sun::star::lang::Locale nLocale )
    
    boolean isTypeCompatible ( [in] short nOldType, [in] short nNewType )
    
    long getFormatForLocale ( [in] long nKey,
                  [in] com::sun::star::lang::Locale nLocale )

    In most cases you will need getStandardFormat(). It expects a type constant from the NumberFormat group and the locale to use, and returns the key of the corresponding predefined format.

    Applying Number Formats

    To format numeric values, an XNumberFormatsSupplier is attached to an instance of a com.sun.star.util.NumberFormatter, available at the global service manager. For this purpose, its main interface com.sun.star.util.XNumberFormatter has a method attachNumberFormatsSupplier(). When the XNumberFormatsSupplier is attached, strings and numeric values are formatted using the methods of the NumberFormatter. To specify the format to apply, you have to get the unique index key for one of the formats defined in NumberFormats. These keys are available at the XNumberFormats and XNumberFormatTypes interface of NumberFormats.

    Numbers in documents, such as in table cells, formulas, and text fields, are formatted by applying the format key to the NumberFormat property of the appropriate element.

    NumberFormatter Service

    The service com.sun.star.util.NumberFormatter implements the interfaces com.sun.star.util.XNumberFormatter and com.sun.star.util.XNumberFormatPreviewer.

    XNumberformatter

    The interface com.sun.star.util.XNumberFormatter converts numbers to strings, or strings to numbers, or detects a number format matching a given string.

    void attachNumberFormatsSupplier ( [in] com::sun::star::util::XNumberFormatsSupplier xSupplier )
    com::sun::star::util::XNumberFormatsSupplier getNumberFormatsSupplier ()
    
    long detectNumberFormat ( [in] long nKey, [in] string aString )
    
    double convertStringToNumber ( [in] long nKey, [in] string aString )
    string convertNumberToString ( [in] long nKey, [in] double fValue );
    
    com::sun::star::util::color queryColorForNumber ( [in] long nKey, [in] double fValue,
                                                  [in] com::sun::star::util::color aDefaultColor )
    string formatString ( [in] long nKey, [in] string aString );
    
    com::sun::star::util::color queryColorForString ( [in] long nKey, [in] string aString,
                                                  [in] com::sun::star::util::color aDefaultColor )
    
    string getInputString ( [in] long nKey, [in] double fValue )

    XNumberformatPreviewer

    string convertNumberToPreviewString ( [in] string aFormat, [in] double fValue,
                                    [in] com::sun::star::lang::Locale nLocale,
                                    [in] boolean bAllowEnglish )
    
    com::sun::star::util::color queryPreviewColorForNumber ( [in] string aFormat, [in] double fValue,
                                    [in] com::sun::star::lang::Locale nLocale,
                                    [in] boolean bAllowEnglish,
                                    [in] com::sun::star::util::color aDefaultColor )

    This interface com.sun.star.util.XNumberFormatPreviewer converts values to strings according to a given format code without inserting the format code into the underlying com.sun.star.util.NumberFormats collection.

    The example below demonstrates the usage of these interfaces.

    public void doSampleFunction() throws RuntimeException, Exception
    {
        // Assume:
        // com.sun.star.sheet.XSpreadsheetDocument maSpreadsheetDoc;
        // com.sun.star.sheet.XSpreadsheet maSheet;
    
        // Query the number formats supplier of the spreadsheet document
        com.sun.star.util.XNumberFormatsSupplier xNumberFormatsSupplier =
            (com.sun.star.util.XNumberFormatsSupplier)
            UnoRuntime.queryInterface(
            com.sun.star.util.XNumberFormatsSupplier.class, maSpreadsheetDoc );
    
        // Get the number formats from the supplier
        com.sun.star.util.XNumberFormats xNumberFormats =
            xNumberFormatsSupplier.getNumberFormats();
    
        // Query the XNumberFormatTypes interface
        com.sun.star.util.XNumberFormatTypes xNumberFormatTypes =
            (com.sun.star.util.XNumberFormatTypes)
            UnoRuntime.queryInterface(
            com.sun.star.util.XNumberFormatTypes.class, xNumberFormats );
    
        // Get the number format index key of the default currency format,
        // note the empty locale for default locale
        com.sun.star.lang.Locale aLocale = new com.sun.star.lang.Locale();
        int nCurrencyKey = xNumberFormatTypes.getStandardFormat(
            com.sun.star.util.NumberFormat.CURRENCY, aLocale );
    
        // Get cell range B3:B11
        com.sun.star.table.XCellRange xCellRange =
            maSheet.getCellRangeByPosition( 1, 2, 1, 10 );
    
        // Query the property set of the cell range
        com.sun.star.beans.XPropertySet xCellProp =
            (com.sun.star.beans.XPropertySet)
            UnoRuntime.queryInterface(
            com.sun.star.beans.XPropertySet.class, xCellRange );
    
        // Set number format to default currency
        xCellProp.setPropertyValue( "NumberFormat", Integer.valueOf(nCurrencyKey) );
    
        // Get cell B3
        com.sun.star.table.XCell xCell = maSheet.getCellByPosition( 1, 2 );
    
        // Query the property set of the cell
        xCellProp = (com.sun.star.beans.XPropertySet)
            UnoRuntime.queryInterface(
            com.sun.star.beans.XPropertySet.class, xCell );
    
        // Get the number format index key of the cell's properties
        int nIndexKey = ((Integer) xCellProp.getPropertyValue( "NumberFormat" )).intValue();
    
        // Get the properties of the number format
        com.sun.star.beans.XPropertySet xProp = xNumberFormats.getByKey( nIndexKey );
    
        // Get the format code string of the number format's properties
        String aFormatCode = (String) xProp.getPropertyValue( "FormatString" );
        System.out.println( "FormatString: `" + aFormatCode + "'" );
    
        // Create an arbitrary format code
        aFormatCode = "\"wonderful \"" + aFormatCode;
    
        // Test if it is already present
        nIndexKey = xNumberFormats.queryKey( aFormatCode, aLocale, false );
    
        // If not, add to number formats collection
        if ( nIndexKey == -1 )
        {
            try
            {
                nIndexKey = xNumberFormats.addNew( aFormatCode, aLocale );
            }
            catch( com.sun.star.util.MalformedNumberFormatException ex )
            {
                System.out.println( "Bad number format code: " + ex );
                nIndexKey = -1;
            }
        }
    
        // Set the new format at the cell
        if ( nIndexKey != -1 )
            xCellProp.setPropertyValue( "NumberFormat", Integer.valueOf(nIndexKey) );
    }

    Document Events

    Recurring actions, such as loading, printing or saving, that occur when working with documents, are document events, and all documents in LibreOffice offer an interface that sends notifications when these events take place.

    There are general events common every document, such as loading, printing, or saving, and there are other events that are specific to a particular document type. Both can be accessed through the same interface.

    In the document events API, these events are represented by an event name. The following table shows a list of all general document event names:

    General Document Event Names
    OnNew New Document was created
    OnLoad Document has been loaded
    OnSaveAs Document is going to be saved under a new name
    OnSaveAsDone Document was saved under a new name
    OnSave Document is going to be saved
    OnSaveDone Document was saved
    OnPrepareUnload Document is going to be removed, but still fully available
    OnUnload Document has been removed, document ist still valid, but closing can no longer be prevented
    OnFocus Document was activated
    OnUnfocus Document was deactivated
    OnPrint Document will be printed
    OnModifyChange Modified state of the document has changed

    These event names are documented in the com.sun.star.document.Events service. Note that this service description exceeds the scope of events that happen on the document as a whole - so it also contains events that can only be accessed by finding the part of the document where the event occurred, for example, a button in a form. This list of events can also be extended by new events, so that future versions of LibreOffice can support new types of events through the same API. Therefore, every client that wants to deal with a particular document event must check if this event is supported, or whether it should be prepared to catch an exception.

    Every client that is interested in document events can register for being notified. The necessary interface for notification is com.sun.star.document.XEventBroadcaster, which is an optional interface of the service com.sun.star.document.OfficeDocument. All document objects in LibreOffice implement this interface. It has two methods to add and remove listeners for document events:

    [oneway] void addEventListener( [in] ::com::sun::star::document::XEventListener Listener );
    [oneway] void removeEventListener( [in] ::com::sun::star::document::XEventListener Listener );

    The listeners must implement the interface com.sun.star.document.XEventListener and get a notification through a call of their method:

    [oneway] void notifyEvent( [in] ::com::sun::star::document::EventObject Event );

    The argument of this call is a com.sun.star.document.EventObject struct, which is derived from the usual com.sun.star.lang.EventObject and contains two members: the member Source, which contains an interface pointer to the event source (here the com.sun.star.document.OfficeDocument service) and the member EventName which can be one of the names shown in the preceding table.

    Both methods in the interface com.sun.star.document.XEventBroadcaster can cause problems in scripting languages if the object that implements this interface also implements com.sun.star.lang.XComponent, because it has two very similar methods:

    [oneway] void addEventListener( [in] ::com::sun::star::lang::XEventListener Listener );
    [oneway] void removeEventListener( [in] ::com::sun::star::lang::XEventListener Listener );

    Unfortunately this applies to all LibreOffice documents.

    In C++ and Java this is no problem, because the complete signature of a method, including the arguments, is used to identify it.

    In LibreOffice Basic, the fully qualified name including the interface can be used from OpenOffice.org 1.1.0:

    Sub RegisterListener
    
      oListener = CreateUnoListener( "DocumentListener_","com.sun.star.document.XEventListener" )
    
      ThisComponent.com_sun_star_document_XEventBroadcaster_addEventListener( oListener )
    
    End Sub
    
    Sub DocumentListener_notifyEvent( o as object )
    
      IF o.EventName = "OnPrepareUnload" THEN
            print o.Source.URL
      ENDIF
    
    end sub
    
    Sub DocumentListener_disposing()
    End Sub

    But the OLE automation bridge, and possibly other scripting language bindings, are unable to distinguish between both addEventListener() and removeEventListener() methods based on the method signature and must be told which interface you want to use.

    You must use the core reflection to get access to either method. The following code shows an example in VBScript, which registers a document event listener at the current document.

    set xContext = objServiceManager.getPropertyValue( "DefaultContext" )
    set xCoreReflection = xContext.getValueByName( "/singletons/com.sun.star.reflection.theCoreReflection" )
    set xClass = xCoreReflection.forName( "com.sun.star.document.XEventBroadcaster" )
    set xMethod = xClass.getMethod( "addEventListener" )
    
    dim invokeargs(0)
    invokeargs(0) = myListener
    
    set value = objServiceManager.Bridge_GetValueObject()
    call value.InitInOutParam("[]any", invokeargs)
    call xMethod.invoke( objDocument, value )

    The C++ code below uses OLE Automation. Two helper functions are provided that help to execute UNO operations.

    // helper function to execute UNO operations via IDispatch
    HRESULT ExecuteFunc( IDispatch* idispUnoObject,
                                            OLECHAR* sFuncName,
                                            CComVariant* params,
                                            unsigned int count,
                                            CComVariant* pResult )
    {
      if( !idispUnoObject )
        return E_FAIL;
    
    DISPID id;
    HRESULT hr = idispUnoObject->GetIDsOfNames( IID_NULL, &sFuncName, 1, LOCALE_USER_DEFAULT, &id);
    if( !SUCCEEDED( hr ) ) return hr;
    
    DISPPARAMS dispparams= { params, 0, count, 0};
    
    // DEBUG
    EXCEPINFO myInfo;
    return idispUnoObject->Invoke( id, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_METHOD,
                      &dispparams, pResult, &myInfo, 0);
    }
    // helper function to execute UNO methods that return interfaces
    HRESULT GetIDispByFunc( IDispatch* idispUnoObject,
                                                      OLECHAR* sFuncName,
                                                      CComVariant* params,
                                                      unsigned int count,
                                                      CComPtr<IDispatch>& pdispResult )
    {
      if( !idispUnoObject )
        return E_FAIL;
    
      CComVariant result;
      HRESULT hr = ExecuteFunc( idispUnoObject, sFuncName, params, count, &result );
      if( !SUCCEEDED( hr ) ) return hr;
    
      if( result.vt != VT_DISPATCH || result.pdispVal == NULL )
        return E_FAIL;
    
      pdispResult = CComPtr<IDispatch>( result.pdispVal );
    
      return S_OK;
    }
    
    // it's assumed that pServiceManager (by creating it as a COM object), pDocument (f.e. by loading it)
    // and pListener (the listener we want to add) are passed as parameters
    
    HRESULT AddDocumentEventListener(
      CComPtr<IDispatch> pServiceManager, CComPtr<IDispatch> pDocument, CComPtr<IDispatch> pListener)
    
    {
      CComPtr<IDispatch> pdispContext;
      hr = GetIDispByFunc( pServiceManager, L"getPropertyValue", &CComVariant( L"DefaultContext" ), 1,
                  pdispContext );
      if( !SUCCEEDED( hr ) ) return hr;
    
      CComPtr<IDispatch> pdispCoreReflection;
      hr = GetIDispByFunc( pdispContext,
                            L"getValueByName",
                            &CcomVariant( L"/singletons/com.sun.star.reflection.theCoreReflection" ),
                            1,
                            pdispCoreReflection );
      if( !SUCCEEDED( hr ) ) return hr;
    
      CComPtr<IDispatch> pdispClass;
      hr = GetIDispByFunc( pdispCoreReflection,
                            L"forName",
                            &CComVariant( L"com.sun.star.document.XEventBroadcaster" ),
                            1,
                            pdispClass );
      if( !SUCCEEDED( hr ) ) return hr;
    
      CComPtr<IDispatch> pdispMethod;
      hr = GetIDispByFunc( pdispClass, L"getMethod", &CComVariant( L"addEventListener" ), 1, pdispMethod );
      if( !SUCCEEDED( hr ) ) return hr;
    
      CComPtr<IDispatch> pdispListener;
      CComPtr<IDispatch> pdispValueObj;
      hr = GetIDispByFunc( mpDispFactory, L"Bridge_GetValueObject", NULL, 0, pdispValueObj );
      if( !SUCCEEDED( hr ) ) return hr;
    
      CComVariant pValParams[2];
      pValParams[1] = CComVariant( L"com.sun.star.document.XEventListener" );
      pValParams[0] = CComVariant( pdispListener );
      CComVariant dummyResult;
      hr = ExecuteFunc( pdispValueObj, L"Set", pValParams, 2, &dummyResult );
      if( !SUCCEEDED( hr ) ) return hr;
    
      SAFEARRAY* pPropVal = SafeArrayCreateVector( VT_VARIANT, 0, 1 );
      long ix1 = 0;
    
      CComVariant aArgs( pdispValueObj );
      SafeArrayPutElement( pPropVal, &ix, &aArgs );
    
      CComVariant aDoc( pdispDocument );
      CComVariant pParams[2];
      pParams[1] = aDoc;
      pParams[0].vt = VT_ARRAY | VT_VARIANT; pParams[0].parray = pPropVal;
    
      CComVariant result;
    
      //invoking the method addeventlistner
      hr = ExecuteFunc( pdispMethod, L"invoke", pParams, 2, &result );
      if( !SUCCEEDED( hr ) ) return hr;
    
      return S_OK;
    }

    Another way to react to document events is to bind a macro to it - a process called event binding. From OpenOffice.org 1.1.0 you can also use scripts in other languages, provided that a corresponding scripting framework implementation is present.

    All document objects in LibreOffice support event binding through an interface com.sun.star.document.XEventsSupplier. This interface has only one method:

    ::com::sun::star::container::XNameReplace getEvents();

    This method gives access to a container of event bindings. The container is represented by a com.sun.star.container.XNameReplace interface that, together with the methods of its base interfaces, offers the following methods:

    void replaceByName( [in] string aName, [in] any aElement );
    any getByName( [in] string aName );
    sequence< string > getElementNames();
    boolean hasByName( [in] string aName );
    type getElementType();
    boolean hasElements();

    Each container element represents an event binding. By default, all bindings are empty. The element names are the event names shown in the preceding table. In addition, there are document type-specific events. The method getElementNames() yields all possible events that are supported by the object and hasByName() checks for the existence of a particular event.

    For every supported event name you can use getByName() to query for the current event binding or replaceByName() to set a new one. Both methods may throw a com.sun.star.container.NoSuchElementException exception if an unsupported event name is used.

    The type of an event binding, which is wrapped in the any returned by getByName(), is a sequence of com.sun.star.beans.PropertyValue that describes the event binding.

    PropertyValue structs in the event binding description
    EventType string. Can assume the values "StarBasic" or "Script". The event type "Script" describes the location as URL.The event type "StarBasic" is provided for compatibility reasons and describes the location of the macro through the properties Library and MacroName, in addition to URL.
    Script string. Available for the event types Script and StarBasic. Describes the location of the macro/script routine which is bound. For the URL property, a command URL is expected (see Using the Dispatch Framework). LibreOffice will execute this command when the event occurs.

    For the event type StarBasic, the URL uses the macro: protocol. For the event type Script, other protocols are possible, especially the script: protocol.

    The macro protocol has two forms:

     macro:///<Library>.<Module>.<Method(args)>
     macro://./<Library>.<Module>.<Method(args)>
    

    The first form points to a method in the global basic storage, while the second one points to a method embedded in the current document. <Library>.<Module>.<Method(args)> represent the names of the library, the module and the method. Currently, for args only string arguments (separated by comma) are possible. If no args exist, empty brackets must be used, because the brackets are part of the scheme. An example URL could look like:

     macro:///MyLib.MyModule.MyMethod(foo,bar)
    

    The exact form of the script: command URL protocol depends on the installed scripting module. They will be available later as additional components for OpenOffice.org 1.1.0.

    Library string. Deprecated. Available for EventType "StarBasic". Can assume the values "application" or empty string for the global basic storage, and "document" for the document where the code is embedded.
    MacroName string. Deprecated. Available for EventType "StarBasic". Describes the macro location as <Library>.<MyModule>.<MyMethod>.
    Note pin.svg

    Note:
    In OpenOffice.org 1.1.0 all properties (URL, Library, MacroName) will be returned for event bindings of type StarBasic, regardless if the binding was created with a URL property only or with the Library and MacroName property. The internal implementation does the necessary conversion. Older versions of LibreOffice always returned only Library and MacroName, even if the binding was created with the URL property.

    In OpenOffice.org 1.1.0 there is another important extension in the area of document events and event bindings. This version has a new service com.sun.star.frame.GlobalEventBroadcaster that offers the same document-event-related functionality as described previously (interfaces com.sun.star.document.XEventBroadcaster, com.sun.star.document.XEventsSupplier), but it allows you to register for events that happen in any document and also allows you to set bindings for all documents that are stored in the global UI configuration of LibreOffice. Using this services frees you from registering at every single document that has been created or loaded.

    Though a potential listener registers for event notifications at this global service and not at any document itself, the received event source in the event notification is the document, not the GlobalEventBroadcaster. The reason for this is that usually a listener contains code that works on the document, so it needs a reference to it.

    The service com.sun.star.frame.GlobalEventBroadcaster also supports two more events that do not occur in any document but are useful for working with document events:

    Global Event Names
    OnStartApp Application has been started
    OnCloseApp Application is going to be closed. This event is fired after all documents have been closed and nobody objected to the shutdown.

    The event source in the notifications is NULL (empty).

    All event bindings can be seen or set in the LibreOffice UI in the Tools ▸ Configure dialog on the Events page. Two radio buttons on the right side of the dialog toggle between LibreOffice and Document binding. In OpenOffice.org 1.1.0, you can still only bind to LibreOffice Basic macros in the dialog. Bindings to script: URLs can only be set using the API, but the dialog is at least able to display them. If, in OpenOffice.org 1.1.0, a global and a document binding are set for the same event, first the global and then the document binding is executed. With older versions, only the document binding was executed, and the global binding was only executed if no document binding was set.

    Path Organization

    The path settings service is the central service that manages the paths of LibreOffice. Almost every component inside LibreOffice uses one or more of the paths to access its resources located on the file system.

    Users can customize most of the paths in LibreOffice by choosing Tools ▸ Options ▸ OpenOffice ▸ Paths.

    Path Settings

    The com.sun.star.util.PathSettings service supports a number of properties which store the LibreOffice predefined paths. There are two different groups of properties. One group stores only a single path and the other group stores two or more paths - separated by a semicolon.

    Properties of com.sun.star.util.PathSettings
    Addin Single path Specifies the directory that contains spreadsheet add-ins which use the old add-in API.
    AutoCorrect Multi path Specifies the directories that contain the settings for the AutoCorrect dialog.
    AutoText Multi path Specifies the directories that contain the AutoText modules.
    Backup Single path Specifies the directory for storing automatic backup copies of documents.
    Basic Multi path Specifies the location of the Basic files that are used by the AutoPilots.
    Bitmap Single path Specifies the directory that contains the external icons for the toolbars.
    Config Single path Specifies the location of the configuration files. This property is not visible in the LibreOffice path options dialog and cannot be changed by users.
    Dictionary Single path Specifies the location of the LibreOffice dictionaries.
    Favorite Single path Specifies the directory that contains the saved folder bookmarks.
    Filter Single path Specifies the directory where the filters are stored.
    Gallery Multi path Specifies the directories that contain the Gallery database and multimedia files.
    Graphic Single path Specifies the directory that is displayed when the dialog for opening a graphic or for saving a new graphic is called.
    Help Single path Specifies the location of the Office help files.
    Linguistic Single path Specifies the directory where the spellcheck files are stored.
    Module Single path Specifies the directory where the modules are stored.
    Palette Single path Specifies the location of the palette files that contain user-defined colors and patterns (*.SOB and *.SOF).
    Plugin Multi path Specifies the directories where the Plugins are stored.
    Storage Single path Specifies the directory where mail and news files as well as other information (for example, about FTP Server) are stored. This property is not visible in the LibreOffice path options dialog and cannot be changed by users.
    Temp Single path Specifies the directory for the office temp-files.
    Template Multi path Specifies the directory for the LibreOffice document templates.
    UIConfig Multi path Specifies the location of global directories when looking for user interface configuration files. The user interface configuration is merged with the user settings that are stored in the directory specified by UserConfig.
    UserConfig Single path Specifies the directory that contains the user settings, including the user interface configuration files for menus, toolbars, accelerators and status bars.
    UserDictionary Single path Specifies the directory for the custom dictionaries.
    Work Single path Specifies the location of the work folder. This path can be modified according to the user's needs and can be seen in the Open or Save dialog.
    Configuration

    The path settings service uses the group Path in the org.Openoffice.Office.Common branch to read and store paths. The Current and Default groups in the share layer of the configuration branch store the path settings properties. The Current group initialize the properties of the path settings service during startup. If the user activates the Default button in the path options dialog, the Default group values are copied to the current ones.

    Note pin.svg

    Note:
    Note: The configuration branch separates the paths of a property with a colon (:), whereas the path settings service separates multiple paths with a semicolon (;).

    <?xml version='1.0' encoding='UTF-8'?>
    <oor:component-schema oor:name="Common" oor:package="org.openoffice.Office" xml:lang="en-US" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://wwww.w3.org/2001/XMLSchema-instance">
        <component>
            <group oor:name="Path">
                <group oor:name="Current">
                    <prop oor:name="OfficeInstall" oor:type="xs:string">
                        <value/>
                    </prop>
                    <prop oor:name="OfficeInstallURL" oor:type="xs:string">
                        <value/>
                    </prop>
                    <prop oor:name="Addin" oor:type="xs:string">
                        <value>$(progpath)/addin</value>
                    </prop>
                    <prop oor:name="AutoCorrect" oor:type="oor:string-list">
                        <value oor:separator=":">$(insturl)/share/autocorr:$(userurl)/autocorr</value>
                    </prop>
                    <prop oor:name="AutoText" oor:type="oor:string-list">
                        <value oor:separator=":">
                            $(insturl)/share/autotext/$(vlang):$(userurl)/autotext
                        </value>
                    </prop>
                    <prop oor:name="Backup" oor:type="xs:string">
                        <value>$(userurl)/backup</value>
                    </prop>
                    <prop oor:name="Basic" oor:type="oor:string-list">
                        <value oor:separator=":">$(insturl)/share/basic:$(userurl)/basic</value>
                    </prop>
                    <prop oor:name="Bitmap" oor:type="xs:string">
                        <value>$(insturl)/share/config/symbol</value>
                    </prop>
                    <prop oor:name="Config" oor:type="xs:string">
                        <value>$(insturl)/share/config</value>
                    </prop>
                    <prop oor:name="Dictionary" oor:type="xs:string">
                        <value>$(insturl)/share/wordbook/$(vlang)</value>
                    </prop>
                    <prop oor:name="Favorite" oor:type="xs:string">
                        <value>$(userurl)/config/folders</value>
                    </prop>
                    <prop oor:name="Filter" oor:type="xs:string">
                        <value>$(progpath)/filter</value>
                    </prop>
                    <prop oor:name="Gallery" oor:type="oor:string-list">
                      <value oor:separator=":">$(insturl)/share/gallery:$(userurl)/gallery</value>
                    </prop>
                    <prop oor:name="Graphic" oor:type="xs:string">
                        <value>$(insturl)/share/gallery</value>
                    </prop>
                    <prop oor:name="Help" oor:type="xs:string">
                        <value>$(instpath)/help</value>
                    </prop>
                    <prop oor:name="Linguistic" oor:type="xs:string">
                        <value>$(insturl)/share/dict</value>
                    </prop>
                    <prop oor:name="Module" oor:type="xs:string">
                        <value>$(progpath)</value>
                    </prop>
                    <prop oor:name="Palette" oor:type="xs:string">
                        <value>$(userurl)/config</value>
                    </prop>
                    <prop oor:name="Plugin" oor:type="oor:string-list">
                        <value oor:separator=":">$(userpath)/plugin</value>
                    </prop>
                    <prop oor:name="Storage" oor:type="xs:string">
                        <value>$(userpath)/store</value>
                    </prop>
                    <prop oor:name="Temp" oor:type="xs:string">
                        <value>$(temp)</value>
                    </prop>
                    <prop oor:name="Template" oor:type="oor:string-list">
                        <value oor:separator=":">
                            $(insturl)/share/template/$(vlang):$(userurl)/template
                        </value>
                    </prop>
                    <prop oor:name="UIConfig" oor:type="oor:string-list">
                        <value oor:separator=":"/>
                    </prop>
                    <prop oor:name="UserConfig" oor:type="xs:string">
                        <value>$(userurl)/config</value>
                    </prop>
                    <prop oor:name="UserDictionary" oor:type="xs:string">
                        <value>$(userurl)/wordbook</value>
                    </prop>
                    <prop oor:name="Work" oor:type="xs:string">
                        <value>$(work)</value>
                    </prop>
                </group>
                <group oor:name="Default">
                    <prop oor:name="Addin" oor:type="xs:string">
                        <value>$(progpath)/addin</value>
                    </prop>
                    <prop oor:name="AutoCorrect" oor:type="oor:string-list">
                        <value oor:separator=":">
                            $(insturl)/share/autocorr:$(userurl)/autocorr
                        </value>
                    </prop>
                    <prop oor:name="AutoText" oor:type="oor:string-list">
                        <value oor:separator=":">
                            $(insturl)/share/autotext/$(vlang):$(userurl)/autotext
                        </value>
                    </prop>
                    <prop oor:name="Backup" oor:type="xs:string">
                        <value>$(userurl)/backup</value>
                    </prop>
                    <prop oor:name="Basic" oor:type="oor:string-list">
                        <value oor:separator=":">$(insturl)/share/basic:$(userurl)/basic</value>
                    </prop>
                    <prop oor:name="Bitmap" oor:type="xs:string">
                        <value>$(insturl)/share/config/symbol</value>
                    </prop>
                    <prop oor:name="Config" oor:type="xs:string">
                        <value>$(insturl)/share/config</value>
                    </prop>
                    <prop oor:name="Dictionary" oor:type="xs:string">
                        <value>$(insturl)/share/wordbook/$(vlang)</value>
                    </prop>
                    <prop oor:name="Favorite" oor:type="xs:string">
                        <value>$(userurl)/config/folders</value>
                    </prop>
                    <prop oor:name="Filter" oor:type="xs:string">
                        <value>$(progpath)/filter</value>
                    </prop>
                    <prop oor:name="Gallery" oor:type="oor:string-list">
                        <value oor:separator=":">$(insturl)/share/gallery:$(userurl)/gallery</value>
                    </prop>
                    <prop oor:name="Graphic" oor:type="xs:string">
                        <value>$(insturl)/share/gallery</value>
                    </prop>
                    <prop oor:name="Help" oor:type="xs:string">
                        <value>$(instpath)/help</value>
                    </prop>
                    <prop oor:name="Linguistic" oor:type="xs:string">
                        <value>$(insturl)/share/dict</value>
                    </prop>
                    <prop oor:name="Module" oor:type="xs:string">
                        <value>$(progpath)</value>
                    </prop>
                    <prop oor:name="Palette" oor:type="xs:string">
                        <value>$(userurl)/config</value>
                    </prop>
                    <prop oor:name="Plugin" oor:type="oor:string-list">
                        <value oor:separator=":">$(userpath)/plugin</value>
                    </prop>
                    <prop oor:name="Temp" oor:type="xs:string">
                        <value>$(temp)</value>
                    </prop>
                    <prop oor:name="Template" oor:type="oor:string-list">
                        <value oor:separator=":">
                            $(insturl)/share/template/$(vlang):$(userurl)/template
                        </value>
                    </prop>
                    <prop oor:name="UIConfig" oor:type="oor:string-list">
                        <value oor:separator=":"/>
                    </prop>
                    <prop oor:name="UserConfig" oor:type="xs:string">
                        <value>$(userurl)/config</value>
                    </prop>
                    <prop oor:name="UserDictionary" oor:type="xs:string">
                        <value>$(userurl)/wordbook</value>
                    </prop>
                    <prop oor:name="Work" oor:type="xs:string">
                        <value>$(work)</value>
                    </prop>
                </group>
            </group>
        </component>
    </oor:component-schema>

    Accessing Path Settings

    The path settings service is a one-instance service that supports the com.sun.star.beans.XPropertySet, com.sun.star.beans.XFastPropertySet and com.sun.star.beans.XMultiPropertySet interfaces for access to the properties.

    The service can be created using the service manager of LibreOffice and the service name com.sun.star.util.PathSettings. The following example creates the path settings service.

    import com.sun.star.lang.XMultiServiceFactory;
    import com.sun.star.uno.Exception;
    import com.sun.star.uno.XInterface;
    import com.sun.star.beans.XPropertySet
    
    XPropertySet createPathSettings() {
    
        // Obtain Process Service Manager.
        XMultiServiceFactory xServiceFactory = ...
    
        // Create Path settings service. Needs to be done only once per process.
        XInterface xPathSettings;
        try {
            xPathSettings = xServiceFactory.createInstance("com.sun.star.util.PathSettings" );
        }
        catch (com.sun.star.uno.Exception e) {
        }
    
        if (xPathSettings != null)
            return UnoRuntime.queryInterface(XPropertySet.class, xPathSettings);
        else
            return null;
    }

    The main interface of the path settings service is com.sun.star.beans.XPropertySet. You can retrieve and write properties with this interface. It also supports getting information about the properties themselves.

    • com::sun::star::beans::XPropertySetInfo getPropertySetInfo();
    The path settings service returns an XPropertySetInfo interface where more information about the path properties can be retrieved. The information includes the name of the property, a handle for faster access with XFastPropertySet, the type of the property value and attribute values.
    • void setPropertyValue( [in] string aPropertyName, [in] any aValue );
    This function can set the path properties to a new value. The path settings service expects that a value of type string is provided. The string must be a correctly encoded file URL. If the path property supports multiple paths, each path must be separated by a semicolon (;). Path variables are also allowed, so long as they can be resolved to a valid file URL.
    • any getPropertyValue( [in] string PropertyName );
    This function retrieves the value of a path property. The property name must be provided and the path is returned. The path settings service always returns the path as a file URL. If the property value includes multiple paths, each path is separated by a semicolon (;).
    Note pin.svg

    Note:
    Note: The path settings service always provides property values as file URLs. Properties which are marked as multi path (see table above) use a semicolon (;) as a separator for the different paths. The service also expects that a new value for a path property is provided as a file URL or has a preceding path variable, otherwise a com.sun.star.lang.IllegalArgumentException is thrown.

    The illustration below shows how the path settings, path substitution, and configuration service work together to read or write path properties.

    Interaction of path settings, path substitution and configuration
    Warning.svg

    Warning:
    Important: Keep in mind that the paths managed by the path settings service are vital for almost all of the functions in LibreOffice. Entering a wrong path can result in minor malfunctions or break the complete LibreOffice installation. Although the path settings service performs a validity check on the provided URL, this cannot prevent all problems.


    The following code example uses the path settings service to retrieve and set the path properties.

    import com.sun.star.bridge.XUnoUrlResolver;
    import com.sun.star.uno.UnoRuntime;
    import com.sun.star.uno.XComponentContext;
    import com.sun.star.lang.XMultiComponentFactory;
    import com.sun.star.beans.XPropertySet;
    import com.sun.star.beans.PropertyValue;
    import com.sun.star.beans.UnknownPropertyException;
    /* Provides example code how to access and use the
      * path pathsettings servce.
      */
    public class PathSettingsTest {
    
        /*
          * List of predefined path variables supported by
          * the path settings service.
          */
        private static String[] predefinedPathProperties = {
            "Addin",
            "AutoCorrect",
            "AutoText",
            "Backup",
            "Basic",
            "Bitmap",
            "Config",
            "Dictionary",
            "Favorite",
            "Filter",
            "Gallery",
            "Graphic",
            "Help",
            "Linguistic",
            "Module",
            "Palette",
            "Plugin",
            "Storage",
            "Temp",
            "Template",
            "UIConfig",
            "UserConfig",
            "UserDictionary",
            "Work"
        };
    
        /*
          * @param args the command line arguments
          */
        public static void main(String[] args) {
    
            XComponentContext xRemoteContext = null;
            XMultiComponentFactory xRemoteServiceManager = null;
            XPropertySet xPathSettingsService = null;
    
            try {
                // connect
                XComponentContext xLocalContext =
                    com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
                XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager();
                Object urlResolver = xLocalServiceManager.createInstanceWithContext(
                    "com.sun.star.bridge.UnoUrlResolver", xLocalContext );
                XUnoUrlResolver xUnoUrlResolver = UnoRuntime.queryInterface(
                    XUnoUrlResolver.class, urlResolver );
                Object initialObject = xUnoUrlResolver.resolve(
                    "uno:socket,host=localhost,port=2083;urp;StarOffice.ServiceManager" );
                XPropertySet xPropertySet = UnoRuntime.queryInterface(
                    XPropertySet.class, initialObject);
                Object context = xPropertySet.getPropertyValue("DefaultContext");
                xRemoteContext = UnoRuntime.queryInterface(
                    XComponentContext.class, context);
                xRemoteServiceManager = xRemoteContext.getServiceManager();
    
                Object pathSubst = xRemoteServiceManager.createInstanceWithContext(
                    "com.sun.star.comp.framework.PathSettings", xRemoteContext );
                xPathSettingsService = UnoRuntime.queryInterface(
                    XPropertySet.class, pathSubst);
    
                /* Work with path settings */
                workWithPathSettings( xPathSettingsService );
            }
            catch (java.lang.Exception e) {
                e.printStackTrace();
            }
            finally {
                System.exit(0);
            }
        }
        /*
          * Retrieve and set path properties from path settings service
          * @param xPathSettingsService the path settings service
          */
        public static void workWithPathSettings(XPropertySet xPathSettingsService) {
            if (xPathSettingsService != null) {
                for (int i=0; i<predefinedPathProperties.length; i++) {
                    try {
                        /* Retrieve values for path properties from path settings service*/
                        Object aValue = xPathSettingsService.getPropertyValue(predefinedPathProperties[i]);
    
                        // getPropertyValue returns an Object, you have to cast it to type that you need
                        String aPath = (String)aValue;
                        System.out.println("Property="+ predefinedPathProperties[i] + " Path=" + aPath);
                    }
                    catch (com.sun.star.beans.UnknownPropertyException e) {
                        System.out.println("UnknownPropertyException has been thrown accessing "+predefinedPathProperties[i]);
                    }
                    catch (com.sun.star.lang.WrappedTargetException e) {
                        System.out.println("WrappedTargetException has been thrown accessing "+predefinedPathProperties[i]);
                    }
                }
    
                // Try to modfiy the work path property. After running this example
                // you should see the new value of "My Documents" in the path options
                // tab page, accessible via "Tools - Options - OpenOffice.org - Paths".
                // If you want to revert the changes, you can also do it with the path tab page.
                try {
                    xPathSettingsService.setPropertyValue("Work", "$(temp)");
                    String aValue = (String)xPathSettingsService.getPropertyValue("Work");
                    System.out.println("The work path should now be " + aValue);
                }
                catch (com.sun.star.beans.UnknownPropertyException e) {
                    System.out.println("UnknownPropertyException has been thrown accessing PathSettings service");
                }
                catch (com.sun.star.lang.WrappedTargetException e) {
                    System.out.println("WrappedTargetException has been thrown accessing PathSettings service");
                }
                catch (com.sun.star.beans.PropertyVetoException e) {
                    System.out.println("PropertyVetoException has been thrown accessing PathSettings service");
                }
                catch (com.sun.star.lang.IllegalArgumentException e) {
                    System.out.println("IllegalArgumentException has been thrown accessing PathSettings service");
                }
            }
        }
    }

    Path Variables

    Path variables are used as placeholders for system-dependent paths or parts of paths which are only known during the runtime of LibreOffice. The path substitution service com.sun.star.util.PathSubstitution - which manages all path variables of LibreOffice - checks the runtime environment during startup and sets the values of the path variables. The path substitution service supports a number of predefined path variables. They provide information about important paths that LibreOffice currently uses. They are implemented as read-only values and cannot be changed.

    LibreOffice is a multi-platform solution that runs on different file systems. Obviously users want to have a single user configuration on all workstations across all platforms in a networked installation. For example, a user wants to use both the Windows and Unix version of LibreOffice. The home directory and the working directory are located on a central file server that uses Samba to provide access for Windows systems. The user only wants to have one user installation for both systems, so that individual settings only need to be specified once.

    The path settings service described in Path Settings utilizes the path substitution service. In the configuration of LibreOffice, path variables describe the path settings, and these variables can be substituted by platform-specific paths during startup. That way, path substitution gives users the power to apply path settings only once, while the system takes care of the necessary platform-dependent and environment adaptations.

    The illustration below shows how a path variable can resolve the path problem that arises when you use the same user directory on different platforms.

    Path variables solve problems in heterogeneous environments

    The following sections describe predefined variables, how to define your own variables, and how to resolve path variables with respect to paths in your programs.

    Predefined Variables

    The path substitution service supports a number of predefined path variables. They provide information about the paths that LibreOffice currently uses. They are implemented as read-only values and cannot be modified.

    The predefined path variables can be separated into three distinct groups. The first group of variables specifies a single path, the second group specifies a list of paths that are separated by the shell or operating system dependent character, and the third group specifies only a part of a path.

    All predefined variable names are case insensitive, as opposed to the user-defined variables that are described below.

    Predefined variables supported by service com.sun.star.util.PathSubstitution
    $(home) Single path The absolute path to the home directory of the current user. Under Windows this depends on the specific versions: usually the <drive>:\Documents and Settings\<username>\Application Data under Windows 2000/XP and <drive>:\Windows\Profiles\<username>\Application Data under Windows NT and Win9x, ME with multi user support. Windows 9x and ME without multi-user support <drive>:\Windows\Application Data.
    $(inst)

    $(instpath)
    $(insturl)

    Single path The absolute installation path of LibreOffice. Normally the share and program folders are located inside the installation folder. The $(instpath) and $(insturl) variables are aliases to $(inst) - they are included for downward compatibility and should not be used.
    $(prog)

    $(progpath)
    $(progurl)

    Single path The absolute path of the program folder of LibreOffice. Normally the executable and the shared libraries are located in this folder. The $(progpath) and $(progurl) variables are aliases to $(prog) - they are supported for downward compatibility and should not be used.
    $(temp) Single path The absolute path of the current temporary directory used by LibreOffice.
    $(user)

    $(userpath)
    $(userurl)

    Single path The absolute path to the user installation folder of LibreOffice. The $(userpath) and $(userurl) variables are aliases to $(user) - they are supported for downward compatibility and should not be used.
    $(work) Single path The absolute path of the working directory of the user. Under Windows this is the My Documents folder. Under Unix this is the home directory of the user.
    $(path) List of paths The value of the PATH environment variable of the LibreOffice process. The single paths are separated by a ';' character independent of the system.
    $(lang) Part of a path The country code used by LibreOffice, see the table Mapping ISO 639/3166 to $(lang) below for examples.
    $(langid) Part of a path The language identifier used by LibreOffice. An identifier is composed of a primary language identifier and a sublanguage identifier such as 0x0009=English (primary language identifier), 0x0409=English US (composed language code). The language identifier is based on the Microsoft language identifiers, for further information please see:

    Language Identifier Constants and Strings
    https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693%28v=vs.85%29.aspx

    The language identifier returned by $(langid) is a string expressed in decimal notation. Example for English US : 1033 whereas Microsoft documentation uses hexadecimal notation : 0x0409

    $(vlang) Part of a path The language used by LibreOffice as an English string, for example, "german" for a German version of LibreOffice.

    The values of $(lang), $(langid) and $(vlang) are based on the property ooLocale in the configuration branch org.openoffice.Setup/L10N, that is normally located in the share directory. This property follows the ISO 639-1/ISO3166 standards that define identification codes for languages and countries. The ooLocale property is written by the setup application during installation time. The following are examples of table Mapping ISO 639/3166 to $(vlang):

    Mapping from ISO639-1/ISO3166 to $(lang) and $(vlang)
    ISO 639-1 ISO 3166 $(lang) $(vlang)
    ar * 96 arabic
    ca AD 37 catalan
    ca ES 37 catalan
    cs * 42 czech
    cz * 42 czech
    da DK 45 danish
    de * 49 german
    el * 30 greek
    en * 1 english
    en GB 1 english_uk
    es * 34 spanish
    fi FI 35 finnish
    fr * 33 french
    he * 97 hebrew
    hu HU 36 hungarian
    it * 39 italian
    ja JP 81 japanese
    ko * 82 korean
    nb NO 47 norwegian
    nl * 31 dutch
    nn NO 47 norwegian
    no NO 47 norwegian
    pl PL 48 polish
    pt BR 55 portuguese_brazilian
    pt PT 3 portuguese
    ru RU 7 russian
    sk SK 43 slovak
    sv * 46 swedish
    th TH 66 thai
    tr TR 90 turkish
    zh CN 86 chinese_simplified
    zh TW 88 chinese_traditional
    Custom Path Variables
    Syntax

    The path substitution service supports the definition and usage of user-defined path variables. The variable names must use this syntax:

     variable ::= "$(" letter { letter | digit } ")"
     letter ::= "A"-"Z"|"a"-"z"
     digit ::= "0"-"9"
    

    The user-defined variables must be defined in the configuration branch org.openoffice.Office.Substitution. LibreOffice employs a rule-based system to evaluate which definition of a user-defined variable is chosen. The following sections describe the different parts of this rule-based system and the configuration settings that are required for defining new path variables.

    Environment Values

    To bind a specific value to a user-defined path variable, the path substitution service uses environment values. The path substitution service chooses a variable definition based on the values of these environment parameters. The following table describes which parameters can be used:

    Environment parameters
    Host This value can be a host name or an IP address , depending on the network configuration (DNS server available). A host name is case-insensitive and can also use the asterisk (*) wildcard to represent match zero or more characters.
    YPDomain The yellow pages domain or NIS domain. The value is case-insensitive and can use the asterisk (*) wildcard to represent match zero or more characters.
    DNSDomain The domain name service. The value is case-insensitive and can use the asterisk (*) wildcard to represent match zero or more characters.
    NTDomain Windows NT domain. The value is case-insensitive and can use the asterisk (*) wildcard to represent match zero or more characters.
    OS The operating system parameter supports the following values:
    • WINDOWS (all windows versions including Win9x, WinME, and WinXP)
    • UNIX (includes LINUX and SOLARIS)
    • SOLARIS
    • LINUX
    Rules

    The user can define the mapping of environment parameter values to variable values. Each definition is called a rule and all rules for a particular variable are the rule set. You can only have one environment parameter value for each rule.

    The following example rules specify that the user-defined variable called devdoc is bound to the directory s:\develop\documentation if LibreOffice is running under Windows. The second rule binds devdoc to /net/develop/documentation if LibreOffice is running under Solaris.

     Variable name=devdoc
     Environment parameter=OS
     Value=file:///s:/develop/documentation
    
     Variable name=devdoc
     Environment parameter=SOLARIS
     Value=file:///net/develop/documentation
    
    Analyzing User-Defined Rules

    LibreOffice uses matching rules to find the active rule inside a provided rule set.

    1. Tries to match with the Host environment parameter. If more than one rule matches - this can be possible if you use the asterisk (*) wildcard character - the first matching rule is applied.
    2. Tries to match with the different Domain parameters. There is no predefined order for the domain parameters - the first matching rule is applied.
    3. Try to match with the OS parameter. The specialized values have a higher priority than generic ones, for example, LINUX has a higher priority than UNIX.

    The illustration below shows the analyzing and matching of user-defined rules.

    Process of the rule set analyzing

    The analyzing and matching process is done whenever a rule set has changed. Afterwards the values of the user-defined path variables are set and can be retrieved using the interface com.sun.star.util.XStringSubstitution.

    Configuration

    The path substitution service uses the org.openoffice.Office.Substitution configuration branch for the rule set definitions, which adhere to this schema:

    <?xml version='1.0' encoding='UTF-8'?>
    <oor:component-schema oor:name="Substitution" oor:package="org.openoffice.Office" xml:lang="en-US" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <templates>
            <group oor:name="SharePointMapping">
                <prop oor:name="Directory" oor:type="xs:string" oor:nillable="false"/>
                <group oor:name="Environment">
                    <prop oor:name="OS" oor:type="xs:string"/>
                    <prop oor:name="Host" oor:type="xs:string"/>
                    <prop oor:name="DNSDomain" oor:type="xs:string"/>
                    <prop oor:name="YPDomain" oor:type="xs:string"/>
                    <prop oor:name="NTDomain" oor:type="xs:string"/>
                </group>
            </group>
            <set oor:name="SharePoint" oor:node-type="SharePointMapping"/>
        </templates>
        <component>
            <set oor:name="SharePoints" oor:node-type="SharePoint"/>
        </component>
    </oor:component-schema>

    The SharePoints set is the root container that store the definition of the different user-defined path variables. The SharePoint set uses nodes of type SharePoint which defines a single user-defined path variable.

    Properties of the SharePoint set nodes
    oor:component-data String. The name of the user-defined path variable. It must be unique inside the SharePoints set.

    The name must meet the requirements for path variable names, see Path Variables. The preceding characters "$(" and the succeeding ")" must be omitted, for example, the node string for the path variable $(devdoc) must be devdoc.

    A SharePoint set is a container for the different rules, called SharePointMapping in the configuration.

    Properties of the SharePointMapping group
    oor:component-data String - must be unique inside the SharePoint set, but with no additional meaning for user-defined path variables. Use a consecutive numbering scheme - even numbers are permitted.
    Directory String - must be set and contain a valid and encoded file URL that represents the value of the user-defined path variable for the rule.
    Environment Group - contains a set of properties that define the environment parameter that this rule must match. You can only use one environment in a rule.
    OS The operating system. The following values are supported:
    • WINDOWS = Matches all Windows OS from Win 98 and higher.
    • LINUX = Matches all supported Linux systems.
    • SOLARIS = Matches all supported Solaris systems.
    • UNIX = Matches all supported Unix systems (Linux,Solaris)
    Host The host name or IP address. The name or address can include the asterisk (*) wildcard to match with zero or more characters. For example, dev*.local.de refers to all systems where the host name starts with "dev" and ends with ".local.de"
    DNSDomain The domain name service. The value is case-insensitive and can use the asterisk (*) wildcard for zero or more characters.
    YPDomain The yellow pages domain or NIS domain. The value is case-insensitive and can use the asterisk (*) wildcard for zero or more characters.
    NTDomain Windows NT domain. The value is case-insensitive and can use the asterisk (*) wildcard for zero or more characters.

    The following example uses two rules to map a Windows and Unix specific path to the user-defined path variable MyDocuments.

    <?xml version="1.0" encoding="utf-8"?>
    <oor:component-data oor:name="Substitution" oor:context="org.openoffice.Office"
    xsi:schemaLocation="http://openoffice.org/2001/registry component-update.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:oor="http://openoffice.org/2001/registry"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <node oor:name="SharePoints">
            <node oor:name="MyDocuments" oor:op="replace">
                <node oor:name="1" oor:op="replace">
                    <prop oor:name="Directory"><value>file:///H:/documents</value></prop>
                    <node oor:name="Environment">
                        <prop oor:name="OS"><value>Windows</value></prop>
                    </node>
                </node>
                <node oor:name="2" oor:op="replace">
                    <prop oor:name="Directory"><value>file:///net/home/user/documents</value></prop>
                    <node oor:name="Environment">
                        <prop oor:name="OS"><value>UNIX</value></prop>
                    </node>
                </node>
            </node>
        </node>
    </oor:component-data>

    Resolving Path Variables

    This section explains how to use the LibreOffice implementation of the path substitution service. The following code snippet creates a path substitution service.

    import com.sun.star.lang.XMultiServiceFactory;
    import com.sun.star.uno.Exception;
    import com.sun.star.uno.XInterface;
    import com.sun.star.util.XStringSubstitution
    
    XStringSubstitution createPathSubstitution() {
    
        /////////////////////////////////////////////////////////////////////
        // Obtain Process Service Manager.
        /////////////////////////////////////////////////////////////////////
    
        XMultiServiceFactory xServiceFactory = ...
    
        /////////////////////////////////////////////////////////////////////
        // Create Path Substitution. This needs to be done only once per process.
        /////////////////////////////////////////////////////////////////////
    
        XInterface xPathSubst;
        try {
            xPathSubst = xServiceFactory.createInstance(
                "com.sun.star.util.PathSubstitution" );
        }
        catch (com.sun.star.uno.Exception e) {
        }
    
        if (xPathSubst != null)
            return UnoRuntime.queryInterface( XStringSubstitution.class, xPathSubst );
        else
            return null;
    }

    The service is implemented as a one-instance service and supports the interface com.sun.star.util.XStringSubstitution. The interface has three methods:

    string substituteVariables( [in] string aText, [in] boolean bSubstRequired )
    string reSubstituteVariables( [in] string aText )
    string getSubstituteVariableValue( [in] string variable )

    The method substituteVariables() returns a string where all known variables are replaced by their value. Unknown variables are not replaced. The argument bSubstRequired can be used to indicate that the client needs a full substitution - otherwise the function fails and throws a com.sun.star.container.NoSuchElementException. For example: $(inst)/share/autotext/$(vlang) could be substituted to file:///c:/OpenOffice.org1.0.2/share/autotext/english.

    The method reSubstituteVariables() returns a string where parts of the provided path aText are replaced by variables that represent this part of the path. If a matching variable is not found, the path is not modified.

    The predefined variable $(path) is not used for substitution. Instead, it is a placeholder for the path environment variable does not have a static value during runtime. The path variables $(lang), $(langid) and $(vlang), which represent a directory or a filename in a path, only match inside or at the end of a provided path. For example: english is not replaced by $(vlang), whereas file:///c:/english is replaced by file:///c:/$(vlang).

    The method getSubstituteVariableValue() returns the current value of the provided path variable as a predefined or a user-defined value. If an unknown variable name is provided, a com.sun.star.container.NoSuchElementException is thrown. The argument variable can be provided with preceding "$(" and succeeding ")" or without them. So both $(work) and work can be used.

    This code example shows how to access, substitute, and resubstitute path variables by means of the LibreOffice API.

    import com.sun.star.bridge.XUnoUrlResolver;
    import com.sun.star.uno.UnoRuntime;
    import com.sun.star.uno.XComponentContext;
    import com.sun.star.lang.XMultiComponentFactory;
    import com.sun.star.beans.XPropertySet;
    import com.sun.star.beans.PropertyValue;
    
    import com.sun.star.util.XStringSubstitution;
    import com.sun.star.frame.TerminationVetoException;
    import com.sun.star.frame.XTerminateListener;
    
    /*
      * Provides example code how to access and use the
      * path substitution sercvice.
      */
    public class PathSubstitutionTest {
    
        /*
          * List of predefined path variables supported by
          * the path substitution service.
          */
        private static String[] predefinedPathVariables = {
            "$(home)","$(inst)","$(prog)","$(temp)","$(user)", "$(username)",
            "$(work)","$(path)","$(langid)","$(vlang)"
        };
    
        /*
          * @param args the command line arguments
          */
        public static void main(String[] args) {
    
            XComponentContext xRemoteContext = null;
            XMultiComponentFactory xRemoteServiceManager = null;
            XStringSubstitution xPathSubstService = null;
    
            try {
                // connect
                XComponentContext xLocalContext =
                    com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
                XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager();
                Object urlResolver = xLocalServiceManager.createInstanceWithContext(
                    "com.sun.star.bridge.UnoUrlResolver", xLocalContext );
                XUnoUrlResolver xUnoUrlResolver = UnoRuntime.queryInterface(
                    XUnoUrlResolver.class, urlResolver );
                Object initialObject = xUnoUrlResolver.resolve(
                    "uno:socket,host=localhost,port=2083;urp;StarOffice.ServiceManager" );
                XPropertySet xPropertySet = UnoRuntime.queryInterface(
                    XPropertySet.class, initialObject);
                Object context = xPropertySet.getPropertyValue("DefaultContext");
                xRemoteContext = UnoRuntime.queryInterface(
                    XComponentContext.class, context);
                xRemoteServiceManager = xRemoteContext.getServiceManager();
    
                Object pathSubst = xRemoteServiceManager.createInstanceWithContext(
                    "com.sun.star.comp.framework.PathSubstitution", xRemoteContext );
                xPathSubstService = UnoRuntime.queryInterface(
                    XStringSubstitution.class, pathSubst);
    
                /* Work with path variables */
                workWithPathVariables( xPathSubstService );
            }
            catch (java.lang.Exception e) {
                e.printStackTrace();
            }
            finally {
                System.exit(0);
            }
        }
    
        /*
          * Retrieve, resubstitute path variables
          * @param xPathSubstService the path substitution service
          */
        public static void workWithPathVariables( XStringSubstitution xPathSubstService )
        {
            if ( xPathSubstService != null ) {
                for ( int i=0; i<predefinedPathVariables.length; i++ ) {
                    try {
                            /* Retrieve values for predefined path variables */
                            String aPath = xPathSubstService.getSubstituteVariableValue(
                                                predefinedPathVariables[i] );
                            System.out.println( "Variable="+ predefinedPathVariables[i] +
                                                " Path=" + aPath );
    
                            /* Check resubstitute */
                            String aValue = xPathSubstService.reSubstituteVariables( aPath );
                            System.out.println( "Path=" + aPath +
                                                " Variable=" + aValue );
                    }
                    catch ( com.sun.star.container.NoSuchElementException e) {
                        System.out.println( "NoSuchElementException has been thrown accessing"
                                            + predefinedPathVariables[i]);
                    }
                }
            }
        }
    }

    LibreOffice Single Sign-On API

    Overview

    Users of a client application that can communicate with a variety of services on a network may need to enter several passwords during a single session to access different services. This situation can be further exacerbated if the client application also requires the user to enter a password each time a particular network service is accessed during a session.

    As most network users must authenticate to an OS at login time, it would make sense to access some required network services at this time as well. A solution to this problem is provided by the Single Sign-On (SSO) methodology, which is the ability to login in once and access several protected network services.

    The best known SSO is the Kerberos network authentication protocol (see rfc1510). Kerberos functionality is commonly accessed through the Generic Security Service Application Program Interface (GSS-API, see rfc2743). Central to GSS-API is the concept of a security context, which is the "state of trust" that is initiated when a client (also known as source or initiator) identifies itself to a network service (also known as target or acceptor). If mutual authentication is supported, then the service can also authenticate itself to the client. To establish a security context, security tokens are exchanged, processed, and verified between the client and the service. The client always initiates this exchange. Once established, a security context can be used to encrypt or decrypt subsequent client-service communications.

    The LibreOffice SSO API is based on GSS-API. The SSO API supports the creation of security contexts on the client and the service side as well as the generation of the security tokens that are required for the exchange to complete the security context based authentication. The SSO API does not support the actual exchange of security tokens or the encryption or decryption of client-service communications in an established security context.

    LibreOffice implements SSO in two different ways to authenticate with an LDAP server for configuration purposes. The first is Kerberos based and the second is a simple non-standard "cached username/password" SSO. The latter is provided as a fallback to support scenarios where no Kerberos server is available.

    Implementing the LibreOffice SSO API

    Implementing the LibreOffice SSO API involves creating security context instances (see XSSOInitiatorContext and XSSOAcceptorContext below) and using these instances to create and process security tokens. All the LibreOffice SSO interfaces are available from the ::com::sun::star::auth namespace. The major interfaces are shown in Illustration 7.22 and described below.

    XSSOManagerFactory

    Represents the starting point for interaction with the SSO API. This interface is responsible for providing XSSOManager (described below) instances based on the user's configured security mechanism e.g. "KERBEROS".

    XSSOManager

    This interface is responsible for the creation of not established security contexts for clients (XSSOInitiatorContext) and services (XSSOAcceptorContext). An XSSOManager instance "supports" a single security mechanism, that is, the context instances that are created by an XSSOManager instance only interact with a single security mechanism implementation.

    XSSOInitiatorContext

    This interface represents a client-side security context that is not established when it is created. A single method, init(), is provided so that you can create an initial client-side security token that can be delivered to the relevant service and for processing or validating returned service-side security tokens (if mutual authentication is supported). The expected sequence of events for this client-side security context is:

    • The client calls init(), passes NULL as the parameter, receives an appropriate client-side security token in return.
    • The client sends the security token to the relevant service.
    • If the service successfully processes this token, the client is authenticated.
    • If mutual authentication is not supported, the client-side authentication sequence is now complete.
    • If mutual authentication is supported, the service sends a service-side security token to the client.
    • The client calls init() a second time and passes the returned service-side security token as a parameter. If the token is successfully passed, the service is authenticated.
    XSSOAcceptorContext

    This interface represents a service-side security context that is not established when it is created. A single method, accept(), is provided and is responsible for processing an initial client-side security token. If mutual authentication is supported, the method also generates a service-side security token for the client. The expected sequence of events for this service-side security context is:

    • The service receives the client-side security token.
    • The service calls accept(), passes the client-side security token as a parameter, and if successful, the client is authenticated.
    • If mutual authentication is not supported, the service-side authentication sequence is now complete.
    • If mutual authentication is supported, accept() returns a non-zero length service-side security token.
    • The service sends the service-side security token to the client to authenticate the service.
    Major Interfaces of the LibreOffice SSO

    The following example is a sample usage of the LibreOffice SSO API that provides the authenticate() method of the fictitious client--side MySSO class. For simplicity, assume that MySSO has the following members:

    • mSourceName identifies a client-side user that needs to authenticate to a network service.
    • mTargetName identifies the service to which the user needs to authenticate.
    • mTargetHost identifies the network host where the service of interest is running.

    namespace auth    = ::com::sun::star::auth;
    namespace lang    = ::com::sun::star::lang;
    namespace uno     = ::com::sun::star::uno;
    
    void MySSO::authenticate(void) {
        static const rtl::OUString kSSOService("com.sun.star.auth.SSOManagerFactory");
        uno::Reference< lang::XMultiServiceFactory > theServiceFactory =
            ::comphelper::getProcessServiceFactory();
    
        // Create an SSO Manager Factory.
        uno::Reference< auth::XSSOManagerFactory > theSSOFactory(
            theServiceFactory->createInstance(kSSOService), uno::UNO_QUERY);
        if (!theSSOFactory.is()) {
            throw;
        }
    
        // Ask the SSO Manager Factory for an SSO Manager.
        uno::Reference<auth::XSSOManager> theSSOManager =
            theSSOFactory->getSSOManager();
        if (!theSSOManager.is()) {
            throw;
        }
    
        // Ask the SSO Manager to create an unestablished client/initiator side
        // security context based on user name, service name and service host.
        uno::Reference<auth::XSSOInitiatorContext> theInitiatorContext =
            theSSOManager->createInitiatorContext(mSourceName, mTargetName, mTargetHost);
    
        // Now create the client side security token to send to the service.
        uno::Sequence<sal_Int8> theClientToken = theInitiatorContext->init(NULL);
    
        // The client should now send 'theClientToken' to the service.
        // If mutual authentication is supported, the service will return a service
        // side security token.
        uno::Sequence<sal_Int8> theServerToken = ''sendToken(theClientToken);''
        if (theInitiatorContext->getMutual()) {
            theInitiatorContext->init(theServerToken);
        }
    }

    The SSO Password Cache

    When you implement the SSO API, you may require access to user passwords, especially if you are relying on a preexisting underlying security mechanism. If you do not know how to gain such access, you can use the LibreOffice SSO password cache. This cache provides basic support for maintaining a list of username or password entries. Individual entries have a default lifetime corresponding to a single user session, but can optionally exist for multiple sessions. Support is provided for adding, retrieving, and deleting cache entries. Only one entry per username can exist in the cache at any time. If you add an entry for an existing username, the new entry replaces the original entry. The SSO password cache is represented by a single interface, namely the XSSOPasswordCache interface, available in the ::com::sun::star::auth namespace.

    RDF metadata

    ODF 1.2 introduces a new metadata mechanism based on RDF. RDF expands to Resource Description Framework, and is an W3C standard. Please refer to the W3C for information about RDF: https://www.w3.org/RDF/

    Especially the first 2 sections of the RDF Primer are required reading for understanding the (really quite simple) basic concepts of the RDF data model: RDF Primer

    If you like reading an in-depth specification, please refer to: RDF Concepts and Abstract Syntax

    If you are interested in some motivational use cases, and an overview of the basic design from the ODF perspective, have a look at the ODF Metadata examples document.

    The LibreOffice implementation of the RDF data model lives in the com.sun.star.rdf module.

    Nodes

    First, there are the basic RDF node types:

    RDF node type Interface Service
    node com.sun.star.rdf.XNode
    literal com.sun.star.rdf.XLiteral com.sun.star.rdf.Literal
    resource com.sun.star.rdf.XResource
    URI com.sun.star.rdf.XURI com.sun.star.rdf.URI
    blank node com.sun.star.rdf.XBlankNode com.sun.star.rdf.BlankNode

    interface XNode
    {
        [readonly, attribute] string StringValue;
    };

    The interface XResource is only necessary to separate the literal, which is not a resource, from the blank node and URI.

    Literals

    Literals are represented by the service com.sun.star.rdf.Literal.

    interface XLiteral : XNode
    {
        [readonly, attribute] string Value;
        [readonly, attribute] string Language;
        [readonly, attribute] XURI   Datatype;
    };
    
    service Literal : XLiteral
    {
        create( [in] string Value );
    
        createWithType( [in] string Value, [in] XURI Type );
    
        createWithLanguage( [in] string Value, [in] string Language );
    };

    This service has three constructors, one for every distinct kind of literal in RDF.

    The simplest kind of literal is a plain value.

    A literal may also have a data type, which is represented by an URI. Such literals are called typed literals. The W3C XMLSchema Part 2 specification contains several widely used data types such as numbers, booleans, dates, and many more.

    It is also possible to create a literal with a specific language. This makes it possible to have a multi-lingual RDF graph, where statements are repeated with the same subject and predicate, but different objects that contain the same text content in different languages.

    URIs

    In RDF, URIs are used to denote resources. URIs may be used as subjects or objects of a statement. In contrast with other node types, URIs may also be used as the predicate of a statement.

    service URI : XURI
    {
        create( [in] string Value )
            raises( lang::IllegalArgumentException );
    
        createNS( [in] string Namespace, [in] string LocalName )
            raises( lang::IllegalArgumentException );
    
        createKnown( [in] short Id )
            raises( lang::IllegalArgumentException );
    };

    The URI service has constructors create and createNS, which allow for creating an URI from a string. These two constructors are very similar, but createNS allows splitting the parameter into two parts, which may be useful when creating several URIs that share a prefix because they belong to the same vocabulary.

    There are many URIs that are well-known because they are specified in various standards, such as XMLSchema datatypes, RDF concepts, OWL, and ODF.

    There is a convenient way to construct such URIs: using the createKnown constructor, together with constants from the com.sun.star.rdf.URIs constant group.

    rdf.XURI xContentFile = rdf.URI.createKnown(rdf.URIs.ODF_CONTENTFILE);

    Of course, string literals would be easier to use, but unfortunately UNO IDL does not permit them.

    Blank nodes

    The other kind of RDF node is the blank node, which is a resource, but in contrast to an URI, is not unique. Because blank nodes are not unique, you should only construct them with the createBlankNode method, not with the service constructor, and you should never use a blank node with a different repository than the one that created it.

    Statements

    Using these nodes, RDF statements can be constructed, which are basically subject-predicate-object triples.

    struct Statement
    {
        XResource Subject;
        XURI      Predicate;
        XNode     Object;
        /// the named graph that contains this statement, or <NULL/>.
        XURI      Graph;
    };

    The subject of the statement is the entity that the statement is about. The subject must be either an URI or a blank node; a literal is not allowed.

    The predicate denotes what the relationship between the subject and the object is. In order to ensure that statements have a machine-readable semantics, only URIs are allowed as predicates.

    The object of the statement may be any kind of RDF node.

    If you put many statements together, and these statements share subjects and objects, then you will get a RDF graph.

    Graphs

    Graphs are represented by the interface com.sun.star.rdf.XNamedGraph. As the name implies, a named graph has a name, which is a URI. This is why the XNamedGraph interface inherits from XURI.

    Note pin.svg

    Note:
    This means that you can easily create RDF statements about named graphs.

    interface XNamedGraph : XURI
    {
        XURI getName();
    
        void clear()
            raises( container::NoSuchElementException, RepositoryException );
    
        void addStatement(
                [in] XResource Subject, [in] XURI Predicate, [in] XNode Object)
            raises( lang::IllegalArgumentException,
                    container::NoSuchElementException, RepositoryException );
    
        void removeStatements(
                [in] XResource Subject, [in] XURI Predicate, [in] XNode Object)
            raises( container::NoSuchElementException, RepositoryException );
    
        container::XEnumeration/*<Statement>*/ getStatements(
                [in] XResource Subject, [in] XURI Predicate, [in] XNode Object)
            raises( container::NoSuchElementException, RepositoryException );
    };

    The individual methods will be discussed in subsequent sections. There is no service for a named graph, because named graphs always live in a repository.

    Repository

    The repository is the centerpiece of the RDF API. It is defined in the interface com.sun.star.rdf.XRepository, and the service com.sun.star.rdf.Repository.

    interface XRepository
    {
        XBlankNode createBlankNode();
    
        XNamedGraph importGraph([in] /*FileFormat*/ short Format,
                    [in] io::XInputStream InStream,
                    [in] XURI GraphName, [in] XURI BaseURI)
            raises( lang::IllegalArgumentException,
                    datatransfer::UnsupportedFlavorException,
                    container::ElementExistException, ParseException,
                    RepositoryException, io::IOException );
    
        void exportGraph([in] /*FileFormat*/ short Format,
                    [in] io::XOutputStream OutStream,
                    [in] XURI GraphName, [in] XURI BaseURI)
            raises( lang::IllegalArgumentException,
                    datatransfer::UnsupportedFlavorException,
                    container::NoSuchElementException, RepositoryException,
                    io::IOException );
    
        sequence<XURI> getGraphNames()
            raises( RepositoryException );
    
        XNamedGraph getGraph([in] XURI GraphName)
            raises( lang::IllegalArgumentException,
                    RepositoryException );
    
        XNamedGraph createGraph([in] XURI GraphName)
            raises( lang::IllegalArgumentException,
                    container::ElementExistException, RepositoryException );
    
        void destroyGraph([in] XURI GraphName)
            raises( lang::IllegalArgumentException,
                    container::NoSuchElementException, RepositoryException );
    
        container::XEnumeration/*<Statement>*/ getStatements(
                [in] XResource Subject, [in] XURI Predicate, [in] XNode Object)
            raises( RepositoryException );
    
        /// executes a SPARQL "SELECT" query.
        XQuerySelectResult querySelect([in] string Query)
            raises( QueryException, RepositoryException );
    
        /// executes a SPARQL "CONSTRUCT" query.
        container::XEnumeration/*<Statement>*/ queryConstruct([in] string Query)
            raises( QueryException, RepositoryException );
    
        /// executes a SPARQL "ASK" query.
        boolean queryAsk([in] string Query)
            raises( QueryException, RepositoryException );
    };

    A RDF repository is basically a set of named RDF graphs. The names of the contained graphs can be retrieved via the getGraphNames method. An individual graph can be retrieved via the getGraph method.

    A repository may be created as a stand-alone service, or it may be associated with a loaded ODF document. The graphs in a document repository correspond to streams in an ODF package, and thus the graph names consist of the URI of the ODF package and the relative path of the stream within the package.

    Warning.svg

    Warning:
    For a document repository, you should not call the createGraph, destroyGraph or importGraph methods directly; instead, call the respective methods of com.sun.star.rdf.XDocumentMetadataAccess, as described below.


    Services that provide an RDF repository implement the interface com.sun.star.rdf.XRepositorySupplier.

    interface XRepositorySupplier
    {
        XRepository getRDFRepository();
    };

    Document integration

    The other main part of the RDF API is the integration in document models. This purpose is served by the interface com.sun.star.rdf.XDocumentMetadataAccess, which is implemented by the Model service of documents.

    Note pin.svg

    Note:
    Because this interface derives from XURI, you can easily create RDF statements about the document.

    Warning.svg

    Warning:
    Do not use methods such as getURL to create a URI for the document. Always use the XURI interface of the model when you need the RDF URI for a document.


    Furthermore, there is the interface com.sun.star.rdf.XMetadatable, which allows document content entities to be used as subjects or objects in the methods that manipulate RDF graphs.

    interface XMetadatable : XURI
    {
        [attribute] beans::StringPair MetadataReference {
            set raises ( lang::IllegalArgumentException );
        };
    
        void ensureMetadataReference();
    };

    Warning.svg

    Warning:
    The XMetadatable interface has an attribute MetadataReference. This attribute is only meant to be set by import filters, such as the ODF import filter. Extensions should use ensureMetadataReference, or simply use a XMetadatable as a parameter to addStatement, which will automatically call ensureMetadataReference().


    Annotated text range

    The service com.sun.star.text.InContentMetadata allows to add annotations to a range of text. The range of text must be contained within a single paragraph, and annotations must not overlap (but they are allowed to nest).

    Note pin.svg

    Note:
    In essence, this is a span with a xml:id.

    text.XText = xDoc.getText();
    text.XTextCursor xCursor = ... // position to where you want the annotation
    Object xMeta = xDocFactory.createInstance(
            "com.sun.star.text.InContentMetadata");
    text.XTextContent xContent = (text.XTextContent)
        UnoRuntime.queryInterface(text.XTextContent.class, xMeta);
    try {
        xDocText.insertTextContent(xCursor, xMeta, true);
    } catch (lang.IllegalArgumentException) {
        // overlap?
    }

    When the InContentMetadata is successfully inserted, you can add metadata by just using it as the subject or object of an RDF statement.

    rdf.XMetadatable xMetadatable = (rdf.XMetadatable)
        UnoRuntime.queryInterface(rdf.XMetadatable.class, xContent);
    rdf.XURI xComment = rdf.URI.createKnown(rdf.URIs.RDFS_COMMENT);
    rdf.XLiteral xObj = rdf.Literal.create("a most interesting description");
    rdf.XNamedGraph xGraph = xDocRepository.getGraph(xMyGraphURI);
    xGraph.addStatement(xMetadatable, xComment, xObj);

    Metadata field

    There is a new text field that is explicitly designed for being used with RDF metadata: com.sun.star.text.textfield.MetadataField.

    In contrast with the InContentMetadata, where an existing range of text is being annotated with additional metadata, the metadata field allows for text content to be generated from RDF metadata.

    For example, a bibliography extension could use a metadata field to insert a citation. The user could tell the bibliography extension which citation format should be used, and the extension will generate the content of all the citation metadata fields based on this choice. The extension may use some bibliography database that may also be stored as an RDF graph.

    Metadata fields must be contained within a single paragraph, and must not overlap (but they are allowed to nest).

    To enable generating the content, the metadata field implements the com.sun.star.text.XText interface.

    text.XTextContent xMetafield = ... ; // get an inserted metadata field
    text.XText xMetafieldText = (text.XText)
        UnoRuntime.queryInterface(text.XText.class, xMetafield);
    xMetafieldText.setString(""); // clear the field: delete all content
    text.XTextCursor xCursor = xMetafieldText.createCursor();
    xMetafieldText.insertString(xCursor, "field content", true);

    Of course, you can not just insert plain text, but everything that you could insert into a paragraph.

    xCursor.gotoEnd(false);
    text.XTextContent xFootnote = ... ; // create and init footnote
    xMetafieldText.insertTextContent(xCursor, xFootnote, false);

    Metadata fields have another interesting aspect: they can have a prefix and/or suffix text that is taken from one of the RDF graphs in the document. In this way you can create a metadata field with text content that is automatically displayed (non-editable) based on an RDF statement.

    rdf.XMetadatable xMetafield = ... ; // get an inserted metadata field
    rdf.XNamedGraph xGraph = xDocRepository.getGraph(xMyGraphURI);
    rdf.XURI xPrefix = rdf.URI.createKnown(rdf.URIs.ODF_PREFIX);
    rdf.XLiteral xPrefixLiteral = rdf.Literal.create("this text will be displayed as prefix content of the field");
    xGraph.addStatement(xMetafield, xPrefix, xPrefixLiteral);

    Finding in-content metadata at a cursor position

    Assume you have a text cursor at some position, and you want to find out whether there is a annotated text range or a metadata field at the cursor position. For this purpose in OpenOffice.org 3.3 the property NestedTextContent was added to the text cursor in Writer. The following sample will retrieve the innermost nested text content at the current cursor position:

    XPropertySet xPropertySet = (XPropertySet)
        UnoRuntime.queryInterface(XPropertySet.class, xTextCursor);
    XTextContent xNTC = (XTextContent)
        UnoRuntime.queryInterface(XTextContent.class, xPropertySet.getPropertyValue("NestedTextContent"));

    Because annotated text ranges and metadata fields may be nested, it may be the case that there are actually several such text contents at a given cursor position. In order to retrieve the outer nested text contents, use the com.sun.star.container.XChild interface of the InContentMetadata and MetadataField services (also added in OOo 3.3).

    XChild xChild = (XChild)
        UnoRuntime.queryInterface(XChild.class, xNTC);
    XTextContent xOuter = (XTextContent)
        UnoRuntime.queryInterface(XTextContent.class, xChild.getParent());

    Note pin.svg

    Note:
    As of OpenOffice.org 3.3, the NestedTextContent property has only InContentMetadata and MetadataField as values. But this may change in future releases; for example, hyperlinks and ruby annotations could be represented as a service some day. So please do not assume that the currently existing are the only possible values.

    Other document entities

    In addition to the annotated text range and the metadata field, other document content entities implement the XMetadatable interface as well. These entities can thus be used in RDF statements. The list of entities which can be thus annotated will grow in future releases of LibreOffice.

    Adding metadata to a document

    The metadata support in ODF 1.2 allows for adding RDF graphs to an ODF package. Every RDF graph is stored as an RDF/XML stream in the package.

    There is a special RDF graph called the metadata manifest. This RDF graph belongs to a document and enumerates all the files relevant for metadata, such as the individual RDF/XML files.

    This manifest graph is maintained by LibreOffice itself; extension authors should not modify it directly, but use the interface com.sun.star.rdf.XDocumentMetadataAccess.

    In order to isolate different metadata users, every extension that wants to add metadata should create one (or several) own RDF graph(s). It is recommended to give the new graphs a type that identifies what kind of information is contained, especially if a well-known RDF vocabulary is used. With the method addMetadataFile, you can specify as many types as you want for a graph.

    rdf.XDocumentMetadataAccess xDMA = (rdf.XDocumentMetadataAccess)
        UnoRuntime.queryInterface(rdf.XDocumentMetadataAccess.class, xModel);
    rdf.XURI xType = rdf.URI.create("http://example.com/myextension/v1.0");
    try {
        rdf.XURI xGraphName = xDMA.addMetadataFile("myextension/mygraph.rdf",
            new rdf.XURI[] { xType } );
        rdf.XNamedGraph xGraph = xDMA.getRDFRepository().getGraph(xGraphName);
    } catch (container.ElementExistException e) { // filename exists?
    }

    Note pin.svg

    Note:
    Choose a stream name that is unlikely to conflict with other extensions. The preferred suffix is .rdf.

    Tip.svg

    Tip:
    The method importMetadataFile allows to import an existing RDF/XML file into the document RDF repository.


    Now you can simply insert RDF statements into the graph:

    rdf.XMetadatable xElement = ...; // some document content entity
    rdf.XURI xLabel = rdf.URI.createKnown(rdf.URIs.RDFS_LABEL);
    rdf.XLiteral xObj = rdf.Literal.create("a most interesting description");
    xGraph.addStatement(xElement, xLabel, xObj);

    Reading metadata from a document

    In order to read metadata that is stored in a document, query the RDF repository of the document.

    There are basically two ways to do this. One way is to use the getter methods at the named graph and repository interfaces. The method getStatements will return all statements in any graph in the repository that match the given parameters. But note that this is not necessarily what you want. Usually, an extension is only interested in the metadata it has itself inserted. Thus it is better to first get the graph(s) that the extension is interested in, and then only query those graphs via getStatements.

    rdf.XDocumentMetadataAccess xDMA = (rdf.XDocumentMetadataAccess)
        UnoRuntime.queryInterface(rdf.XDocumentMetadataAccess.class, xModel);
    rdf.XURI xType = rdf.URI.create("http://example.com/myextension/v1.0");
    rdf.XURI[] GraphNames = xDMA.getMetadataGraphsWithType(xType);
    for (rdf.XURI xGraphName : GraphNames) {
        rdf.XNamedGraph xGraph = xDMA.getRDFRepository().getGraph(xGraphName);
        // replace nulls with interesting URIs
        container.XEnumeration xResult = xGraph.getStatements(null, null, null);
        while (xResult.hasMoreElements()) {
            rdf.Statement stmt = (rdf.Statement) xResult.nextElement();
        }
    }

    The other way of getting information out of the repository is via the SPARQL query language.

    The same considerations as above apply: by default, the result will contain information from all graphs in the repository. You can restrict the graphs via the GRAPH or FROM clauses.

    rdf.XDocumentMetadataAccess xDMA = (rdf.XDocumentMetadataAccess)
        UnoRuntime.queryInterface(rdf.XDocumentMetadataAccess.class, xModel);
    rdf.XURI xType = rdf.URI.create("http://example.com/myextension/v1.0");
    rdf.XURI[] GraphNames = xDMA.getMetadataGraphsWithType(xType);
    if (GraphNames.length > 0) {
        String graphName = GraphNames[0].getStringValue();
        String query =
            "CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <"
                + graphName + "> { ?s ?p ?o } . }";
        container.XEnumeration xResult =
            xDMA.getRDFRepository().queryConstruct(query);
        while (xResult.hasMoreElements()) {
            rdf.Statement stmt = (rdf.Statement) xResult.nextElement();
        }
    }

    Besides the CONSTRUCT query shown above, there are also the methods querySelect, which returns a table of results, and queryAsk, which simply returns a boolean. There are different methods for the different query types because the return types differ.

    Warning.svg

    Warning:
    Always use the query method that matches the query type.


    Mapping from URIs to document entities

    Note that for a document content annotation the RDF repository will only give you an URI in its results, not the actual document content entity. In order to map the URI to the document entity, use the getElementByURI method.

    rdf.XDocumentMetadataAccess xDMA = (rdf.XDocumentMetadataAccess)
        UnoRuntime.queryInterface(rdf.XDocumentMetadataAccess.class, xModel);
    rdf.XURI xElemURI = ... // query the document repository
    rdf.XMetadatable xElement = xDMA.getElementByURI(xElemURI);
    text.XTextContent xElemContent = (text.XTextContent)
        UnoRuntime.queryInterface(text.XTextContent.class, xElement);

    Tip.svg

    Tip:
    You can check if an URI refers to a document entity: it does if it has as a prefix the base URI of the document, which is available as the XURI implementation of the document model. But please keep in mind that the document entity does not need to exist, because it may have been removed from the document.


    Removing metadata from a document

    The named graph supports the removeStatements method, which removes all statements that match the parameters from the graph.

    Note pin.svg

    Note:
    There is no update operation. If you want to update an RDF statement, you have to remove the existing statement, and insert a new one.

    Also, there is the clear method, which is equivalent to removeStatements(null, null, null), and removes all statements from the graph.

    For removing whole metadata streams from an ODF document, there is the method removeMetadataFile.

    Vocabulary

    Some notes on RDF vocabularies; of course, a comprehensive discussion of RDF design is beyond the scope of this guide.

    Warning.svg

    Warning:
    Do not use URIs based on the document base URI as properties. Properties should be independent of the location of the document.


    An important question when adding RDF metadata is: which vocabulary do you use? First, it is usually a good idea to re-use an existing vocabulary. This will improve the chances that other software, which also supports the existing vocabulary, is able to do something interesting and useful with the metadata that is added.

    For example, for basic datatypes you can use the types specified in W3C XMLSchema Part 2. A well-known vocabulary for expressing social relations is Friend-of-a-Friend. A directory of RDF schemata can be found at SchemaWeb. Another useful resource might be DBPedia.

    If you do not find an existing vocabulary that matches your use case, here are some hints for designing your own: The most important thing is that the URIs should really be unique. If you have a DNS domain, then that is quite easy to achieve: use URIs like http://example.com/myvocabulary/v1.0/foo. It is probably a good idea to use a versioned namespace prefix like http://example.com/myvocabulary/v1.0/ for all URIs. The version component allows you to evolve the vocabulary to meet future requirements. Now you can use the versioned namespace prefix to denote the format, i.e., as a type for your metadata stream, making it easy to find. You can also create an actual HTML page to document the vocabulary at the URI.

    Status

    As of Openoffice.org version 3.2, RDF metadata is only supported in Writer documents. The generic RDF parts of the API are all implemented. The document integration parts are not yet completely implemented: most elements do not support the xml:id that is required for use with RDF. In OOo version 3.3, support for more elements was added. The following elements can be annotated:

    ODF element Service description since
    <text:p> com.sun.star.text.Paragraph paragraph 3.2
    <text:h> com.sun.star.text.Paragraph heading 3.2
    <text:bookmark> com.sun.star.text.Bookmark bookmark 3.2
    <text:bookmark-start> com.sun.star.text.Bookmark bookmark with range 3.2
    <text:meta> com.sun.star.text.InContentMetadata annotated text range 3.2
    <text:meta-field> com.sun.star.text.textfield.MetadataField text field whose content is generated from metadata 3.2
    <text:section> com.sun.star.text.TextSection text section 3.3
    <text:index-title> com.sun.star.text.TextSection index title section 3.3
    <text:alphabetical-index> com.sun.star.text.DocumentIndex alphabetical index 3.3
    <text:user-index> com.sun.star.text.UserDefinedIndex user defined index 3.3
    <text:table-of-content> com.sun.star.text.ContentIndex table of content 3.3
    <text:table-index> com.sun.star.text.TableIndex table index 3.3
    <text:object-index> com.sun.star.text.ObjectIndex object index 3.3
    <text:illustration-index> com.sun.star.text.IllustrationsIndex illustration index 3.3
    <text:bibliography> com.sun.star.text.Bibliography bibliography 3.3

    OS-specific features

    Windows

    Jump Lists
    Result of BASIC example
    Note pin.svg

    Note:
    The Jump List API is available since LibreOffice 7.4.

    Jump Lists is a Windows feature which adds shortcuts to the application menu in the task bar. By default, LibreOffice shows the recently added and often used documents there.

    With the Jump List UNO API you can customize what is shown in the Jump List and add you own shortcuts.

    There are four groups which can be shown in the Jump List:

    • Recent documents
    • Frequent documents
    • Tasks (Shortcuts to application features)
    • Custom categories

    The following BASIC example adds a custom category, some tasks and shows the recent and frequent documents.

    Sub AddJumpList
    oService = createUnoService("com.sun.star.system.windows.JumpList")
    oService.beginList("Writer")
    Dim a(1) As new com.sun.star.system.windows.JumpListItem
    a(0).name = "Run custom service"
    a(0).description = "Add tooltip here"
    a(0).arguments = "service:org.libreoffice.example.YourProject?yourAction"
    a(0).iconPath = "D:\icon.ico"
    oService.appendCategory("My category", a)
    
    a(0).name = "Save document"
    a(0).description = "Save the current document"
    a(0).arguments = ".uno:Save"
    a(0).iconPath = "D:\icon2.ico"
    oService.addTasks(a)
    
    oService.showFrequentFiles(true)
    oService.showRecentFiles(true)
    oService.commitList()
    End Sub

    Heckert GNU white.svg

    Content on this page is licensed under the Public Documentation License (PDL).