LibreOffice Developer's Guide: Chapter 6 - Office Development

From The Document Foundation Wiki
Jump to navigation Jump to search

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 <idl>com.sun.star.frame.Desktop</idl> 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 <idl>com.sun.star.frame.XDesktop</idl> were kept for compatibility reasons.

The desktop object and frame objects use auxiliary services, such as the <idl>com.sun.star.document.TypeDetection</idl> 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 <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.frame.Desktop</idl> 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 <idlmodule>com.sun.star.awt</idlmodule>, 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 <idlm>com.sun.star.frame.XFrame:initialize</idlm>(). This method expects the <idl>com.sun.star.awt.XWindow</idl> interface of a surrounding window instance, which becomes the container window of the frame. The window instance passed to initialize() must also support <idl>com.sun.star.awt.XTopWindow</idl> 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 <idl>com.sun.star.awt.XTopWindow</idl> 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 <idlm>com.sun.star.frame.XFrame:setComponent</idlm>() that takes another <idl>com.sun.star.awt.XWindow</idl> instance and the <idl>com.sun.star.frame.XController</idl> 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 <idl>com.sun.star.awt.XWindow</idl> 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 <idl>com.sun.star.frame.XFramesSupplier</idl> 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 <idl>com.sun.star.frame.XFrames</idl> 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 <idl>com.sun.star.frame.XFrames</idl> 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 <idl>com.sun.star.frame.XFrame</idl> 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 <idl>com.sun.star.frame.XDispatchProvider</idl> 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 <idl>com.sun.star.frame.Controller</idl> service represented by the interface <idl>com.sun.star.frame.XController</idl>. In the call to <idlm>com.sun.star.frame.XFrame:setComponent</idlm>(), the controller is passed with the component window instance. If the component has no controller, it directly implements <idl>com.sun.star.lang.XComponent</idl> and <idl>com.sun.star.awt.XWindow</idl>. 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 <idl>com.sun.star.frame.XDispatchProvider</idl> interface.

Controllers

Controllers in LibreOffice are between a frame and document model. This is their basic role as expressed in <idl>com.sun.star.frame.XController</idl>, 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 <idl>com.sun.star.sheet.XSpreadsheetView</idl> for Calc document controllers or <idl>com.sun.star.text.XTextViewCursorSupplier</idl> 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 (<idl>com.sun.star.awt.XWindow</idl>). They have a platform-specific counterpart that is wrapped in the <idl>com.sun.star.awt.XWindowPeer</idl> 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 <idl>com.sun.star.frame.XDispatchProvider</idl>. 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 <idl>com.sun.star.frame.XDispatchProviderInterception</idl>, 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 <idl>com.sun.star.frame.Desktop</idl> service available at the global service manager includes the service <idl>com.sun.star.frame.Frame</idl>. The Desktop service specification provides three interfaces: <idl>com.sun.star.frame.XDesktop</idl>, <idl>com.sun.star.frame.XComponentLoader</idl> and <idl>com.sun.star.document.XEventBroadcaster</idl>, as shown in the following UML chart:

UML description of the desktop service

The interface <idl>com.sun.star.frame.XDesktop</idl> 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 <idl>com.sun.star.container.XEnumerationAccess</idl> to all loaded documents. For documents loaded in the desktop environment the methods getComponents() and getCurrentComponent() always return the <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.frame.XModel</idl> 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 <idl>com.sun.star.frame.XTerminateListener</idl> implementation that prevents the office from being terminated when the class TerminationTest is still active:

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 class TerminationTest tests the TerminateListener above.

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 extends java.lang.Object {

    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 = (XUnoUrlResolver) UnoRuntime.queryInterface(
                XUnoUrlResolver.class, urlResolver );
            Object initialObject = xUnoUrlResolver.resolve(
                "uno:socket,host=localhost,port=2083;urp;StarOffice.ServiceManager" );
            XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
                XPropertySet.class, initialObject);
            Object context = xPropertySet.getPropertyValue("DefaultContext");
            xRemoteContext = (XComponentContext)UnoRuntime.queryInterface(
                XComponentContext.class, context);
            xRemoteServiceManager = xRemoteContext.getServiceManager();

            // get Desktop instance
            Object desktop = xRemoteServiceManager.createInstanceWithContext (
                "com.sun.star.frame.Desktop", xRemoteContext);
            xDesktop = (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;
    }

}

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

The Desktop offers a facility to load components through its interface <idl>com.sun.star.frame.XComponentLoader</idl>. 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 <idl>com.sun.star.document.XEventBroadcaster</idl>.

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 <idl>com.sun.star.document.EventObject</idl> has a string member EventName that assumes one of the values specified in <idl>com.sun.star.document.Events</idl>. 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 <idl>com.sun.star.frame.Frame</idl> 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 <idl>com.sun.star.frame.XFramesSupplier</idl> interface must be passed to <idlm>com.sun.star.frame.XFrame:setCreator</idlm>() at the child frames, therefore the desktop becomes the parent frame. However, the following functionality of <idl>com.sun.star.frame.Frame</idl> is still in place:

The desktop interface <idl>com.sun.star.frame.XFramesSupplier</idl> offers methods to access frames. This interface inherits from <idl>com.sun.star.frame.XFrame</idl>, 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 <idl>com.sun.star.frame.XFrames</idl> container, that is a <idl>com.sun.star.container.XIndexAccess</idl>, 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, <idl>com.sun.star.frame.XFrame</idl> 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 <idl>com.sun.star.frame.XDispatchProvider</idl> 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 LibreOffice (OpenOffice.org_3.x_Commands). Through the <idl>com.sun.star.frame.XDispatchProviderInterception</idl>, 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 <idl>com.sun.star.lang.XComponent</idl> 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, <idl>com.sun.star.frame.XModel</idl> comes into operation, since it has methods to handle URLs, among others.

So a complete office component is made up of

  • a controller object that presents the model or shows a view to the model that implements the <idl>com.sun.star.frame.Controller</idl> service, but publishes additional document-specific interfaces. For almost all LibreOffice document types there are document specific controller object specifications,such as <idl>com.sun.star.sheet.SpreadsheetView</idl>, and <idl>com.sun.star.drawing.DrawingDocumentDrawView</idl>. For controllers, refer to the section Controllers.
  • a model object implementing the <idl>com.sun.star.document.OfficeDocument</idl> service. Refer to the section Models.

Getting Frames, Controllers and Models from Each Other

Usually developers require the controller and frame of an already loaded document model. The <idl>com.sun.star.frame.XModel</idl> interface of LibreOffice document models gets the controller that provides access to the frame through its <idl>com.sun.star.frame.XController</idl> 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 <idl>com.sun.star.awt.XWindow</idl> only, the window and the window peer is reached by querying these interfaces from the <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.frame.XFrame</idl>:

// 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 (<idl>com.sun.star.frame.XFrames</idl>) when a frame is added to it by a call to <idlm>com.sun.star.frame.XFrames:append</idlm>(). A frames container is provided by frames supporting the interface <idl>com.sun.star.frame.XFramesSupplier</idl>. 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 <idl>com.sun.star.frame.FrameSearchFlag</idl> 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 <idl>com.sun.star.awt.XWindow</idl> interface of the container window.

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

Method of <idl>com.sun.star.frame.XFrameActionListener</idl>
<idlm>com.sun.star.frame.XFrameActionListener:frameAction</idlm>() Takes a struct <idl>com.sun.star.frame.FrameActionEvent</idl>. The struct contains two members: the source <idl>com.sun.star.frame.XFrame</idl> Frame and an enum <idl>com.sun.star.frame.FrameActionEvent</idl> 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 <idl>com.sun.star.frame.XComponentLoader</idl> interface of the <idl>com.sun.star.frame.Desktop</idl> or the interfaces <idl>com.sun.star.frame.XSynchronousFrameLoader</idl>, the preferred frame loader interface, and the asynchronous <idl>com.sun.star.frame.XFrameLoader</idl> of the <idl>com.sun.star.frame.FrameLoader</idl> 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 <idl>com.sun.star.frame.XComponentLoader</idl> 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 <idl>com.sun.star.frame.XFramesSupplier</idl> offers methods to access sub-frames of a frame. The frame implementation of LibreOffice supports this interface. This interface inherits from <idl>com.sun.star.frame.XFrame</idl>, 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 <idl>com.sun.star.frame.XFrames</idl> container, that is a <idl>com.sun.star.container.XIndexAccess</idl> 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 <idlm>com.sun.star.frame.FrameActionEvent:Action</idlm> set to FRAME_UI_ACTIVATED.

XDispatchProvider and XDispatchProviderInterception

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

XStatusIndicatorFactory

The frame implementation supplies a status indicator through its interface <idl>com.sun.star.task.XStatusIndicatorFactory</idl>. 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 <idl>com.sun.star.task.XStatusIndicator</idl>:

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

The status indicator is displayed by a call to <idlm>com.sun.star.task.XStatusIndicator:start</idlm>(). 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 <idl>com.sun.star.frame.XController</idl> inherits from <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.frame.XController</idl> links model and frame through the methods get/attachModel() and get/attachFrame(). These methods and the corresponding methods in the <idl>com.sun.star.frame.XModel</idl> and <idl>com.sun.star.frame.XFrame</idl> 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 <idl>com.sun.star.frame.XDispatchProvider</idl>, the controller participates in the dispatch framework. It is described in section Using the Dispatch Framework.

XSelectionSupplier

The optional Controller interface <idl>com.sun.star.view.XSelectionSupplier</idl> 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 <idl>com.sun.star.frame.XModel</idl> interface.

XContextMenuInterception

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

Document Specific Controller Services

The <idl>com.sun.star.frame.Controller</idl> 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 <idl>com.sun.star.lang.XServiceInfo</idl> 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 <idl>com.sun.star.text.TextDocumentView</idl> 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 <idl>com.sun.star.sheet.SpreadsheetView</idl> The spreadsheet view is extremely powerful. It includes the services <idl>com.sun.star.sheet.SpreadsheetViewPane</idl> and <idl>com.sun.star.sheet.SpreadsheetViewSettings</idl>. 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 <idl>com.sun.star.drawing.DrawingDocumentDrawView</idl> 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 <idl>com.sun.star.drawing.DrawingDocumentDrawView</idl>

<idl>com.sun.star.presentation.PresentationView</idl>

The presentation view does not introduce presentation specific features. Running presentations are controlled by the <idl>com.sun.star.presentation.XPresentationSupplier</idl> interface of the presentation document model.
DataBaseAccess <idl>com.sun.star.sdb.DataSourceBrowser</idl> 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 <idl>com.sun.star.frame.XModel</idl> is currently supported by Writer, Calc, Draw and Impress document components. In our context, we call objects supporting <idl>com.sun.star.frame.XModel</idl>, model objects. All LibreOffice document components have the service <idl>com.sun.star.document.OfficeDocument</idl> in common. An OfficeDocument implements the following interfaces:

XModel

The interface <idl>com.sun.star.frame.XModel</idl> inherits from <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.document.MediaDescriptor</idl>, 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 <idl>com.sun.star.view.XSelectionSupplier</idl> interface supported by controller services.

XModifiable

The interface <idl>com.sun.star.util.XModifiable</idl> 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 <idl>com.sun.star.frame.XStorable</idl> 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 <idl>com.sun.star.view.XPrintable</idl> 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 <idl>com.sun.star.document.XEventBroadcaster</idl> 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 <idl>com.sun.star.document.EventObject</idl> has a string member EventName, that assumes one of the values specified in <idl>com.sun.star.document.Events</idl>. 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 <idl>com.sun.star.document.XEventsSupplier</idl> 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 <idl>com.sun.star.document.XEventBroadcaster</idl> interface. This is done programmatically versus manually in Tools ▸ Configure ▸ Events.

XDocumentPropertiesSupplier

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

Note pin.svg

Note:
In OpenOffice.org versions before 3.0, use the interface <idl>com.sun.star.document.XDocumentInfoSupplier</idl> instead.

XViewDataSupplier

The optional <idl>com.sun.star.document.XViewDataSupplier</idl> 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 <idl>com.sun.star.container.XIndexAccess</idl> to sequences of <idl>com.sun.star.beans.PropertyValue</idl> 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 <idl>com.sun.star.text.TextDocument</idl> provides text related interfaces. Having received a reference to a model, developers query for these interfaces. The <idl>com.sun.star.lang.XServiceInfo</idl> interface of a model can be used to ask for supported services. The LibreOffice document types support the following services:

Document Service Chapter
Calc <idl>com.sun.star.sheet.SpreadsheetDocument</idl> Spreadsheet Documents
Draw <idl>com.sun.star.drawing.DrawingDocument</idl> Drawing Documents and Presentation Documents
Impress <idl>com.sun.star.presentation.PresentationDocument</idl> Drawing Documents and Presentation Documents
Math <idl>com.sun.star.formula.FormulaProperties</idl> -
Writer (all Writer modules) <idl>com.sun.star.text.TextDocument</idl> Text Documents
Chart <idl>com.sun.star.chart.ChartDocument</idl> and <idl>com.sun.star.chart2.ChartDocument</idl> 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 <idl>com.sun.star.awt.XWindow</idl> 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 <idl>com.sun.star.awt.XWindowListener</idl> gets the following notifications. The <idl>com.sun.star.awt.WindowEvent</idl> 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 <idl>com.sun.star.awt.XTopWindow</idl> 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 <idl>com.sun.star.awt.XMenuBar</idl> interface is deprecated.

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

[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 <idl>com.sun.star.awt.XWindowPeer</idl>. The <idl>com.sun.star.awt.XWindowPeer</idl> 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 <idl>com.sun.star.frame.Frame</idl> 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 <idl>com.sun.star.frame.Frame</idl> 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 <idl>com.sun.star.frame.Frame</idl> 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 <idl>com.sun.star.frame.Frame</idl> service at the global service manager. For window creation, the current <idlmodule>com.sun.star.awt</idlmodule> 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 <idl>com.sun.star.awt.Toolkit</idl> service implementation. Not every object implementing the <idl>com.sun.star.awt.XWindow</idl> 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 <idlmodule>com.sun.star.awt</idlmodule> 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 <idl>com.sun.star.awt.XTopWindow</idl>. 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 <idl>com.sun.star.awt.Toolkit</idl> to create windows, supply a <idl>com.sun.star.awt.WindowDescriptor</idl> struct to describe what kind of window is required. Set the Type member of this struct to <idlm>com.sun.star.awt.WindowClass:TOP</idlm> 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 <idlm>com.sun.star.frame.XFramesSupplier:getFrames</idlm>(). Its method <idlm>com.sun.star.frame.XFrames:append</idlm>() 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 =
  (com.sun.star.awt.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 = (com.sun.star.frame.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 =
(com.sun.star.frame.XFramesSupplier)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 <idl>com.sun.star.frame.XComponentLoader</idl>. This interface is implemented by the globally accessible <idl>com.sun.star.frame.Desktop</idl> service and also by the <idl>com.sun.star.frame.Frame</idl> service.

Services Involved in Document Loading

The interface <idl>com.sun.star.frame.XComponentLoader</idl> 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 <idl>com.sun.star.frame.XLoadable</idl> interface from the document
  • call its <idlm>com.sun.star.frame.XLoadable:load</idlm>(…) method that gets the MediaDescriptor as parameter
MediaDescriptor

A call to loadComponentFromURL() receives a sequence of <idl>com.sun.star.beans.PropertyValue</idl> structs as a parameter, which implements the <idl>com.sun.star.document.MediaDescriptor</idl> 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 <idl>com.sun.star.frame.XStorable</idl> 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 <idl>com.sun.star.document.MediaDescriptor</idl>
<idlm>com.sun.star.document.MediaDescriptor:AsTemplate</idlm> 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.

<idlm>com.sun.star.document.MediaDescriptor:Author</idlm> string. Only for storing versions in components supporting versioning: author of version.
<idlm>com.sun.star.document.MediaDescriptor:CharacterSet</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:Comment</idlm> string. Only for storing versions in components supporting versioning: comment (description) for stored version.
<idlm>com.sun.star.document.MediaDescriptor:ComponentData</idlm> any. This is a parameter that is used for any properties specific for a special office component type.
<idlm>com.sun.star.document.MediaDescriptor:FileName</idlm> - deprecated string. Same as URL (added for compatibility reasons)
<idlm>com.sun.star.document.MediaDescriptor:FilterData</idlm> any. This is a parameter that is used for any properties specific for a special filter type.
<idlm>com.sun.star.document.MediaDescriptor:FilterName</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:FilterFlags</idlm> - deprecated string. For compatibility reasons: same as FilterOptions
<idlm>com.sun.star.document.MediaDescriptor:FilterOptions</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:Hidden</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:InputStream</idlm> 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 <idl>com.sun.star.io.XSeekable</idl> interface, do not interfere with the model's reading process. Consider all the objects involved in the loading process as temporary.

<idlm>com.sun.star.document.MediaDescriptor:InteractionHandler</idlm> com.sun.star.task.XInteractionHandler. Object implementing the <idl>com.sun.star.task.InteractionHandler</idl> 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.

<idlm>com.sun.star.document.MediaDescriptor:JumpMark</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:MediaType</idlm> (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.
<idlm>com.sun.star.document.MediaDescriptor:OpenFlags</idlm> - 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.

<idlm>com.sun.star.document.MediaDescriptor:OpenNewView</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:Overwrite</idlm> boolean. For storing only: overwrite existing files with the same name, default is true, so an <idl>com.sun.star.io.IOException</idl> 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.
<idlm>com.sun.star.document.MediaDescriptor:Password</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:PostData</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:PostString</idlm> - deprecated string. Same as PostData, but the data is transferred as a string (just for compatibility).
<idlm>com.sun.star.document.MediaDescriptor:Preview</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:ReadOnly</idlm> 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.

<idlm>com.sun.star.document.MediaDescriptor:Referer</idlm>

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

<idlm>com.sun.star.document.MediaDescriptor:StatusIndicator</idlm> <idl>com.sun.star.task.XStatusIndicator</idl>. Object implementing the <idl>com.sun.star.task.XStatusIndicator</idl> 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.

<idlm>com.sun.star.document.MediaDescriptor:TemplateName</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:TemplateRegionName</idlm> string. See TemplateName. The template region names are the folder names you see in the templates dialog.
<idlm>com.sun.star.document.MediaDescriptor:Unpacked</idlm>
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.

<idlm>com.sun.star.document.MediaDescriptor:URL</idlm> string. The location of the component in URL syntax.
<idlm>com.sun.star.document.MediaDescriptor:Version</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:ViewData</idlm> 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 <idl>com.sun.star.document.XViewDataSupplier</idl> interface. Default is: no ViewData.
<idlm>com.sun.star.document.MediaDescriptor:ViewId</idlm> 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.
<idlm>com.sun.star.document.MediaDescriptor:MacroExecutionMode</idlm> short. How should the macro be executed - the value should be one from <idl>com.sun.star.document.MacroExecMode</idl> constants group
<idlm>com.sun.star.document.MediaDescriptor:UpdateDocMode</idlm> short. Can the document be updated depending on links. The value should be one from <idl>com.sun.star.document.UpdateDocMode</idl> 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 <idl>com.sun.star.document.TypeDetection</idl> service or a <idl>com.sun.star.document.ExtendedTypeDetection</idl> 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 <idl>com.sun.star.io.XSeekable</idl>, 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:

  • create the document by instantiating it with its UNO service name
  • get the <idl>com.sun.star.frame.XLoadable</idl> interface from the document
  • call its initNew() method.
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 <idl>com.sun.star.frame.XFrame</idl> 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 <idl>com.sun.star.frame.XComponentLoader</idl> 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 <idl>com.sun.star.frame.XModel</idl> 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 <idl>com.sun.star.frame.FrameSearchFlag</idl> bit vector:

Constants in <idl>com.sun.star.frame.FrameSearchFlag</idl> 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 <idl>com.sun.star.frame.XComponentLoader</idl>, 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 =
  (com.sun.star.frame.XComponentLoader)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 <idl>com.sun.star.lang.XComponent</idl> 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 <idlm>com.sun.star.frame.XComponentLoader:loadComponentFromURL()</idlm> has previously been discussed. The return value is a reference to a <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.lang.XEventListener</idl> 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 <idl>com.sun.star.uno.XWeak</idl>, 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 <idl>com.sun.star.lang.XEventListener</idl> at the <idl>com.sun.star.lang.XComponent</idl> 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 <idl>com.sun.star.util.XCloseable</idl> has been introduced. If the object you are referencing is a <idls>com.sun.star.util.XCloseable</idls>, register it as a <idl>com.sun.star.util.XCloseListener</idl> and throw a <idl>com.sun.star.util.CloseVetoException</idl> 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 <idlm>com.sun.star.util.XCloseable:close</idlm>.


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 <idl>com.sun.star.util.XModifiable</idl> and <idl>com.sun.star.frame.XStorable</idl>, 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 <idlm>com.sun.star.util.XCloseable:close</idlm>() 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 <idl>com.sun.star.util.XCloseable</idl>, 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 from <idl>com.sun.star.util.CloseVetoException</idl> 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 <idl>com.sun.star.util.XModifiable</idl> and <idl>com.sun.star.frame.XStorable</idl> interfaces of the document. The fact that a model refuses to be closed if it is modified is not dependable.

The interface <idl>com.sun.star.util.XCloseable</idl> inherits from <idl>com.sun.star.util.XCloseBroadcaster</idl> 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 <idl>com.sun.star.util.XCloseListener</idl> 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 )

<idlm>com.sun.star.util.XCloseable:close</idlm>() and <idlm>com.sun.star.util.XCloseListener:queryClosing</idlm>() throw a <idl>com.sun.star.util.CloseVetoException</idl>.

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 <idl>com.sun.star.frame.XController</idl> 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 =
  (com.sun.star.frame.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 =
    (com.sun.star.util.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 =
      (com.sun.star.lang.XComponent)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 <idl>com.sun.star.text.XTextDocument</idl>, <idl>com.sun.star.sheet.XSpreadsheetDocument</idl> or <idl>com.sun.star.drawing.XDrawPagesSupplier</idl> are retrieved using queryInterface().

If the office component supports the <idl>com.sun.star.frame.XStorable</idl> interface applying to every component implementing the service <idl>com.sun.star.document.OfficeDocument</idl>, 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 <idl>com.sun.star.util.XModifiable</idl> 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 =
(com.sun.star.lang.XServiceInfo)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") == true)
    sFilter = new String("HTML (StarWriter)");
else
if(xInfo.supportsService ("com.sun.star.text.WebDocument") == true)
    sFilter = new String("HTML");
else
if(xInfo.supportsService ("com.sun.star.sheet.SpreadsheetDocument") == true)
    sFilter = new String("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 = new Boolean(true);

  com.sun.star.frame.XStorable xStore =
(com.sun.star.frame.XStorable)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 <idl>com.sun.star.frame.XModel</idl> 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 <idl>com.sun.star.view.XPrintable</idl>. 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 <idl>com.sun.star.util.URL</idl> 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 <idl>com.sun.star.frame.XDispatchProvider</idl>. 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 <idl>com.sun.star.frame.XDispatchProviderInterception</idl> 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 <idl>com.sun.star.util.URL</idl> 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 <idl>com.sun.star.util.URL</idl> 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 <idl>com.sun.star.util.URLTransformer</idl> 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 <idl>com.sun.star.frame.FeatureStateEvent</idl> 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 <idl>com.sun.star.frame.XDispatchProvider</idl> interface does not handle requests, but delegates every request to an individual dispatch object implementing <idl>com.sun.star.frame.XDispatch</idl>.

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 <idl>com.sun.star.frame.XDispatchProvider</idl> 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 <idl>com.sun.star.frame.XDispatch</idl>:

[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 <idl>com.sun.star.frame.XStatusListener</idl> 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 <idl>com.sun.star.frame.FeatureStateEvent</idl> 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 <idl>com.sun.star.frame.FeatureStateEvent</idl> 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 <idl>com.sun.star.frame.FrameActionEvent</idl>. 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 <idl>com.sun.star.beans.PropertyValue</idl> 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 =
  (com.sun.star.frame.XDispatchProvider)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 =
  (com.sun.star.util.XURLTransformer)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 <idl>com.sun.star.frame.XNotifyingDispatch</idl> 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 <idl>com.sun.star.frame.XNotifyingDispatch</idl> 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 <idl>com.sun.star.frame.XDispatchResultListener</idl> 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 <idl>com.sun.star.frame.DispatchResultEvent</idl> 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 =
  (com.sun.star.frame.XDispatchProvider)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 =
  (com.sun.star.util.XURLTransformer)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 =
  (com.sun.star.frame.XNotifyingDispatch)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 <idl>com.sun.star.frame.XDispatch</idl>. 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 <idl>com.sun.star.frame.XDispatchProvider</idl> 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 <idl>com.sun.star.frame.XDispatchProviderInterceptor</idl>.

This chain usually only consists of the frame and the controller of the office component it contains, but the frame offers the <idl>com.sun.star.frame.XDispatchProviderInterception</idl> 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 <idl>com.sun.star.frame.XDispatch</idl>. 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 <idl>com.sun.star.frame.FeatureStateEvent</idl> 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 <idl>com.sun.star.frame.XDispatchProviderInterceptor</idl> 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.

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 <idl>com.sun.star.awt.XWindowPeer</idl> so that it is usable for the LibreOffice window toolkit. This interface can have an empty implementation. In <idlm>com.sun.star.awt.XToolkit:createWindow</idlm>(), another interface <idl>com.sun.star.awt.XSystemDependentWindowPeer</idl> is expected that queries the HWND. Thus, XWindowPeer is for transporting and <idl>com.sun.star.awt.XSystemDependentWindowPeer</idl> queries the HWND.

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

com.sun.star.awt.XToolkit xToolkit =
        (com.sun.star.awt.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 =
        (com.sun.star.awt.XWindowPeer)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 <idl>com.sun.star.awt.Toolkit</idl> service implements the interface <idl>com.sun.star.awt.XSystemChildFactory</idl> 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 <idl>com.sun.star.awt.XWindow</idl>. 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 <idlm>com.sun.star.awt.XSystemChildFactory:createSystemChild</idlm>() 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 =
        (com.sun.star.awt.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 =
        (com.sun.star.awt.XSystemChildFactory)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 <idl>com.sun.star.lang.DisposedException</idl>, 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 <idl>com.sun.star.datatransfer.clipboard.SystemClipboard</idl>. 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 <idl>com.sun.star.datatransfer.DataFlavor</idl> has three members:

Members of <idl>com.sun.star.datatransfer.DataFlavor</idl>
<idlm>com.sun.star.datatransfer.DataFlavor:MimeType</idlm> 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.
<idlm>com.sun.star.datatransfer.DataFlavor:HumanPresentableName</idlm> The human presentable name for the data format that this DataFlavor represents.
<idlm>com.sun.star.datatransfer.DataFlavor:DataType</idlm> 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 <idl>com.sun.star.datatransfer.XTransferable</idl>. 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 <idl>com.sun.star.datatransfer.XTransferable</idl>. 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 <idl>com.sun.star.datatransfer.clipboard.XClipboardOwner</idl>. 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 <idl>com.sun.star.datatransfer.clipboard.XClipboardOwner</idl>.

...

//----------------------------------------
// 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 = (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 <idl>com.sun.star.datatransfer.clipboard.XClipboardListener</idl> and registers as a clipboard listener. Implementing the interface <idl>com.sun.star.datatransfer.clipboard.XClipboardListener</idl> 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 <idl>com.sun.star.i18n.XLocaleData</idl> 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 <idl>com.sun.star.i18n.XCharacterClassification</idl> 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.

Warning.svg

Warning:
There was a bug in OpenOffice.org 1.0.2 that prevents this special example of Turkish case conversion to work properly. The issue is resolved for OpenOffice.org 1.1.0.


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

XCalendar

The <idl>com.sun.star.i18n.XCalendar</idl> 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 <idl>com.sun.star.i18n.Calendar</idl> even if the default calendar is not a Gregorian calendar, but this is not mandatory. Available calendars are obtained through the <idl>com.sun.star.i18n.XLocaleData</idl> interface.

Warning.svg

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


XExtendedCalendar

The <idl>com.sun.star.i18n.XExtendedCalendar</idl> 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 <idl>com.sun.star.i18n.XCalendar</idl>. The interface provides a method to obtain display strings of date parts for specific calendars of a specific locale.

XNumberFormatCode

The <idl>com.sun.star.i18n.XNumberFormatCode</idl> interface provides access to predefined number format codes for a given locale, which in turn are obtained through the <idl>com.sun.star.i18n.XLocaleData</idl> 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 <idl>com.sun.star.i18n.XNativeNumberSupplier</idl> 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 <idl>com.sun.star.i18n.XCollator</idl> 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 <idl>com.sun.star.i18n.XLocaleData</idl> 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 <idl>com.sun.star.i18n.XTransliteration</idl> interface instead.

XTransliteration

The <idl>com.sun.star.i18n.XTransliteration</idl> 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 <idl>com.sun.star.i18n.XLocaleData</idl> 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 <idl>com.sun.star.i18n.XTextConversion</idl> 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 <idl>com.sun.star.i18n.XBreakIterator</idl> 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 <idl>com.sun.star.linguistic2.XHyphenator</idl>. 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 <idl>com.sun.star.i18n.XLocaleData</idl> 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 <idl>com.sun.star.i18n.XIndexEntrySupplier</idl> 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 <idl>com.sun.star.i18n.XExtendedIndexEntrySupplier</idl> 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 <idl>com.sun.star.i18n.XIndexEntrySupplier</idl>.

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

Implementing a New Locale

Warning.svg

Warning:
The procedures, directory layout, and file contents described here reflect the structure of the i18npool module as of OpenOffice.org version 1.1.0, and not the i18n module for OpenOffice.org 1.0.2.


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 <idl>com.sun.star.i18n.XLocaleData</idl> interface methods:

Type Count
<idl>com.sun.star.i18n.LanguageCountryInfo</idl> exactly 1
<idl>com.sun.star.i18n.LocaleDataItem</idl> exactly 1
sequence<<idl>com.sun.star.i18n.Calendar</idl>> 1 or more
sequence<<idl>com.sun.star.i18n.Currency</idl>> 1 or more
sequence<<idl>com.sun.star.i18n.FormatElement</idl>> at least all <idl>com.sun.star.i18n.NumberFormatIndex</idl> format codes (see below)
sequence<<idl>com.sun.star.i18n.Implementation</idl>> 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
<idl>com.sun.star.i18n.ForbiddenCharacters</idl> exactly 1, though may have empty elements
sequence<string> reserved words all words of <idl>com.sun.star.i18n.reservedWords</idl>
sequence<<idl>com.sun.star.beans.PropertyValues</idl>> numbering levels

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

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

(no public XLocaleData API method available, used by and accessible through <idl>com.sun.star.text.XDefaultNumberingProvider</idl> 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 <idl>com.sun.star.i18n.NumberFormatIndex</idl> 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" <idl>com.sun.star.i18n.NumberFormatIndex</idl> 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" <idl>com.sun.star.i18n.NumberFormatIndex</idl> 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 <idl>com.sun.star.i18n.XCalendar</idl> 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 <idl>com.sun.star.i18n.XCharacterClassification</idl> 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 <idl>com.sun.star.i18n.XBreakIterator</idl> 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 <idl>com.sun.star.i18n.XCollator</idl> 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)
        throw (::com::sun::star::uno::RuntimeException)
{
    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)
        throw (::com::sun::star::uno::RuntimeException)
{
    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 <idl>com.sun.star.i18n.XTransliteration</idl> can be used for string conversion. The front end implementation TransliterationImpl will load and cache specific transliteration services by a predefined enum in <idl>com.sun.star.i18n.TransliterationModules</idl> or<idl>com.sun.star.i18n.TransliterationModulesNew</idl>, 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 <idl>com.sun.star.i18n.XTextConversion</idl> can be used for string conversion. The service <idl>com.sun.star.i18n.TextConversion</idl> 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 <idl>com.sun.star.linguistic2.XConversionDictionary</idl>, 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", new Integer(24) );
            xPS.setPropertyValue( "CharHeight", new Integer(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 = (com.sun.star.frame.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 = (com.sun.star.i18n.XTextConversion) 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 <idl>com.sun.star.i18n.XNativeNumberSupplier</idl> 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 <idl>com.sun.star.i18n.NativeNumberMode</idl> constants, please add the description to <idl>com.sun.star.i18n.NativeNumberMode</idl> if your changes are to be added back to the OpenOffice.org code repository.

XIndexEntrySupplier

The interface <idl>com.sun.star.i18n.XIndexEntrySupplier</idl> 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 )
        throw (uno::RuntimeException)
{
    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 )
        throw (uno::RuntimeException)
{
    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:

  • <idl>com.sun.star.linguistic2.LinguServiceManager</idl>
  • <idl>com.sun.star.linguistic2.DictionaryList</idl>
  • <idl>com.sun.star.linguistic2.LinguProperties</idl>

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

  • <idl>com.sun.star.linguistic2.SpellChecker</idl>
  • <idl>com.sun.star.linguistic2.Hyphenator</idl>
  • <idl>com.sun.star.linguistic2.Thesaurus</idl>

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 <idl>com.sun.star.linguistic2.XSupportedLocales</idl>.

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 <idl>com.sun.star.linguistic2.XLinguServiceManager</idl>.

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 <idl>com.sun.star.linguistic2.LinguProperties</idl>.

The DictionaryList (see <idl>com.sun.star.linguistic2.DictionaryList</idl>) 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 = (XUnoUrlResolver)UnoRuntime.queryInterface(
        XUnoUrlResolver.class, xUrlResolver );
    Object rInitialObject = urlResolver.resolve( "uno:" + sConnection +
        ";urp;StarOffice.NamingService" );
    XNamingService rName = (XNamingService)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 = new 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 <idl>com.sun.star.linguistic2.XSpellChecker</idl>. 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.Exception,
    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 <idl>com.sun.star.linguistic2.LinguProperties</idl> Description
<idlm>com.sun.star.linguistic2.LinguProperties:IsIgnoreControlCharacters</idlm> Defines if control characters should be ignored or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsUseDictionaryList</idlm> Defines if the dictionary-list should be used or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsGermanPreReform</idlm> Defines if the new German spelling rules should be used for German language text or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsSpellUpperCase</idlm> Defines if words with only uppercase letters should be subject to spellchecking or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsSpellWithDigits</idlm> Defines if words containing digits or numbers should be subject to spellchecking or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsSpellCapitalization</idlm> 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 <idl>com.sun.star.linguistic2.LinguProperties</idl> instance and change it by calling <idlm>com.sun.star.beans.XPropertySet:setPropertyValue</idlm>(). 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 <idl>com.sun.star.linguistic2.XSpellAlternatives</idl>.

Using Hyphenator

The interface used for hyphenation is <idl>com.sun.star.linguistic2.XHyphenator</idl>. 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.Exception,
    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 <idl>com.sun.star.linguistic2.LinguProperties</idl>
<idlm>com.sun.star.linguistic2.LinguProperties:IsIgnoreControlCharacters</idlm> Defines if control characters should be ignored or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsUseDictionaryList</idlm> Defines if the dictionary-list should be used or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsGermanPreReform</idlm> Defines if the new German spelling rules should be used for German language text or not.
<idlm>com.sun.star.linguistic2.LinguProperties:HyphMinLeading</idlm> The minimum number of characters of a hyphenated word to remain before the hyphenation character.
<idlm>com.sun.star.linguistic2.LinguProperties:HyphMinTrailing</idlm> The minimum number of characters of a hyphenated word to remain after the hyphenation character.
<idlm>com.sun.star.linguistic2.LinguProperties:HyphMinWordLength</idlm> 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 <idl>com.sun.star.linguistic2.XHyphenatedWord</idl>.

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 (<idl>com.sun.star.linguistic2.XHyphenatedWord</idl>).

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 <idl>com.sun.star.linguistic2.XPossibleHyphens</idl>.

Using Thesaurus

The interface used for the thesaurus is <idl>com.sun.star.linguistic2.XThesaurus</idl>. 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.Exception,
    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 <idl>com.sun.star.linguistic2.LinguProperties</idl>
<idlm>com.sun.star.linguistic2.LinguProperties:IsIgnoreControlCharacters</idlm> Defines if control characters should be ignored or not.
<idlm>com.sun.star.linguistic2.LinguProperties:IsGermanPreReform</idlm> 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 <idl>com.sun.star.linguistic2.XDictionary</idl> report their status changes as events <idl>com.sun.star.linguistic2.DictionaryEvent</idl> to the DictionaryList, which collects and transforms their information into DictionaryList events <idl>com.sun.star.linguistic2.DictionaryListEvent</idl>, 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 <idl>com.sun.star.linguistic2.LinguServiceEvent</idl> that hints to its listeners that spelling or hyphenation should be reevaluated. For this purpose, those implementations support the <idl>com.sun.star.linguistic2.XLinguServiceEventBroadcaster</idl> interface.

The LinguServiceManager acts as a listener for <idl>com.sun.star.linguistic2.DictionaryListEvent</idl> and <idl>com.sun.star.linguistic2.LinguServiceEvent</idl> events. The respective interfaces are <idl>com.sun.star.linguistic2.XDictionaryListEventListener</idl> and <idl>com.sun.star.linguistic2.XLinguServiceEventListener</idl>. The events from the DictionaryList are transformed into <idl>com.sun.star.linguistic2.LinguServiceEvent</idl> 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 <idl>com.sun.star.linguistic2.XLinguServiceEventListener</idl> to the LinguServiceManager only.

Implementing the <idl>com.sun.star.linguistic2.XLinguServiceEventListener</idl> 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 <idl>com.sun.star.linguistic2.XLinguServiceEventListener</idl>. 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 <idl>com.sun.star.linguistic2.LinguServiceEvent</idl>s 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:

  • <idl>com.sun.star.linguistic2.XSpellChecker</idl>
  • <idl>com.sun.star.linguistic2.XLinguServiceEventBroadcaster</idl>
  • <idl>com.sun.star.lang.XInitialization</idl>
  • <idl>com.sun.star.lang.XServiceDisplayName</idl>
  • <idl>com.sun.star.lang.XServiceInfo</idl>
  • <idl>com.sun.star.lang.XComponent</idl>

and

  • <idl>com.sun.star.lang.XTypeProvider</idl>, to access your add-in interfaces from LibreOffice Basic, otherwise, this interface is not mandatory.

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 <idl>com.sun.star.linguistic2.SpellFailure</idl>.

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 <idl>com.sun.star.linguistic2.LinguServiceEvent</idl> 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:

  • <idl>com.sun.star.linguistic2.XHyphenator</idl>
  • <idl>com.sun.star.linguistic2.XLinguServiceEventBroadcaster</idl>
  • <idl>com.sun.star.lang.XInitialization</idl>
  • <idl>com.sun.star.lang.XServiceDisplayName</idl>
  • <idl>com.sun.star.lang.XServiceInfo</idl>
  • <idl>com.sun.star.lang.XComponent</idl>

and

  • <idl>com.sun.star.lang.XTypeProvider</idl>, if you want to access your add-in interfaces from LibreOffice Basic, otherwise, this interface is not mandatory.

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 <idl>com.sun.star.linguistic2.XHyphenator</idl> interface instead of the <idl>com.sun.star.linguistic2.XSpellChecker</idl> 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 <idl>com.sun.star.linguistic2.XHyphenator</idl> 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:

  • <idl>com.sun.star.linguistic2.XThesaurus</idl>
  • <idl>com.sun.star.lang.XInitialization</idl>
  • <idl>com.sun.star.lang.XServiceDisplayName</idl>
  • <idl>com.sun.star.lang.XServiceInfo</idl>
  • <idl>com.sun.star.lang.XComponent</idl>

and

  • <idl>com.sun.star.lang.XTypeProvider</idl>, if you want to access your add-in interfaces from LibreOffice Basic, otherwise, this interface is not mandatory.

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 <idl>com.sun.star.linguistic2.XThesaurus</idl> 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 <idl>com.sun.star.document.ExportFilter</idl> or <idl>com.sun.star.document.ImportFilter</idl> service, or both in case the filter should support import and export. The filter will get a <idl>com.sun.star.document.MediaDescriptor</idl> 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 <idl>com.sun.star.document.ImportFilter</idl> and/or <idl>com.sun.star.document.ExportFilter</idl>

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 <idl>com.sun.star.document.ExtendedTypeDetection</idl> 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 <idl>com.sun.star.document.ExtendedTypeDetection</idl> and the filter services) and so allow to bind extendable sets of instances implementing them, others (like e.g. the <idl>com.sun.star.document.TypeDetection</idl> 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 <idlm>com.sun.star.frame.XComponentLoader:loadComponentFromURL</idlm>() method of a suitable object. Such object may be the <idl>com.sun.star.frame.Desktop</idl> object or any instance of the <idl>com.sun.star.frame.Frame</idl> 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 <idl>com.sun.star.document.MediaDescriptor</idl> service that here is implemented as a Sequence of <idl>com.sun.star.beans.PropertyValue</idl>. In most cases it will contain several properties that allow to create an object implementing <idl>com.sun.star.io.XStream</idl> or <idl>com.sun.star.io.XInputStream</idl> 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 <idl>com.sun.star.frame.FrameLoader</idl> or <idl>com.sun.star.frame.SynchronousFrameLoader</idl> 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 <idl>com.sun.star.frame.FrameLoader</idl> service is deprecated. If a custom frame loader is registered, it should be a <idl>com.sun.star.frame.SynchronousFrameLoader</idl> 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 <idl>com.sun.star.document.TypeDetection</idl> 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 <idl>com.sun.star.frame.SynchronousFrameLoader</idl> 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 <idl>com.sun.star.document.FilterFactory</idl> 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 <idl>com.sun.star.frame.XStorable</idl>, implemented by office documents. It will contain several properties that allow to create an object implementing <idl>com.sun.star.io.XStream</idl> or <idl>com.sun.star.io.XOutputStream</idl> 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 <idl>com.sun.star.document.ImportFilter</idl> and <idl>com.sun.star.document.ExportFilter</idl>. 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 <idl>com.sun.star.document.FilterFactory</idl>. 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 <idl>com.sun.star.lang.XInitialization</idl>. 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:

  • The first item in the list is a sequence of <idl>com.sun.star.beans.PropertyValue</idl> structs, that describe the configuration properties of the filter.
  • All other items are directly copied from the parameter Arguments of the factory interface method <idlm>com.sun.star.lang.XMultiServiceFactory:createInstanceWithArguments</idlm>().
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 <idlm>com.sun.star.container.XNamed:setName</idlm>() 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 <idl>com.sun.star.document.XImporter</idl> interface and its method setTargetDocument() to bind the filter to the document it should import into. If content is stored, the interface <idl>com.sun.star.document.XExporter</idl> 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 <idl>com.sun.star.document.XFilter</idl> 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 <idl>com.sun.star.document.TypeDetection</idl> 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 <idl>com.sun.star.document.TypeDetection</idl> 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 <idl>com.sun.star.document.FilterFactory</idl> 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 <idl>com.sun.star.document.ExtendedTypeDetection</idl>. 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 <idl>com.sun.star.document.FilterFactory</idl> 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 <idl>com.sun.star.document.TypeDetection</idl> using the interface <idl>com.sun.star.container.XNameAccess</idl>. 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 <idl>com.sun.star.document.FilterFactory</idl> using the interface <idl>com.sun.star.container.XNameAccess</idl>. 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 <idl>com.sun.star.document.ImportFilter</idl> or <idl>com.sun.star.document.ExportFilter</idl>. 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.
  • FilterFactory service, see <idl>com.sun.star.document.FilterFactory:XNameAccess</idl> : int. Contains a set of boolean values.
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="PocketWord File" oor:op="replace">
    <prop oor:name="UIName">
        <value>Pocket Word</value>
    </prop>
    <prop oor:name="Type"><value>writer_PocketWord_File</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>com.sun.star.documentconversion.XMergeBridge classes/pocketword.jar com.sun.star.comp.Writer.XMLImporter com.sun.star.comp.Writer.XMLExporter staroffice/sxw application/x-pocket-word</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 <idl>com.sun.star.documentconversion.XSLTFilter</idl> 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 <idl>com.sun.star.ui.dialogs.FilterOptionsDialog</idl>. 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 <idl>com.sun.star.beans.XPropertyAccess</idl>. 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 (<idl>com.sun.star.frame.FrameLoader</idl>) and synchronous (<idl>com.sun.star.frame.SynchronousFrameLoader</idl>) 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 <idl>com.sun.star.frame.FrameLoaderFactory</idl> using the interface <idl>com.sun.star.container.XNameAccess</idl>. 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 <idl>com.sun.star.document.XFilter</idl> 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 <idlm>com.sun.star.xml.XImportFilter:importer</idlm>() 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 <idlm>com.sun.star.xml.XExportFilter:exporter</idlm>() to pass a target location and user data. In this case, the office expects the export filter to be a <idl>com.sun.star.xml.sax.XDocumentHandler</idl>, 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 <idlm>com.sun.star.xml.XExportFilter:exporter</idlm>().

Sample implementations

There are currently three filtering components which use the XML filter adapter.

The first one is the XMergeBridge. This has been created as a means of linking the XMerge Small Device filter framework with LibreOffice. This means that any available XMerge plugin, can also be used as a LibreOffice filter. This is currently hosted within the XMerge project in OpenOffice HG at

 xmerge/java/org/openoffice/xmerge/xmergebridge

The final two are a Java and a C++ implementation of a Flat LibreOffice XML reader and writer. 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 temporarily hosted in HG at

 xmerge/java/org/openoffice/xmerge/xmergebridge/FlatXml
Writing the Filtering Component

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

Importer:

<idl>com.sun.star.xml.XMLImportFilter</idl>

Exporter:

<idl>com.sun.star.xml.XMLExportFilter</idl> and <idl>com.sun.star.xml.sax.XDocumentHandler</idl>
XImportFilter

The service <idl>com.sun.star.xml.XMLImportFilter</idl> 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 <idl>com.sun.star.xml.XExportFilter</idl> 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 <idl>com.sun.star.xml.sax.XDocumentHandler</idl> 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.equalsAsciiL (RTL_CONSTASCII_STRINGPARAM("InputStream")))
                pValue[i].Value >>= xInputStream;
        else if (pValue[i].Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("FileName")
                pValue[i].Value >>= sFileName;
        else if (pValue[i].Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("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 <idlm>com.sun.star.xml.XImportFilter:importer</idlm>, 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(RTL_CONSTASCII_USTRINGPARAM("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 <idlm>com.sun.star.xml.XExportFilter:exporter</idlm>() method operates in much the same way as <idlm>com.sun.star.xml.XImportFilter:importer</idlm>(), except that instead of the exporter using a provided XDocumentHandler, it is itself a <idl>com.sun.star.xml.sax.XDocumentHandler</idl> 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 <idlm>com.sun.star.xml.XExportFilter:exporter</idlm>() method returns, the XML filter adapter then invokes the <idl>com.sun.star.xml.sax.XDocumentHandler</idl> methods to parse the XML output.

For the filtering, the <idl>com.sun.star.xml.sax.XDocumentHandler</idl> 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 <idl>com.sun.star.document.ExtendedTypeDetection</idl> 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 <idl>com.sun.star.document.XExtendedFilterDetection</idl> 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 ) throw (com::sun::star::uno::RuntimeException)
{
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.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("TypeName"))) {
        }
        else if (pValue[i].Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("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 sal_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 <idl>com.sun.star.util.XNumberFormatsSupplier</idl> that has one method getNumberFormats() that returns <idl>com.sun.star.util.NumberFormats</idl>. 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 <idl>com.sun.star.beans.XPropertySet</idl> interface, for example, from data sources supporting the <idl>com.sun.star.sdb.DataSource</idl> service and from database connections supporting the service <idl>com.sun.star.sdb.DatabaseEnvironment</idl>, or <idl>com.sun.star.sdb.DatabaseAccess</idl>. In addition, all UNO controls offering the service <idl>com.sun.star.awt.UnoControlFormattedFieldModel</idl> have a NumberFormatsSupplier property.

NumberFormats Service

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

XNumberFormats

NumberFormats supports the interface <idl>com.sun.star.util.XNumberFormats</idl>. 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 <idl>com.sun.star.util.NumberFormatProperties</idl> service for the given index key.

XNumberFormatTypes

The interface <idl>com.sun.star.util.XNumberFormatTypes</idl> offers functions to retrieve the index keys of specific predefined number format types. The predefined types are addressed by constants from <idl>com.sun.star.util.NumberFormat</idl>. 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 <idl>com.sun.star.util.NumberFormatter</idl>, available at the global service manager. For this purpose, its main interface <idl>com.sun.star.util.XNumberFormatter</idl> 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 <idl>com.sun.star.util.NumberFormatter</idl> implements the interfaces <idl>com.sun.star.util.XNumberFormatter</idl> and <idl>com.sun.star.util.XNumberFormatPreviewer</idl>.

XNumberformatter

The interface <idl>com.sun.star.util.XNumberFormatter</idl> 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 <idl>com.sun.star.util.XNumberFormatPreviewer</idl> converts values to strings according to a given format code without inserting the format code into the underlying <idl>com.sun.star.util.NumberFormats</idl> 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", new Integer(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", new Integer(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 <idl>com.sun.star.document.Events</idl> 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 <idl>com.sun.star.document.XEventBroadcaster</idl>, which is an optional interface of the service <idl>com.sun.star.document.OfficeDocument</idl>. 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 <idl>com.sun.star.document.XEventListener</idl> 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 <idl>com.sun.star.document.EventObject</idl> struct, which is derived from the usual <idl>com.sun.star.lang.EventObject</idl> and contains two members: the member Source, which contains an interface pointer to the event source (here the <idl>com.sun.star.document.OfficeDocument</idl> service) and the member EventName which can be one of the names shown in the preceding table.

Both methods in the interface <idl>com.sun.star.document.XEventBroadcaster</idl> can cause problems in scripting languages if the object that implements this interface also implements <idl>com.sun.star.lang.XComponent</idl>, 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;

  SAFERRAY FAR* 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 <idl>com.sun.star.document.XEventsSupplier</idl>. 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 <idl>com.sun.star.container.XNameReplace</idl> 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 <idl>com.sun.star.container.NoSuchElementException</idl> 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 <idl>com.sun.star.beans.PropertyValue</idl> 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 <idl>com.sun.star.frame.GlobalEventBroadcaster</idl> that offers the same document-event-related functionality as described previously (interfaces <idl>com.sun.star.document.XEventBroadcaster</idl>, <idl>com.sun.star.document.XEventsSupplier</idl>), 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 <idl>com.sun.star.frame.GlobalEventBroadcaster</idl> 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 <idl>com.sun.star.util.PathSettings</idl> 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 <idl>com.sun.star.util.PathSettings</idl>
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 <idl>com.sun.star.beans.XPropertySet</idl>, <idl>com.sun.star.beans.XFastPropertySet</idl> and <idl>com.sun.star.beans.XMultiPropertySet</idl> 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 (XpropertySet) UnoRuntime.queryInterface(XPropertySet.class, xPathSettings);
    else
        return null;
}

The main interface of the path settings service is <idl>com.sun.star.beans.XPropertySet</idl>. 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 <idl>com.sun.star.lang.IllegalArgumentException</idl> 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 extends java.lang.Object {

    /*
      * 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 = (XUnoUrlResolver) UnoRuntime.queryInterface(
                XUnoUrlResolver.class, urlResolver );
            Object initialObject = xUnoUrlResolver.resolve(
                "uno:socket,host=localhost,port=2083;urp;StarOffice.ServiceManager" );
            XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
                XPropertySet.class, initialObject);
            Object context = xPropertySet.getPropertyValue("DefaultContext");
            xRemoteContext = (XComponentContext)UnoRuntime.queryInterface(
                XComponentContext.class, context);
            xRemoteServiceManager = xRemoteContext.getServiceManager();

            Object pathSubst = xRemoteServiceManager.createInstanceWithContext(
                "com.sun.star.comp.framework.PathSettings", xRemoteContext );
            xPathSettingsService = (XPropertySet)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 <idl>com.sun.star.util.PathSubstitution</idl> - 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 <idl>com.sun.star.util.PathSubstitution</idl>
$(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 <idl>com.sun.star.util.XStringSubstitution</idl>.

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 (XStringSubstitution)UnoRuntime.queryInterface(
            XStringSubstitution.class, xPathSubst );
    else
        return null;
}

The service is implemented as a one-instance service and supports the interface <idl>com.sun.star.util.XStringSubstitution</idl>. 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 <idl>com.sun.star.container.NoSuchElementException</idl>. 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 <idl>com.sun.star.container.NoSuchElementException</idl> 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 extends java.lang.Object {

    /*
      * List of predefined path variables supported by
      * the path substitution service.
      */
    private static String[] predefinedPathVariables = {
        "$(home)","$(inst)","$(prog)","$(temp)","$(user)",
        "$(work)","$(path)","$(lang)","$(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 = (XUnoUrlResolver) UnoRuntime.queryInterface(
                XUnoUrlResolver.class, urlResolver );
            Object initialObject = xUnoUrlResolver.resolve(
                "uno:socket,host=localhost,port=2083;urp;StarOffice.ServiceManager" );
            XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
                XPropertySet.class, initialObject);
            Object context = xPropertySet.getPropertyValue("DefaultContext");
            xRemoteContext = (XComponentContext)UnoRuntime.queryInterface(
                XComponentContext.class, context);
            xRemoteServiceManager = xRemoteContext.getServiceManager();

            Object pathSubst = xRemoteServiceManager.createInstanceWithContext(
                "com.sun.star.comp.framework.PathSubstitution", xRemoteContext );
            xPathSubstService = (XStringSubstitution)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(
        RTL_CONSTASCII_USTRINGPARAM("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

<idltopic>com.sun.star.rdf</idltopic>

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 <idlmodule>com.sun.star.rdf</idlmodule> module.

Nodes

First, there are the basic RDF node types:

RDF node type Interface Service
node <idl>com.sun.star.rdf.XNode</idl>
literal <idl>com.sun.star.rdf.XLiteral</idl> <idl>com.sun.star.rdf.Literal</idl>
resource <idl>com.sun.star.rdf.XResource</idl>
URI <idl>com.sun.star.rdf.XURI</idl> <idl>com.sun.star.rdf.URI</idl>
blank node <idl>com.sun.star.rdf.XBlankNode</idl> <idl>com.sun.star.rdf.BlankNode</idl>

interface XNode
{
    [readonly, attribute] string StringValue;
};

The interface <idls>com.sun.star.rdf.XResource</idls> 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 <idl>com.sun.star.rdf.Literal</idl>.

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 <idlm>com.sun.star.rdf.URI:create</idlm> and <idlm>com.sun.star.rdf.URI:createNS</idlm>, 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 <idlm>com.sun.star.rdf.URI:createKnown</idlm> constructor, together with constants from the <idl>com.sun.star.rdf.URIs</idl> 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 <idlm>com.sun.star.rdf.XRepository:createBlankNode</idlm> 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 <idl>com.sun.star.rdf.XNamedGraph</idl>. As the name implies, a named graph has a name, which is a URI. This is why the <idls>com.sun.star.rdf.XNamedGraph</idls> interface inherits from <idls>com.sun.star.rdf.XURI</idls>.

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 <idl>com.sun.star.rdf.XRepository</idl>, and the service <idl>com.sun.star.rdf.Repository</idl>.

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 <idlm>com.sun.star.rdf.XRepository:getGraphNames</idlm> method. An individual graph can be retrieved via the <idlm>com.sun.star.rdf.XRepository:getGraph</idlm> 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 <idlm>com.sun.star.rdf.XRepository:createGraph</idlm>, <idlm>com.sun.star.rdf.XRepository:destroyGraph</idlm> or <idlm>com.sun.star.rdf.XRepository:importGraph</idlm> methods directly; instead, call the respective methods of <idl>com.sun.star.rdf.XDocumentMetadataAccess</idl>, as described below.


Services that provide an RDF repository implement the interface <idl>com.sun.star.rdf.XRepositorySupplier</idl>.

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 <idl>com.sun.star.rdf.XDocumentMetadataAccess</idl>, which is implemented by the Model service of documents.

Note pin.svg

Note:
Because this interface derives from <idls>com.sun.star.rdf.XURI</idls>, you can easily create RDF statements about the document.

Warning.svg

Warning:
Do not use methods such as <idlm>com.sun.star.frame.XModel:getURL</idlm> to create a URI for the document. Always use the <idls>com.sun.star.rdf.XURI</idls> interface of the model when you need the RDF URI for a document.


Furthermore, there is the interface <idl>com.sun.star.rdf.XMetadatable</idl>, 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 <idls>com.sun.star.rdf.XMetadatable</idls> interface has an attribute <idlm>com.sun.star.rdf.XMetadatable:MetadataReference</idlm>. This attribute is only meant to be set by import filters, such as the ODF import filter. Extensions should use <idlm>com.sun.star.rdf.XMetadatable:ensureMetadataReference</idlm>, or simply use a <idls>com.sun.star.rdf.XMetadatable</idls> as a parameter to <idlm>com.sun.star.rdf.XNamedGraph:addStatement</idlm>, which will automatically call ensureMetadataReference().


Annotated text range

The service <idl>com.sun.star.text.InContentMetadata</idl> 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: <idl>com.sun.star.text.textfield.MetadataField</idl>.

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 <idl>com.sun.star.text.XText</idl> 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 <idl>com.sun.star.container.XChild</idl> 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 <idls>com.sun.star.rdf.XMetadatable</idls> 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 <idl>com.sun.star.rdf.XDocumentMetadataAccess</idl>.

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 <idlm>com.sun.star.rdf.XDocumentMetadataAccess:addMetadataFile</idlm>, 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 <idlm>com.sun.star.rdf.XDocumentMetadataAccess:importMetadataFile</idlm> 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 <idlm>com.sun.star.rdf.XRepository:getStatements</idlm> 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 <idlm>com.sun.star.rdf.XNamedGraph:getStatements</idlm>.

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 <idlm>com.sun.star.rdf.XRepository:querySelect</idlm>, which returns a table of results, and <idlm>com.sun.star.rdf.XRepository:queryAsk</idlm>, 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 <idlm>com.sun.star.rdf.XDocumentMetadataAccess:getElementByURI</idlm> 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 <idls>com.sun.star.rdf.XURI</idls> 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 <idlm>com.sun.star.rdf.XNamedGraph:removeStatements</idlm> 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 <idlm>com.sun.star.rdf.XNamedGraph:clear</idlm> 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 <idlm>com.sun.star.rdf.XDocumentMetadataAccess:removeMetadataFile</idlm>.

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> <idl>com.sun.star.text.Paragraph</idl> paragraph 3.2
<text:h> <idl>com.sun.star.text.Paragraph</idl> heading 3.2
<text:bookmark> <idl>com.sun.star.text.Bookmark</idl> bookmark 3.2
<text:bookmark-start> <idl>com.sun.star.text.Bookmark</idl> bookmark with range 3.2
<text:meta> <idl>com.sun.star.text.InContentMetadata</idl> annotated text range 3.2
<text:meta-field> <idl>com.sun.star.text.textfield.MetadataField</idl> text field whose content is generated from metadata 3.2
<text:section> <idl>com.sun.star.text.TextSection</idl> text section 3.3
<text:index-title> <idl>com.sun.star.text.TextSection</idl> index title section 3.3
<text:alphabetical-index> <idl>com.sun.star.text.DocumentIndex</idl> alphabetical index 3.3
<text:user-index> <idl>com.sun.star.text.UserDefinedIndex</idl> user defined index 3.3
<text:table-of-content> <idl>com.sun.star.text.ContentIndex</idl> table of content 3.3
<text:table-index> <idl>com.sun.star.text.TableIndex</idl> table index 3.3
<text:object-index> <idl>com.sun.star.text.ObjectIndex</idl> object index 3.3
<text:illustration-index> <idl>com.sun.star.text.IllustrationsIndex</idl> illustration index 3.3
<text:bibliography> <idl>com.sun.star.text.Bibliography</idl> bibliography 3.3

OS-specific features

Windows

Jump Lists
Result of BASIC example

<idltopic>com.sun.star.system.windows</idltopic>

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