StarOffice 7 (Win32)
Writing UNO Components
Dietrich Schulten
2002-04-22T14:03:36
Joachim Lingner
2006-09-27T16:50:25
Diane O'Brien
2002-07-25T18:59:47
en-US
1693
P20DT3H10M3S
3433512
0
32546
22015
true
false
false
false
view2
13995
3451121
0
3433512
32544
3455525
0
100
false
false
false
2
zh
CN
﹀﹂﹄﹏、~¢々‖•·ˇˉ―--′
.([{£¥'"‵〈《「『【〔〖([{£¥〝︵︷︹︻︽︿﹁﹃﹙﹛﹝({
0
false
false
false
false
true
true
true
true
8gT+/1hlcm94IERvY3VQcmludCBOMjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGVyb3ggRG9jdVByaW50IE4yNAAAAAAAAAAAAAAAAAAWAAEAOAQAAAAAAAABAAhSAAAEdAAAM1ROVwEACABYZXJveCBEb2N1UHJpbnQgTjI0AAAAAAAAAAAAAAAAAAEEAAWcAJQDQ/+ABwEACQCaCzQIZAABAA8AWAIBAAIAWAIDAAEAQTQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAIAAAAQAQAA/////wAAAAAAAAAAAAAAAAAAAABESU5VIgAAADQCYAGE7oCvAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAQAAAAAAAAADAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAQAANVhSWAEAAAAAAAAAAAAAAAAAPkAcAAAA5ubmAEQAcgBhAGYAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMAAA
0
true
true
false
false
false
false
Adreßbuch
Xerox DocuPrint N24
true
false
false
false
true
false
0
disabled
true
true
adressen
true
false
121 Product name and Release number Manual Title March 2001
Chapter 121
121 Product name and Release number Manual Title March 2001
121
121 Product name and Release number Manual Title March 2001
121
121
121
121 Product name and Release number Manual Title
121 Product name and Release number Manual Title
Contents 121
Contents 121
121 Product name and Release number Manual Title March 2001
Index 121
Chapter 4
121
121
Index
121
Chapter 4 Writing UNO Components 121
121 Product name and Release number Manual Title March 2001
Writing UNO Components
[PRODUCTNAME] can be extended by UNO components. UNO components are shared libraries or jar files with the ability to instantiate objects that can integrate themselves into the UNO environment. A UNO component can access existing features of [PRODUCTNAME], and it can be used from within [PRODUCTNAME] through the object communication mechanisms provided by UNO.
[PRODUCTNAME] provides many entry points for these extensions.
Arbitrary objects written in Java or C++ can be called from the user interface, display their own GUI, and work with the entire application.
Calc Add-Ins can be used to create new formula sets that are presented in the formula autopilot.
Chart Add-Ins can insert new Chart types into the charting tool.
New database drivers can be installed into the office to extend data access.
Entire application modules are exchangeable, for instance the linguistics module.
It is possible to create new document types and add them to the office. For instance, a personal information manager could add message, calendar, task and journal document components, or a project manager could support a new project document.
Developers can leverage the [PRODUCTNAME] XML file format to read and write new file formats through components.
From [PRODUCTNAME] [OO1.1] there is comprehensive support for component extensions. The entire product cycle of a component is now covered:
The design and development of components has been made easier by adding wizards for components to the NetBeans IDE. They are described in the directory docs/DevStudioWizard of the SDK. There are wizards for general components, for Calc AddIns and for IDL files.
Components can integrate themselves into the user interface, using simple configuration files. You can add new menus, toolbar items, and help items for a component simply by editing XML configuration files.
[jl]Components are deployed with the Extension Manager. See chapter [CHAPTER:Extensions].Component deployment is performed by a package installer, which inserts new components and their user interface extensions into networked and single installations of [PRODUCTNAME]. During the production phase the package installer makes it simple to maintain components, to introduce bug fixes and new versions of a component. When a packaged component is no longer needed, it can easily be removed. This way, [PRODUCTNAME] keeps the promise of being open for modular extensions.
Last but not least, this is not the only way to add features to the office. Learning how to write components and how to use the [PRODUCTNAME] API at the same time teaches you the techniques used in the [PRODUCTNAME] code base, thus enabling you to work with the existing [PRODUCTNAME] source code, extend it or introduce bug fixes.
Components are the basis for all of these extensions. This chapter teaches you how to write UNO components. It assumes that you have at least read the chapter [CHAPTER:FirstSteps] and—depending on your target language—the section about the Java or C++ language binding in [CHAPTER:ProfUNO].
Required Files
[PRODUCTNAME] Software Development Kit (SDK)
The SDK provides a build environment for your projects, separate from the OpenOffice.org build environment. It contains the necessary tools for UNO development, C and C++ libraries, JARs and include files, Java packages, UNO type definitions and example code. But most of the necessary libraries and files Java UNO packages are shared with an existing [PRODUCTNAME] installation which is a prerequisite for a SDK.
The SDK development tools (executables) contained in the SDK are used in the following chapter. Become familiar with the following table that lists the executables from the SDK. These executables are found in the platform specific bin folder of the SDK installation. In Windows, they are in the folder <SDK>\windows\bin, on Linux they are stored in <SDK>/linux/bin and on Solaris in <SDK>/solaris/bin.
Executable
Description
idlc
The UNOIDL compiler that creates binary type description files with the extension .urd for registry database files.
idlcpp
The idlc preprocessor used by idlc.
cppumaker
The C++ UNO maker that generates headers with UNO types mapped from binary type descriptions to C++ from binary type descriptions.
javamaker
Java maker that generates interface and class definitions for UNO types mapped from binary type descriptions to Java from binary type descriptions.
xml2cmp
XML to Component that can extract type names from XML object descriptions for use with cppumaker and javamaker, creates functions.
regmerge
The registry merge that merges binary type descriptions into registry files.
regcomp
The register component that tells a registry database file that there is a new component and where it can be found.
pkgchkunopkg
The command line tool of the extension manager.package check that installs components into an installed [PRODUCTNAME].
regview
The registry view that outputs the content of a registry database file in readable format.
autodoc
The automatic documentation tool that evaluates Javadoc style comments in idl files and generates documentation from them.
rdbmaker
The registry database maker that creates registry files with selected types and their dependencies.
uno
The UNO executable. It is a standalone UNO environment which is able to run UNO components supporting the [IDL:com.sun.star.lang.XMain] interface, one possible use is: $ uno -s ServiceName -r MyRegistry.rdb -- MyMainClass arg1
GNU Make
The makefiles in the SDK assume that the GNU make is used. Documentation for GNU make command line options and syntax are available at www.gnu.org. In Windows, not every GNU make seems stable, notably some versions of Cygwin make were reported to have problems with the SDK makefiles. Other GNU make binaries, such as the one from unixutils.sourceforge.net work well even on the Windows command line. The package UnxUtils comes with a zsh shell and numerous utilities, such as find, sed. To install UnxUtils, download and unpack the archive, and add <UnxUtils>\usr\local\wbin to the PATH environment variable. Now launch sh.exe from <UnxUtils>\bin and issue the command make from within zsh or use the Windows command line to run make. For further information about zsh, go to zsh.sunsite.dk.
Using UNOIDL to Specify New Components
Component development does not necessarily start with the declaration of new interfaces or new types. Try to use the interfaces and types already defined in the [PRODUCTNAME] API. If existing interfaces cover your requirements and you need to know how to implement them in your own component, go to section [CHAPTER:Components.Architecture]. The following describes how to declare your own interfaces and other types you might need.
UNO uses its own meta language UNOIDL (UNO Interface Definition Language) to specify types. Using a meta language for this purpose enables you to generate language specific code, such as header files and class definitions, to implement objects in any target language supported by UNO. UNOIDL keeps the foundations of UNO language independent and takes the burden of mechanic language adaptation from the developer's shoulders when implementing UNO objects.
To define a new interface, service or other entity, write its specification in UNOIDL, then compile it with the UNOIDL compiler idlc. After compilation, merge the resulting binary type description into a type library that is used during the make process to create necessary language dependent type representations, such as header or Java class files. The chapter [CHAPTER:ProfUNO] provides the various type mappings used by cppumaker and javamaker in the language binding sections. Refer to the section [CHAPTER:Components.Deployment.UNORegistries.TypeLibrary] for details about type information in the registry-based type library.
Note graphics marks a special text section
When writing your own specifications, please consult the chapter [CHAPTER:API.Design] which treats design principles and conventions used in API specifications. Follow the rules for universality, orthogonality, inheritance and uniformity of the API as described in the Design Guide.
Writing the Specification
There are similarities between C++, CORBA IDL and UNOIDL, especially concerning the syntax and the general usage of the compiler. If you are familiar with reading C++ or CORBA IDL, you will be able to understand much of UNOIDL, as well.
As a first example, consider the IDL specification for the [IDL:com.sun.star.bridge.XUnoUrlResolver] interface. An idl file usually starts with a number of preprocessor directives, followed by module instructions and a type definition:
#ifndef __com_sun_star_bridge_XUnoUrlResolver_idl__
#define __com_sun_star_bridge_XUnoUrlResolver_idl__
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/IllegalArgumentException.idl>
#include <com/sun/star/connection/ConnectionSetupException.idl>
#include <com/sun/star/connection/NoConnectException.idl>
module com { module sun { module star { module bridge {
/** service <type scope="com::sun::star::bridge">UnoUrlResolver</type>
implements this interface.
*/
published interface XUnoUrlResolver: com::sun::star::uno::XInterface
{
// method com::sun::star::bridge::XUnoUrlResolver::resolve
/** resolves an object, on the UNO URL.
*/
com::sun::star::uno::XInterface resolve( [in] string sUnoUrl )
raises (com::sun::star::connection::NoConnectException,
com::sun::star::connection::ConnectionSetupException,
com::sun::star::lang::IllegalArgumentException);
};
}; }; }; };
#endif
We will discuss this idl file step by step below, and we will write our own UNOIDL specification as soon as possible. The file specifying [IDL:com.sun.star.bridge.XUnoUrlResolver] is located in the idl folder of your SDK installation, <SDK>/idl/com/sun/star/bridge/XUnoUrlResolver.idl.
UNOIDL definition file names have the extension .idl by convention. The descriptions must use the US ASCII character set without special characters and separate symbols by whitespace, i.e. blanks, tabs or linefeeds.
Preprocessing
Just like a C++ compiler, the UNOIDL compiler idlc can only use types it already knows. The idlc knows 15 simple types such as boolean, int or string (they are summarized below). Whenever a type other than a simple type is used in the idl file, its declaration has to be included first. For instance, to derive an interface from the interface XInterface, include the corresponding file XInterface.idl. Including means telling the preprocessor to read a given file and execute the instructions found in it.
#include <com/sun/star/uno/XInterface.idl> // searched in include path given in -I parameter
#include "com/sun/star/uno/XInterface.idl" // searched in current path, then in include path
There are two ways to include idl files. A file name in angled brackets is searched on the include path passed to idlc using its -I option. File names in double quotes are first searched on the current path and then on the include path.
The XUnoUrlResolver definition above includes [IDL:com.sun.star.uno.XInterface] and the three exceptions thrown by the method resolve(), [IDL:com.sun.star.lang.IllegalArgumentException], [IDL:com.sun.star.connection.ConnectionSetupException] and [IDL:com.sun.star.connection.NoConnectException].
In [PRODUCTNAME] [OO2.0], it is no longer necessary to explicitly state that an interface type derives from XInterface—if an interface type derives from no other interface type, it is implicitly taken to derive from XInterface. However, even in such situations it is important to explicitly include the file XInterface.idl.
Furthermore, to avoid warnings about redefinition of already included types, use #ifndef and #define as shown above. Note how the entire definition for XUnoUrlResolver is enclosed between #ifndef and #endif. The first thing the preprocessor does is to check if the flag __com_sun_star_bridge_XUnoUrlResolver_idl__ has already been defined. If not, the flag is defined and idlc continues with the definition of XUnoUrlResolver.
Adhere to the naming scheme for include flags used by the [PRODUCTNAME] developers: Use the file name of the IDL file that is to be included, add double underscores at the beginning and end of the macro, and replace all slashes and dots by underscores.
For other preprocessing instructions supported by idlc refer to Bjarne Stroustrup: The C++ Programming Language.
Grouping Definitions in Modules
To avoid name clashes and allow for a better API structure, UNOIDL supports naming scopes. The corresponding instruction is module:
module mymodule {
};
Instructions are only known inside the module mymodule for every type defined within the pair of braces of this module {}. Within each module, the type identifiers are unique. This makes an UNOIDL module similar to a Java package or a C++ namespace.
Modules may be nested. The following code shows the interface XUnoUrlResolver contained in the module bridge that is contained in the module star, which is in turn contained in the module sun of the module com.
module com { module sun { module star { module bridge {
// interface XUnoUrlResolver in module com::sun::star::bridge
}; }; }; };
It is customary to write module names in lower case letters. Use your own module hierarchy for your IDL types. To contribute code to OpenOffice.org, use the org::openoffice namespace or com::sun::star. Discuss the name choice with the leader of the API project on www.openoffice.org to add to the latter modules. The com::sun::star namespace mirrors the historical roots of OpenOffice.org in StarOffice and will probably be kept for compatibility purposes.
Types defined in UNOIDL modules have to be referenced using full-type or scoped names, that is, you must enter all modules your type is contained in and separate the modules by the scope operator ::. For instance, to reference XUnoUrlResolver in another idl definition file, write com::sun::star::bridge::XUnoUrlResolver.
Besides, modules have an advantage when it comes to generating language specific files. The tools cppumaker and javamaker automatically create subdirectories for every referenced module, if required. Headers and class definitions are kept in their own folders without any further effort.
One potential source of confusion is that UNOIDL and C++ use “::” to separate the individual identifiers within a name, whereas UNO itself (e.g., in methods like [IDL:com.sun.star.lang.XMultiComponentFactory:createInstanceWithContext]) and Java use “.”.
Simple Types
Before we can go about defining our first interface, you need to know the simple types you may use in your interface definition. You should already be familiar with the simple UNO types from the chapters [CHAPTER:FirstSteps] and [CHAPTER:ProfUNO]. Since we have to use them in idl definition files, we repeat the type keywords and their meaning here.
simple UNO type
Type description
char
16-bit unicode character type
boolean
boolean type; true and false
byte
8-bit ordinal integer type
short
signed 16-bit ordinal integer type
unsigned short
unsigned 16-bit ordinal integer type (deprecated)
long
signed 32-bit ordinal integer type
unsigned long
unsigned 32-bit integer type (deprecated)
hyper
signed 64-bit ordinal integer type
unsigned hyper
unsigned 64-bit ordinal integer type (deprecated)
float
processor dependent float
double
processor dependent double
string
string of 16-bit unicode characters
any
universal type, takes every simple or compound UNO type, similar to Variant in other environments or Object in Java
void
Indicates that a method does not provide a return value
Defining an Interface
Interfaces describe aspects of objects. To specify a new behavior for the component, start with an interface definition that comprises the methods offering the new behavior. Define a pair of plain get and set methods in a single step using the attribute instruction. Alternatively, choose to define your own operations with arbitrary arguments and exceptions by writing the method signature, and the exceptions the operation throws. We will first write a small interface definition with attribute instructions, then consider the resolve() method in XUNoUrlResolver.
Let us assume we want to contribute an ImageShrink component to OpenOffice.org to create thumbnail images for use in [PRODUCTNAME] tables. There is already a [IDL:com.sun.star.document.XFilter] interface offering methods supporting file conversion. In addition, a method is required to get and set the source and target directories, and the size of the thumbnails to create. It is common practice that a service and its prime interface have corresponding names, so our component shall have an org::openoffice::test::XImageShrink interface with methods to do so through get and set operations.
Attributes
The attribute instruction creates these operations for the experimental interface definition:
Look at the specification for our XImageShrink interface1
Perhaps in real life it would be better to define a more universal XBatchConverter interface for the source and target directories and derive XImageShrink from it. There are other options as well, but we want to keep things simple.: [SOURCE:Components/Thumbs/org/openoffice/test/XImageShrink.idl]
#ifndef __org_openoffice_test_XImageShrink_idl__
#define __org_openoffice_test_XImageShrink_idl__
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/awt/Size.idl>
module org { module openoffice { module test {
interface XImageShrink : com::sun::star::uno::XInterface
{
[attribute] string SourceDirectory;
[attribute] string DestinationDirectory;
[attribute] com::sun::star::awt::Size Dimension;
};
}; }; };
#endif
We protect the interface from being redefined using #ifndef, then added #include [IDL:com.sun.star.uno.XInterface] and the struct [IDL:com.sun.star.awt.Size]. These were found in the API reference using its global index. Our interface will be known in the org::openoffice::test module, so it is nested in the corresponding module instructions.
Define an interface using the interface instruction. It opens with the keyword interface, gives an interface name and derives the new interface from a parent interface (also called super interface). It then defines the interface body in braces. The interface instruction concludes with a semicolon.
In this case, the introduced interface is XImageShrink. By convention, all interface identifiers start with an X. Every interface must inherit from the base interface for all UNO interfaces XInterface or from one of its derived interfaces. The simple case of single inheritance is expressed by a colon : followed by the fully qualified name of the parent type. The fully qualified name of a UNOIDL type is its identifier, including all containing modules separated by the scope operator ::. Here we derive from com::sun::star::uno::XInterface directly. If you want to declare a new interface that inherits from multiple interfaces, you do not use the colon notation, but instead list all inherited interfaces within the body of the new interface:
interface XMultipleInheritance { interface XBase1;
interface XBase2;};
Pay attention to the following important text section
UNOIDL allows forward declaration of interfaces used as parameters, return values or struct members. However, an interface you want to derive from must be a fully defined interface.
After the super interface the interface body begins. It may contain attribute and method declarations, and, in the case of a multiple-inheritance interface, the declaration of inherited interfaces. Consider the interface body of XImageShrink. It contains three attributes and no methods. Interface methods are discussed below.
An attribute declaration opens with the keyword attribute in square brackets, then it gives a known type and an identifier for the attribute, and concludes with a semicolon.
In our example, the string attributes named SourceDirectory and DestinationDirectory and a com::sun::star::awt::Size attribute known as Dimension were defined:
[attribute] string SourceDirectory;
[attribute] string DestinationDirectory;
[attribute] com::sun::star::awt::Size Dimension;
During code generation in Java and C++, the attribute declaration leads to pairs of get and set methods. For instance, the Java interface generated by javamaker from this type description contains the following six methods:
// from attribute SourceDir
public String getSourceDirectory();
public void setSourceDirectory(String _sourcedir);
// from attribute DestinationDir
public String getDestinationDirectory();
public void setDestinationDirectory(String _destinationdir);
// from attribute Dimension
public com.sun.star.awt.Size getDimension();
public void setDimension(com.sun.star.awt.Size _dimension);
As an option, define that an attribute cannot be changed from the outside using a readonly flag. To set this flag, write [attribute, readonly]. The effect is that only a get() method is created during code generation, but not a set() method. Another option is to mark an attribute as bound; that flag is of interest when mapping interface attributes to properties, see [CHAPTER:Components.Java] and [CHAPTER:Components.Cpp].
Since [PRODUCTNAME] [OO2.0], there can be exception specifications for attributes, individually for the operations of getting and setting an attribute:
[attribute] long Age { get raises (DatabaseException); // raised when retrieving the age from the database fails
set raises (IllegalArgumentException, // raised when the new age is negative
DatabaseException); // raised when storing the new age in the database fails };
If no exception specification is given, only runtime exceptions may be thrown.
Methods
When writing a real component, define the methods by providing their signature and the exceptions they throw in the idl file. Our XUnoUrlResolver example above features a resolve() method taking a UNO URL and throwing three exceptions.
interface XUnoUrlResolver: com::sun::star::uno::XInterface
{
com::sun::star::uno::XInterface resolve( [in] string sUnoUrl )
raises (com::sun::star::connection::NoConnectException,
com::sun::star::connection::ConnectionSetupException,
com::sun::star::lang::IllegalArgumentException);
};
The basic structure of a method is similar to C++ functions or Java methods. The method is defined giving a known return type, the operation name, an argument list in brackets () and if necessary, a list of the exceptions the method may throw. The argument list, the exception clause raises() and an optional [oneway] flag preceding the operation are special in UNOIDL.
Each argument in the argument list must commence with one of the direction flags [in], [out] or [inout] before a known type and identifier for the argument is given. The direction flag specifies how the operation may use the argument:
Direction Flags for Methods
Description
in
Specifies that the method shall evaluate the argument as input parameter, but it cannot change it.
out
Specifies that the argument does not parameterize the method, instead the method uses the argument as output parameter.
inout
Specifies that the operation is parameterized by the argument and that the method uses the argument as output parameter as well.
Try to avoid the [inout] and [out] qualifiers, as they are awkward to handle in certain language bindings, like the Java language binding. The argument list can be empty. Multiple arguments must be separated by commas.
Exceptions are given through an optional raises() clause containing a comma-separated list of known exceptions given by their full name. The presence of a raises() clause means that only the listed exceptions, [IDL:com.sun.star.uno.RuntimeException] and their descendants may be thrown by the implementation. By specifying exceptions for metnods, the implementer of your interface can return information to the caller, thus avoiding possible error conditions.
If you prepend a [oneway] flag to an operation, the operation can be executed asynchronously if the underlying method invocation system does support this feature. For example, a UNO Remote Protocol (URP) bridge is such a system that supports oneway calls. A oneway operation can not have a return value, or out or inout parameters. It must not throw other exceptions than com.sun.star.uno.RuntimeException.
Pay attention to the following important text section
Although there are no general problems with the specification and the implementation of the UNO oneway feature, there are several API remote usage scenarios where oneway calls cause deadlocks in [PRODUCTNAME]. Therefore it is not recommended to introduce new oneway methods with new [PRODUCTNAME] UNO APIs.
Pay attention to the following important text section
You can not override an attribute or a method inherited from a parent interface, that would not make sense in an abstract specification anyway. Furthermore, overloading is not possible. The qualified interface identifier in conjunction with the name of the method creates a unique method name.
Defining a Service
UNOIDL Services combine interfaces and properties to specify a certain functionality. In addition, old-style services can include other services. For these purposes, interface, property and service declarations are used within service specifications. Usually services are the basis for an object implementation, although there are old-style services in the [PRODUCTNAME] API that only serve as foundation or addition to other services, but are not meant to be implemented by themselves2
The services [IDL:com.sun.star.text.BaseFrame] or [IDL:com.sun.star.style.CharacterProperties] are part of other services, but are not implemented as such anywhere..
We are ready to assemble our ImageShrink service. Our service will read image files from a source directory and write shrinked versions of the found images to a destination directory. Our XImageShrink interface offers the needed capabilities, together with the interface [IDL:com.sun.star.document.XFilter] that supports two methods:
boolean filter( [in] sequence< com::sun::star::beans::PropertyValue > aDescriptor)
void cancel()
A new-style service can only encompass one interface, so we need to combine XImageShrink and XFilter in a single, multiple-inheritance interface:
#ifndef __org_openoffice_test_XImageShrinkFilter_idl__#define __org_openoffice_test_XImageShrinkFilter_idl__
#include <com/sun/star/document/XFilter.idl>
#include <org/openoffice/test/XImageShrink.idl>
module org { module openoffice { module test {
interface XImageShrinkFilter {
interface XImageShrink;
interface com::sun::star::document::XFilter;
};
}; }; };
#endif
The following code shows the ImageShrink service specification: [SOURCE:Components/Thumbs/org/openoffice/test/ImageShrink.idl]
#ifndef __org_openoffice_test_ImageShrink_idl__
#define __org_openoffice_test_ImageShrink_idl__
#include <org/openoffice/test/XImageShrinkFilter.idl>
module org { module openoffice { module test {
service ImageShrink: XImageShrinkFilter;
}; }; };
#endif
Define a service using the service declaration. A new-style service opens with the keyword service, followed by a service name, a colon, the name of the interface supported by the service, and is terminated by a semicolon. The first letter of a service name should be an upper-case letter.
An old-style service is much more complex. It opens with the keyword service, followed by a service name and the service body in braces. The service instruction concludes with a semicolon. The body of a service can reference interfaces and services using interface and service instructions, and it can identify properties supported by the service through [property] instructions.
Interface keywords followed by interface names in a service body indicates that the service supports these interfaces. By default, the interface forces the developer to implement this interface. To suggest an interface for a certain service, prepend an [optional] flag in front of the keyword interface. This weakens the specification to a permission. An optional interface can be implemented. Use one interface declaration for each supported interface or give a comma-separated list of interfaces to be exported by a service. You must terminate the interface instruction using a semicolon.
service instructions in a service body include other services. The effect is that all interface and property definitions of the other services become part of the current service. A service reference can be optional using the [optional] flag in front of the service keyword. Use one declaration per service or a comma-separated list for the services to reference. The service declaration ends with a semicolon.
[property] declaration s describe qualities of a service that can be reached from the outside under a particular name and type. As opposed to interface attributes, these qualities are not considered to be a structural part of a service. Refer to the section [CHAPTER:ProfUNO.UNOConcepts.Properties] in the chapter [CHAPTER:ProfUNO] to determine when to use interface attributes and when to introduce properties in a service . The property keyword must be enclosed in square brackets, and continue with a known type and a property identifier. Just like a service and an interface, make a property non-mandatory writing [property, optional]. Besides optional,there is a number of other flags to use with properties. The following table shows all flags that can be used with [property]:
Property Flags
Description
optional
Property is non-mandatory.
readonly
The value of the property cannot be changed using the setter methods for properties, such as setPropertyValue(string name).
bound
Changes of values are broadcast to [IDL:com.sun.star.beans.XPropertyChangeListener]s registered with the component.
constrained
The component must broadcast an event before a value changes, listeners can veto.
maybeambiguous
The value cannot be determined in some cases, for example, in multiple selections.
maybedefault
The value might come from a style or the application environment instead of from the object itself.
maybevoid
The property type determines the range of possible values, but sometimes there may be situations where there is no information available. Instead of defining special values for each type denoting that there are no meaningful values, the UNO type void can be used. Its meaning is comparable to null in relational databases.
removable
The property is removable. If a property is made removable, you must check for the existence of a property using hasPropertyByName() at the interface [IDL:com.sun.star.beans.XPropertySetInfo] and consider providing the capability to add or remove properties using [IDL:com.sun.star.beans.XPropertyContainer].
transient
The property will not be stored if the object is serialized (made persistent).
Several properties of the same type can be listed in one property declaration. Remember to add a semicolon at the end. Implement the interface [IDL:com.sun.star.beans.XPropertySet] when putting properties in your service, otherwise the properties specified will not work for others using the component.
Note graphics marks a special text section
Some old-style services, which specify no interfaces at all, only properties, are used as a sequence of [IDL:com.sun.star.beans.PropertyValue] in [PRODUCTNAME], for example, [IDL:com.sun.star.document.MediaDescriptor].
The following UNOIDL snippet shows the service, the interfaces and the properties supported by the old-style service [IDL:com.sun.star.text.TextDocument] as defined in UNOIDL. Note the optional interfaces and the optional and read-only properties.
service TextDocument
{
service com::sun::star::document::OfficeDocument;
interface com::sun::star::text::XTextDocument;
interface com::sun::star::util::XSearchable;
interface com::sun::star::util::XRefreshable;
interface com::sun::star::util::XNumberFormatsSupplier;
[optional] interface com::sun::star::text::XFootnotesSupplier;
[optional] interface com::sun::star::text::XEndnotesSupplier;
[optional] interface com::sun::star::util::XReplaceable;
[optional] interface com::sun::star::text::XPagePrintable;
[optional] interface com::sun::star::text::XReferenceMarksSupplier;
[optional] interface com::sun::star::text::XLineNumberingSupplier;
[optional] interface com::sun::star::text::XChapterNumberingSupplier;
[optional] interface com::sun::star::beans::XPropertySet;
[optional] interface com::sun::star::text::XTextGraphicObjectsSupplier;
[optional] interface com::sun::star::text::XTextEmbeddedObjectsSupplier;
[optional] interface com::sun::star::text::XTextTablesSupplier;
[optional] interface com::sun::star::style::XStyleFamiliesSupplier;
[optional, property] com::sun::star::lang::Locale CharLocale;
[optional, property] string WordSeparator;
[optional, readonly, property] long CharacterCount;
[optional, readonly, property] long ParagraphCount;
[optional, readonly, property] long WordCount;
};
Pay attention to the following important text section
You might encounter two more keywords in old-style service bodies. The keyword observes can stand in front of interface references and means that the given interfaces must be "observed". Since the observes concept is disapproved of, no further explanation is provided.
If a service references another service using the keyword needs in front of the reference, then this service depends on the availability of the needed service at runtime. Services should not use needs as it is considered too implementation specific.
Defining a Sequence
A sequence in UNOIDL is an array containing a variable number of elements of the same UNOIDL type. The following is an example of a sequence term:
// this term could occur in a UNOIDL definition block somewhere
sequence< com::sun::star::uno::XInterface >
It starts with the keyword sequence and gives the element type enclosed in angle brackets <>. The element type must be a known type. A sequence type can be used as parameter, return value, property or struct member just like any other type. Sequences can also be nested, if necessary.
// this could be a nested sequence definition
sequence< sequence< long > >
// this could be an operation using sequences in some interface definition
sequence< string > getNamesOfIndex(sequence< long > indexes);
Defining a Struct
A struct is a compound type which puts together arbitrary UNOIDL types to form a new data type. Its member data are not encapsulated, rather they are publicly available. Structs are frequently used to handle related data easily, and the event structs broadcast to event listeners.
A plain struct instruction opens with the keyword struct, gives an identifier for the new struct type and has a struct body in braces. It is terminated by a semicolon. The struct body contains a list of struct member declarations that are defined by a known type and an identifier for the struct member. The member declarations must end with a semicolon, as well.
#ifndef __com_sun_star_reflection_ParamInfo_idl__
#define __com_sun_star_reflection_ParamInfo_idl__
#include <com/sun/star/reflection/ParamMode.idl>
module com { module sun { module star { module reflection {
interface XIdlClass; // forward interface declaration
struct ParamInfo
{
string aName;
ParamMode aMode;
XIdlClass aType;
};
}; }; }; };
#endif
UNOIDL supports inheritance of struct types. Inheritance is expressed by a colon : followed by the full name of the parent type. A struct type recursively inherits all members of the parent struct and their parents. For instance, derive from the struct [IDL:com.sun.star.lang.EventObject] to put additional information about new events into customized event objects to send to event listeners.
// com.sun.star.beans.PropertyChangeEvent inherits from com.sun.star.lang.EventObject
// and adds property-related information to the event object
struct PropertyChangeEvent : com::sun::star::lang::EventObject
{
string PropertyName;
boolean Further;
long PropertyHandle;
any OldValue;
any NewValue;
};
A new feature of [PRODUCTNAME] [OO2.0] are polymorphic struct types. A polymorphic struct type template is similar to a plain struct type, but it has one or more type parameters enclosed in angle brackets <>, and its members can have these parameters as types:
// A polymorphic struct type template with two type parameters:struct Poly<T,U> {
T member1;
T member2;
U member3;
long member4;};
A polymorphic struct type template is not itself a UNO type—it has to be instantiated with actual type arguments to be used as a type:
// Using an instantiation of Poly as a type in UNOIDL:interface XIfc { Poly<boolean, any> fn(); };
Defining an Exception
An exception type is a type that contains information about an error. If an operation detects an error that halts the normal process flow, it must raise an exception and send information about the error back to the caller through an exception object. This causes the caller to interrupt its normal program flow as well and react according to the information received in the exception object. For details about exceptions and their implementation, refer to the chapters [CHAPTER:ProfUNO.LangBind] and [CHAPTER:ProfUNO.UNOConcepts.Exceptions].
There are a number of exceptions to use. The exceptions should be sufficient in many cases, because a message string can be sent back to the caller. When defining an exception, do it in such a way that other developers could reuse it in their contexts.
An exception declaration opens with the keyword exception, gives an identifier for the new exception type and has an exception body in braces. It is terminated by a semicolon. The exception body contains a list of exception member declarations that are defined by a known type and an identifier for the exception member. The member declarations must end with a semicolon, as well.
Exceptions must be based on [IDL:com.sun.star.uno.Exception] or [IDL:com.sun.star.uno.RuntimeException], directly or indirectly through derived exceptions of these two exceptions. [IDL:com.sun.star.uno.Exception]s can only be thrown in operations specified to raise them while [IDL:com.sun.star.uno.RuntimeException]s can always occur. Inheritance is expressed by a colon :, followed by the full name of the parent type.
// com.sun.star.uno.Exception is the base exception for all exceptions
exception Exception {
string Message;
XInterface Context;
};
// com.sun.star.lang.IllegalArgumentException tells the caller which
// argument caused trouble
exception IllegalArgumentException: com::sun::star::uno::Exception
{
/** identifies the position of the illegal argument.
<p>This field is -1 if the position is not known.</p>
*/
short ArgumentPosition;
};
// com.sun.star.uno.RuntimeException is the base exception for serious errors
// usually caused by programming errors or problems with the runtime environment
exception RuntimeException : com::sun::star::uno::Exception {
};
// com.sun.star.uno.SecurityException is a more specific RuntimeException
exception SecurityException : com::sun::star::uno::RuntimeException {
};
Predefining Values
Predefined values can be provided, so that implementers do not have to use cryptic numbers or other literal values. There are two kinds of predefined values, constants and enums. Constants can contain values of any basic UNO type, except void. The enums are automatically numbered long values.
Const and Constants
The constants type is a container for const types. A constants instruction opens with the keyword constants, gives an identifier for the new group of const values and has the body in braces. It terminates with a semicolon. The constants body contains a list of const definitions that define the values of the members starting with the keyword const followed by a known type name and the identifier for the const in uppercase letters. Each const definition must assign a value to the const using an equals sign. The value must match the given type and can be an integer or floating point number, or a character, or a suitable const value or an arithmetic term based on the operators in the table below. The const definitions must end with a semicolon, as well.
#ifndef __com_sun_star_awt_FontWeight_idl__
#define __com_sun_star_awt_FontWeight_idl__
module com { module sun { module star { module awt {
constants FontWeight
{
const float DONTKNOW = 0.000000;
const float THIN = 50.000000;
const float ULTRALIGHT = 60.000000;
const float LIGHT = 75.000000;
const float SEMILIGHT = 90.000000;
const float NORMAL = 100.000000;
const float SEMIBOLD = 110.000000;
const float BOLD = 150.000000;
const float ULTRABOLD = 175.000000;
const float BLACK = 200.000000;
};
}; }; }; };
Operators Allowed in const
Meaning
+
addition
-
subtraction
*
multiplication
/
division
%
modulo division
-
negative sign
+
positive sign
|
bitwise or
^
bitwise xor
&
bitwise and
~
bitwise not
>> <<
bitwise shift right, shift left
Tip graphics marks a hint section in the text
Use constants to group const types. In the Java language, binding a constants group leads to one class for all const members, whereas a single const is mapped to an entire class.
Enum
An enum type holds a group of predefined long values and maps them to meaningful symbols. It is equivalent to the enumeration type in C++. An enum instruction opens with the keyword enum, gives an identifier for the new group of enum values and has an enum body in braces. It terminates with a semicolon. The enum body contains a comma-separated list of symbols in uppercase letters that are automatically mapped to long values counting from zero, by default.
#ifndef __com_sun_star_style_ParagraphAdjust_idl__
#define __com_sun_star_style_ParagraphAdjust_idl__
module com { module sun { module star { module style {
enum ParagraphAdjust
{
LEFT,
RIGHT,
BLOCK,
CENTER,
STRETCH
};
}; }; }; };
#endif
In this example, [IDL:com.sun.star.style.ParagraphAdjust:LEFT] corresponds to 0, ParagraphAdjust.RIGHT corresponds to 1 and so forth.
An enum member can also be set to a long value using the equals sign. All the following enum values are then incremented starting from this value. If there is another assignment later in the code, the counting starts with that assignment:
enum Error {
SYSTEM = 10, // value 10
RUNTIME, // value 11
FATAL, // value 12
USER = 30, // value 30
SOFT // value 31
};
Pay attention to the following important text section
The explicit use of enum values is deprecated and should not be used. It is a historical characteristic of the enum type but it makes not really sense and makes, for example language bindings unnecessarily complicated.
Using Comments
Comments are code sections ignored by idlc. In UNOIDL, use C++ style comments. A double slash // marks the rest of the line as comment. Text enclosed between /* and */ is a comment that may span over multiple lines.
service ImageShrink
{
// the following lines define interfaces:
interface org::openoffice::test::XImageShrink; // our home-grown interface
interface com::sun::star::document::XFilter;
/* we could reference other interfaces, services and properties here.
However, the keywords uses and needs are deprecated
*/
};
Based on the above, there are documentation comments that are extracted when idl files are processed with autodoc, the UNOIDL documentation generator. Instead of writing /* or //to mark a plain comment, write /** or /// to create a documentation comment.
/** Don't repeat asterisks within multiple line comments,
* <- as shown here
*/
/// Don't write multiple line documentation comments using triple slashes,
/// since only this last line will make it into the documentation
Our XUnoUrlResolver sample idl file contains plain comments and documentation comments.
/** service <type scope="com::sun::star::bridge">UnoUrlResolver</type>
implements this interface.
*/
interface XUnoUrlResolver: com::sun::star::uno::XInterface
{
// method com::sun::star::bridge::XUnoUrlResolver::resolve
/** resolves an object, on the UNO URL.
*/
...
}
Note the additional <type/> tag in the documentation comment pointing out that the service UnoUrlResolver implements the interface XUnoUrlResolver. This tag becomes a hyperlink in HTML documentation generated from this file. The chapter [CHAPTER:API.Documentation] provides a comprehensive description for UNOIDL documentation comments.
Singleton
A singleton declaration defines a global name for a UNO object and determines that there can only be one instance of this object that must be reachable under this name. The singleton instance can be retrieved from the component context using the name of the singleton. If the singleton has not been instantiated yet, the component context creates it. A new-style singleton declaration, that binds a singleton name to an object with a certain interface type, looks like this:
singleton thePackageManagerFactory: com::sun::star::depoyment::XPackageManager;
There are also old-style singletons, which reference (old-style) services instead of interfaces.
Reserved Types
There are types in UNOIDL which are reserved for future use. The idlc will refuse to compile the specifications if they are tried.
Array
The keyword array is reserved, but it cannot be used in UNOIDL. There will be sets containing a fixed number of elements, as opposed to sequences, that can have an arbitrary number of elements.
Union
There is also a reserved keyword for union types that cannot be used in UNOIDL. A union will look at a variable value from more than one perspective. For instance, a union for a long value is defined and this same value is accessed as a whole, or accessed by its high and low part separately through a union.
Published Entities
A new feature of [PRODUCTNAME] [OO2.0] is the UNOIDL published keyword. If you mark a declaration (of a struct, interface, service, etc.) as published, you give the guarantee that you will not change the declaration in the future, so that clients of your API can depend on that. On the other hand, leaving a declaration unpublished is like a warning to your clients that the declared entity may change or even vanish in a future version of your API. The idlc will give an error if you try to use an unpublished entity in the declaration of a published one, as that would not make sense.
The [PRODUCTNAME] API has always been intended to never change in incompatible ways. This is now reflected formally by publishing all those entities of the [PRODUCTNAME] [OO2.0] API that were already available in previous API versions. Some new additions to the API have been left unpublished, however, to document that they are probably not yet in their final form. When using such additions, keep in mind that you might need to adapt your code to work with future versions of [PRODUCTNAME]. Generally, each part of the [PRODUCTNAME] API should stabilize over time, however, and so each addition should eventually be published. Consider this as a means in attempting to make new functionality available as early as possible, and at the same time ensure that no APIs are fixed prematurely, before they have matured to a truly useful form.
Generating Source Code from UNOIDL Definitions
The type description provided in .idl files is used in the subsequent process to create type information for the service manager and to generate header and class files. Processing the UNOIDL definitions is a three-step process.
Compile the .idl files using idlc. The result are .urd files (UNO reflection data) containing binary type descriptions.
Merge the .urd files into a registry database using regmerge. The registry database files have the extension .rdb (registry database). They contain binary data describing types in a tree-like structure starting with / as the root. The default key for type descriptions is the /UCR key (UNO core reflection).
Generate sources from registry files using javamaker or cppumaker. The tools javamaker and cppumaker map UNOIDL types to Java and C++ as described in the chapter [CHAPTER:ProfUNO.LangBind]. The registries used by these tools must contain all types to map to the programming language used, including all types referenced in the type descriptions. Therefore, javamaker and cppumaker need the registry that was merged, but the entire office registry as well. [PRODUCTNAME] comes with a complete registry database providing all types used by UNO at runtime. The SDK uses the database (type library) of an existing [PRODUCTNAME] installation.
The following shows the necessary commands to create Java class files and C++ headers from .idl files in a simple setup under Linux. We assume the jars from <OFFICE_PROGRAM_PATH>/classes have been added to your CLASSPATH, the SDK is installed in /home/sdk, and /home/sdk/linux/bin is in the PATH environment variable, so that the UNO tools can be run directly. The project folder is /home/sdk/Thumbs and it contains the above .idl file XImageShrink.idl.
# make project folder the current directory
cd /home/sdk/Thumbs
# compile XImageShrink.idl using idlc
# usage: idlc [-options] file_1.idl ... file_n.idl
# -C adds complete type information including services
# -I includepath tells idlc where to look for include files
#
# idlc writes the resulting urds to the current folder by default
idlc -C -I../idl XImageShrink.idl
# create registry database (.rdb) file from UNO registry data (.urd) using regmerge
# usage: regmerge mergefile.rdb mergeKey regfile_1.urd ... regfile_n.urd
# mergeKey entry in the tree-like rdb structure where types from .urd should be recorded, the tree
# starts with the root / and UCR is the default key for type descriptions
#
# regmerge writes the rdb to the current folder by default
regmerge thumbs.rdb /UCR XImageShrink.urd
# generate Java class files for new types from rdb
# -B base node to look for types, in this case UCR
# -T type to generate Java files for
# -nD do not generate sources for dependent types, they are available in the Java UNO jar files
#
# javamaker creates a directory tree for the output files according to
# the modules the given types were placed in. The tree is created in the current folder by default
javamaker -BUCR -Torg.openoffice.test.XImageShrink -nD <OFFICE_PROGRAM_PATH>/types.rdb thumbs.rdb
# generate C++ header files (hpp and hdl) for new types and their dependencies from rdb
# -B base node to look for types, in this case UCR
# -T type to generate Java files for
#
# cppumaker creates a directory tree for the output files according to
# the modules the given types were placed in. The tree is created in the current folder by default
cppumaker -BUCR -Torg.openoffice.test.XImageShrink <OFFICE_PROGRAM_PATH>/types.rdb thumbs.rdb
After issuing these commands you have a registry database thumbs.rdb and a Java class file XImageShrink.class. (In versions of [PRODUCTNAME] prior to [OO2.0], javamaker produced Java source files instead of class files; you therefore had to call javac on the source files in an additional step.) You can run regview against thumbs.rdb to see what regmerge has accomplished.
regview thumbs.rdb
The result for our interface XImageShrink looks like this:
Registry "file:///home/sdk/Thumbs/thumbs.rdb":
/
/ UCR
/ org
/ openoffice
/ test
/ XImageShrink
Value: Type = RG_VALUETYPE_BINARY
Size = 316
Data = minor version: 0
major version: 1
type: 'interface'
uik: { 0x00000000-0x0000-0x0000-0x00000000-0x00000000 }
name: 'org/openoffice/test/XImageShrink'
super name: 'com/sun/star/uno/XInterface'
Doku: ""
IDL source file: "/home/sdk/Thumbs/XImageShrink.idl"
number of fields: 3
field #0:
name='SourceDirectory'
type='string'
access=READWRITE
Doku: ""
IDL source file: ""
field #1:
name='DestinationDirectory'
type='string'
access=READWRITE
Doku: ""
IDL source file: ""
field #2:
name='Dimension'
type='com/sun/star/awt/Size'
access=READWRITE
Doku: ""
IDL source file: ""
number of methods: 0
number of references: 0
Source generation can be fully automated with makefiles. For details, see the sections [CHAPTER:Components.Java.TestDebug] and [CHAPTER:Components.Cpp.TestDebug] below. You are now ready to implement your own types and interfaces in a UNO component. The next section discusses the UNO core interfaces to implement in UNO components.
Component Architecture
UNO components are archive files or dynamic link libraries with the ability to instantiate objects which can integrate themselves into the UNO environment. For this purpose, components must contain certain static methods (Java) or export functions (C++) to be called by a UNO service manager. In the following, these methods are called component operations.
There must be a method to supply single-service factories for each object implemented in the component. Through this method, the service manager can get a single factory for a specific object and ask the factory to create the object contained in the component. Furthermore, there has to be a method which writes registration information about the component, which is used when a component is registered with the service manager. In C++, an additional function is necessary that informs the component loader about the compiler used to build the component.
The component operations are always necessary in components and they are language specific. Later, when Java and C++ are discussed, we will show how to write them.
Overview graphic of an UNO component
Illustration 4.1: A Component implementing three UNO objects
The illustration shows a component which contains three implemented objects. Two of them, srv1 and srv2 implement a single service specification (Service1 and Service2), whereas srv3_4 supports two services at once (Service3 and Service4).
The objects implemented in a component must support a number of core UNO interfaces to be fully usable from all parts of the [PRODUCTNAME] application. These core interfaces are discussed in the next section. The individual functionality of the objects is covered by the additional interfaces they export. Usually these interfaces are enclosed in a service specification.
Core Interfaces to Implement
[TOPIC:com.sun.star.uno.XInterface;com.sun.star.lang.XTypeProvider;com.sun.star.lang.XServiceInfo;com.sun.star.uno.XWeak;com.sun.star.lang.XComponent;com.sun.star.lang.XInitialization;com.sun.star.lang.XMain;com.sun.star.uno.XAggregation;com.sun.star.lang.XUnoTunnel]It is important to know where the interfaces to implement are located. The interfaces here are located at the object implementations in the component. When writing UNO components, the desired methods have to be implemented into the application and also, the core interfaces used to enable communication with the UNO environment. Some of them are mandatory, but there are others to choose from.
Interface
Required
Should be implemented
Optional
Special Cases
Helper class available for C++ and Java
XInterface
●
●
XTypeProvider
●
●
XServiceInfo
●
XWeak
●
●
XComponent
●
●
XInitialization
●
XMain
●
XAggregation
●
XUnoTunnel
●
The interfaces listed in the table above have been characterized here briefly. More descriptions of each interface are provided later, as well as if helpers are available and which conditions apply.
[IDL:com.sun.star.uno.XInterface]
The component will not work without it. The base interface XInterface gives access to higher interfaces of the service and allows other objects to tell the service when it is no longer needed, so that it can destroy itself.
// com::sun::star::uno::XInterface
any queryInterface( [in] type aType );
[oneway] void acquire(); // increase reference counter in your service implementation
[oneway] void release(); // decrease reference counter, delete object when counter becomes zero
Usually developers do not call acquire() explicitly, because it is called automatically by the language bindings when a reference to a component is retrieved through UnoRuntime.queryInterface() or Reference<destInterface>(sourceInterface, UNO_QUERY) . The counterpart release() is called automatically when the reference goes out of scope in C++ or when the Java garbage collector throws away the object holding the reference.
[IDL:com.sun.star.lang.XTypeProvider]
This interface is used by scripting languages such as [PRODUCTNAME] Basic to get type information. [PRODUCTNAME] Basic cannot use the component without it.
// com::sun::star::lang::XTypeProvider
sequence<type> getTypes();
sequence<byte> getImplementationId();
It is possible that XTypeProvider and XServiceInfo (below) will be deprecated in the future, and that alternative, language-binding–specific mechanisms will be made available to query an object for its characteristics.
[IDL:com.sun.star.lang.XServiceInfo]
This interface is used by other objects to get information about the service implementation.
// com::sun::star::lang::XServiceInfo
string getImplementationName();
boolean supportsService( [in] string ServiceName );
sequence<string> getSupportedServiceNames();
[IDL:com.sun.star.uno.XWeak]
This interface allows clients to keep a weak reference to the object. A weak reference does not prevent the object from being destroyed if another client keeps a hard reference to it, therefore it allows a hard reference to be retrieved again. The technique is used to avoid cyclic references. Even if the interface is not required by you, it could be implemented for a client that may want to establish a weak reference to an instance of your object.
// com.sun.star.uno.XWeak
com::sun::star::uno::XAdapter queryAdapter(); // creates Adapter
[IDL:com.sun.star.lang.XComponent]
This interface is used if cyclic references can occur in the component holding another object and the other object is holding a reference to that component. It can be specified in the service description who shall destroy the object.
// com::sun::star::lang::XComponent
void dispose(); //an object owning your component may order it to delete itself using dispose()
void addEventListener(com::sun::star::lang::XEventListener xListener); // add dispose listeners
void removeEventListener (com::sun::star::lang::XEventListener aListener); // remove them
[IDL:com.sun.star.lang.XInitialization]
This interface is used to allow other objects to use createInstanceWithArguments() or createInstanceWithArgumentsAndContext() with the component. It should be implemented and the arguments processed in initialize():
// com::sun::star::lang::XInitialization
void initialize(sequence< any > aArguments) raises (com::sun::star::uno::Exception);
[IDL:com.sun.star.lang.XMain]
This interface is for use with the uno executable to instantiate the component independently from the [PRODUCTNAME] service manager.
// com.sun.star.lang.XMain
long run (sequence< string > aArguments);
[IDL:com.sun.star.uno.XAggregation]
This interfaces makes the implementation cooperate in an aggregation. If implemented, other objects can aggregate to the implementation. Aggregated objects behave as if they were one. If another object aggregates the component, it holds the component and delegates calls to it, so that the component seems to be one with the aggregating object.
// com.sun.star.uno.XAggregation
void setDelegator(com.sun.star.uno.XInterface pDelegator);
any queryAggregation(type aType);
[IDL:com.sun.star.lang.XUnoTunnel]
This interface provides a pointer to the component to another component in the same process. This can be achieved with XUnoTunnel. XUnoTunnel should not be used by new components, because it is to be used for integration of existing implementations, if all else fails.
By now you should be able to decide which interfaces are interesting in your case. Sometimes the decision for or against an interface depends on the necessary effort as well. The following section discusses for each of the above interfaces how you can take advantage of pre-implemented helper classes in Java or C++, and what must happen in a possible implementation, no matter which language is used.
XInterface
[TOPIC:com.sun.star.uno.XInterface]All service implementations must implement [IDL:com.sun.star.uno.XInterface]. If a Java component is derived from a Java helper class that comes with the SDK, it supports XInterface automatically. Otherwise, it is sufficient to add XInterface or any other UNO interface to the implements list. The Java UNO runtime takes care of XInterface. In C++, there are helper classes to inherit that already implement XInterface. However, if XInterface is to be implemented manually, consider the code below.
The IDL specification for [IDL:com.sun.star.uno.XInterface] looks like this:
// module com::sun::star::uno
interface XInterface
{
any queryInterface( [in] type aType );
[oneway] void acquire();
[oneway] void release();
};
Requirements for queryInterface()
When queryInterface() is called, the caller asks the implementation if it supports the interface specified by the type argument. The UNOIDL base type stores the name of a type and its [IDL:com.sun.star.uno.TypeClass]. The call must return an interface reference of the requested type if it is available or a void any if it is not. There are certain conditions a queryInterface() implementation must meet:
Constant Behaviour
If queryInterface() on a specific object has once returned a valid interface reference for a given type, it must always return a valid reference for any subsequent queryInterface() call for the same type on this object. A query for XInterface must always return the same reference.
If queryInterface() on a specific object has once returned a void any for a given type, it must always return a void any for the same type.
Symmetry
If queryInterface() for XBar on a reference xFoo returns a reference xBar, then queryInterface() on reference xBar for type XFoo must return xFoo or calls made on the returned reference must be equivalent to calls to xFoo.
Object Identity
In C++, two objects are the same if their XInterface are the same. The queryInterface() for XInterface will have to be called on both. In Java, check for the identity by calling the runtime function com.sun.star.uni.UnoRuntime.areSame().
The reason for this specifications is that a UNO runtime environment may choose to cache queryInterface() calls. The rules are identical to the rules of the function QueryInterface() in MS COM.
Tip graphics marks a hint section in the text
If you want to implement queryInterface() in Java, for example, you want to export less interfaces than you implement, your class must implement the Java interface com.sun.star.uno.IQueryInterface.
Reference Counting
The methods acquire() and release() handle the lifetime of the UNO object. This is discussed in detail in chapter [CHAPTER:ProfUNO.UNOConcepts.Lifetime]. Acquire and release must be implemented in a thread-safe fashion. This is demonstrated in C++ in the section about C++ components below.
XTypeProvider
[TOPIC:com.sun.star.lang.XTypeProvider]Every UNO object should implement the [IDL:com.sun.star.lang.XTypeProvider] interface.
Some applications need to know which interfaces an UNO object supports, for example, the [PRODUCTNAME] Basic engine or debugging tools, such as the InstanceInspector. The [IDL:com.sun.star.lang.XTypeProvider] interface was introduced to avoid going through all known interfaces calling queryInterface() repetitively. The XTypeProvider interface is implemented by Java and C++ helper classes. If the XTypeProvider must be implemented manually, use the following methods:
// module com::sun::star::lang
interface XTypeProvider: com::sun::star::uno::XInterface
{
sequence<type> getTypes();
sequence<byte> getImplementationId();
};
The sections about Java and C++ components below show examples of XTypeProvider implementations.
Provided Types
The [IDL:com.sun.star.lang.XTypeProvider:getTypes]() method must return a list of types for all interfaces that queryInterface() provides. The [PRODUCTNAME] Basic engine depends on this information to establish a list of method signatures that can be used with an object.
ImplementationID
For caching purposes, the getImplementationId() method has been introduced. The method must return a byte array containing an identifier for the implemented set of interfaces in this implementation class. It is important that one ID maps to one set of interfaces, but one set of interfaces can be known under multiple IDs. Every implementation class should generate a static ID.
XServiceInfo
[TOPIC:com.sun.star.lang.XServiceInfo]Every service implementation should export the [IDL:com.sun.star.lang.XServiceInfo] interface. XServiceInfo must be implemented manually, because only the programmer knows what services the implementation supports. The sections about Java and C++ components below show examples for XServiceInfo implementations.
This is how the IDL specification for XServiceInfo looks like:
// module com::sun::star::lang
interface XServiceInfo: com::sun::star::uno::XInterface
{
string getImplementationName();
boolean supportsService( [in] string ServiceName );
sequence<string> getSupportedServiceNames();
};
Implementation Name
The method getImplementationName() provides access to the implementation name of a service implementation. The implementation name uniquely identifies one implementation of service specifications in a UNO object. The name can be chosen freely by the implementation alone, because it does not appear in IDL. However, the implementation should adhere to the following naming conventions:
company prefix
dot
"comp"
dot
module name
dot
unique object name in module
implemented service(s)
com.sun.star
.
comp
.
forms
.
ODataBaseForm
com.sun.star.forms.DataBaseForm
org.openoffice
.
comp
.
test
.
OThumbs
org.openoffice.test.ImageShrinkorg.openoffice.test.ThumbnailInsert...
If an object implements one single service, it can use the service name to derive an implementation name. Implementations of several services should use a name that describes the entire object.
If a createInstance() is called at the service manager using an implementation name, an instance of exactly that implementation is received. An implementation name is equivalent to a class name in Java. A Java component simply returns the fully qualified class name in getImplementationName().
Tip graphics marks a hint section in the text
It is good practice to program against the specification and not against the implementation, otherwise, your application could break with future versions. [PRODUCTNAME]s API implementation is not supposed to be compatible, only the specification is.
Supported Service Names
The methods getSupportedServiceNames() and supportsService() deal with the availability of services in an implemented object. Note that the supported services are the services implemented in one class that supports these services, not the services of all implementations contained in the component file. If the illustration 4.1: A Component implementing three UNO objects, XServiceInfo is exported by the implemented objects in a component, not by the component. That means, srv3_4 must support XServiceInfo and return "Service3" and "Service4" as supported service names.
The service name identifies a service as it was specified in IDL. If an object is instantiated at the service manager using the service name, an object that complies to the service specification is returned.
Note graphics marks a special text section
The single service factories returned by components that are used to create instances of an implementation through their interfaces [IDL:com.sun.star.lang.XSingleComponentFactory] or [IDL:com.sun.star.lang.XSingleServiceFactory] must support XServiceInfo. The single factories support this interface to allow UNO to inspect the capabilities of a certain implementation before instantiating it. You can take advantage of this feature through the [IDL:com.sun.star.container.XContentEnumerationAccess] interface of a service manager.
XWeak
[TOPIC:com.sun.star.uno.XWeak]A component supporting XWeak offers other objects to hold a reference on itself without preventing it from being destroyed when it is no longer needed. Thus, cyclic references can be avoided easily. The chapter [CHAPTER:ProfUNO.UNOConcepts.Lifetime] discusses this in detail. In Java, derive from the Java helper class com.sun.star.lib.uno.helper.WeakBase to support XWeak. If a C++ component is derived from one of the ::cppu::Weak...ImplHelperNN template classes as proposed in the section [CHAPTER:Components.Cpp], a XWeak support is obtained, virtually for free. For the sake of completeness, this is the XWeak specification:
// module com::sun::star::uno::XWeak
interface XWeak: com::sun::star::uno::XInterface
{
com::sun::star::uno::XAdapter queryAdapter();
};
XComponent
[TOPIC:com.sun.star.lang.XComponent]If the implementation holds a reference to another UNO object internally, there may be a problem of cyclic references that might prevent your component and the other object from being destroyed forever. If it is probable that the other object may hold a reference to your component, implement [IDL:com.sun.star.lang.XComponent] that contains a method dispose(). Chapter [CHAPTER:ProfUNO.UNOConcepts.Lifetime] discusses the intricacies of this issue.
Supporting XComponent in a C++ or Java component is simple, because there are helper classes to derive from that implement XComponent. The following code is an example if you must implement XComponent manually.
The interface XComponent specifies these operations:
// module com::sun::star::lang
interface XComponent: com::sun::star::uno::XInterface
{
void dispose();
void addEventListener( [in] XEventListener xListener );
void removeEventListener( [in] XEventListener aListener );
};
XComponent uses the interface [IDL:com.sun.star.lang.XEventListener]:
// module com::sun::star::lang
interface XEventListener: com::sun::star::uno::XInterface
{
void disposing( [in] com::sun::star::lang::EventObject Source );
};
Disposing of an XComponent
The idea behind XComponent is that the object is instantiated by a third object that makes the third object the owner of first object. The owner is allowed to call dispose(). When the owner calls dispose() at your object, it must do three things:
Release all references it holds.
Inform registered XEventListeners that it is being disposed of by calling their method disposing().
Behave as passive as possible afterwards. If the implementation is called after being disposed, throw a [IDL:com.sun.star.lang.DisposedException] if you cannot fulfill the method specification.
That way the owner of XComponent objects can dissolve a possible cyclic reference.
XInitialization
[TOPIC:com.sun.star.lang.XInitialization]The interface [IDL:com.sun.star.lang.XInitialization] is usually implemented manually, because only the programmer knows how to initialize the object with arguments received from the service manager through createInstanceWithArguments() or createInstanceWithArgumentsAndContext(). In Java, XInitialization is used as well, but know that the Java factory helper provides a shortcut that uses arguments without implementing XInitialization directly. The Java factory helper can pass arguments to the class constructor under certain conditions. Refer to the section [CHAPTER:Components.Java.Init] for more information.
The specification for XInitialization looks like this:
// module com::sun::star::lang
interface XInitialization : com::sun::star::uno::XInterface
{
void initialize(sequence< any > aArguments) raises (com::sun::star::uno::Exception);
};
An old-style UNOIDL service specification will typically specify which arguments and in which order are expected within the any sequence.
With the advent of new-style service specifications with explicit constructors, you can now declare explicitly what arguments can be passed to an object when creating it. The arguments listed in a constructor are exactly the arguments passed to XInitialization.initialize (the various language bindings currently use XInitialization internally to implement service constructors; that may change in the future, however).
XMain
[TOPIC:com.sun.star.lang.XMain]The implementation of [IDL:com.sun.star.lang.XMain] is used for special cases. Its run() operation is called by the uno executable. The section [CHAPTER:Components.UNOExe] below discusses the use of XMain and the uno executable in detail.
// module com::sun::star::lang
interface XMain: com::sun::star::uno::XInterface{ long run( [in] sequence< string > aArguments ); };
XAggregation
[TOPIC:com.sun.star.uno.XAggregation]A concept called aggregation is commonly used to plug multiple objects together to form one single object at runtime. The main interface in this context is [IDL:com.sun.star.uno.XAggregation]. After plugging the objects together, the reference count and the queryInterface() method is delegated from multiple slave objects to one master object.
It is a precondition that at the moment of aggregation, the slave object has a reference count of exactly one, which is the reference count of the master. Additionally, it does not work on proxy objects, because in Java, multiple proxy objects of the same interface of the same slave object might exist.
While aggregation allows more code reuse than implementation inheritance, the facts mentioned above, coupled with the implementation of independent objects makes programming prone to errors. Therefore the use of this concept is discourage and not explained here. For further information visit http://udk.openoffice.org/common/man/concept/unointro.html#aggregation.
XUnoTunnel
[TOPIC:com.sun.star.lang.XUnoTunnel]The [IDL:com.sun.star.lang.XUnoTunnel] interface allows access to the this pointer of an object. This interface is used to cast a UNO interface that is coming back to its implementation class through a UNO method. Using this interface is a result of an unsatisfactory interface design, because it indicates that some functionality only works when non-UNO functions are used. In general, these objects cannot be replaced by a different implementation, because they undermine the general UNO interface concept. This interface can be understood as admittance to an already existing code that cannot be split into UNO components easily. If designing new services, do not use this interface.
interface XUnoTunnel: com::sun::star::uno::XInterface
{
hyper getSomething( [in] sequence< byte > aIdentifier );
};
The byte sequence contains an identifier that both the caller and implementer must know. The implementer returns the this pointer of the object if the byte sequence is equal to the byte sequence previously stored in a static variable. The byte sequence is usually generated once per process per implementation.
Note graphics marks a special text section
Note that the previously mentioned 'per process' is important because the this pointer of a class you know is useless, if the instance lives in a different process.
Simple Component in Java
This section shows how to write Java components. The examples in this chapter are in the samples folder that was provided with the programmer's manual.
A Java component is a library of Java classes (a jar) containing objects that implement arbitrary UNO services. For a service implementation in Java, implement the necessary UNO core interfaces and the interfaces needed for your purpose. These could be existing interfaces or interfaces defined by using UNOIDL.
Besides these service implementations, Java components need two methods to instantiate the services they implement in a UNO environment: one to get single factories for each service implementation in the jar, and another one to write registration information into a registry database. These methods are called static component operations in the following:
The method that provides single factories for the service implementations in a component is __getServiceFactory():
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey)
In theory, a client obtains a single factory from a component by calling __getServiceFactory() on the component implementation directly. This is rarely done because in most cases service manager is used to get an instance of the service implementation. The service manager uses __getServiceFactory() at the component to get a factory for the requested service from the component, then asks this factory to create an instance of the one object the factory supports.
To find a requested service implementation, the service manager searches its registry database for the location of the component jar that contains this implementation. For this purpose, the component must have been registered beforehand. UNO components are able to write the necessary information on their own through a function that performs the registration and which can be called by the registration tool regcomp. The function has this signature:
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey)
These two methods work together to make the implementations in a component available to a service manager. The method __writeRegistryServiceInfo() tells the service manager where to find an implementation while __getServiceFactory() enables the service manager to instantiate a service implementation, once found.
The necessary steps to write a component are:
Define service implementation classes.
Implement UNO core interfaces.
Implement your own interfaces.
Provide static component operations to make your component available to a service manager.
Class Definition with Helper Classes
XInterface, XTypeProvider and XWeak
The [PRODUCTNAME] Java UNO environment contains Java helper classes that implement the majority of the core interfaces that are implemented by UNO components. There are two helper classes:
The helper com.sun.star.lib.uno.helper.WeakBase is the minimal base class and implements XInterface, XTypeProvider and Xweak.
The helper com.sun.star.lib.uno.helper.ComponentBase that extends WeakBase and implements XComponent.
The [IDL:com.sun.star.lang.XServiceInfo] is the only interface that should be implemented, but it is not part of the helpers.
Use the naming conventions described in section [CHAPTER:Components.CoreInterfaces.XServiceInfo] for the service implementation. Following the rules, a service org.openoffice.test.ImageShrink should be implemented in org.openoffice.comp.test.ImageShrink.
A possible class definition that uses WeakBase could look like this: [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
package org.openoffice.comp.test;
public class ImageShrink extends com.sun.star.lib.uno.helper.WeakBase
implements com.sun.star.lang.XServiceInfo,
org.openoffice.test.XImageShrinkFilter {
com.sun.star.uno.XComponentContext xComponentContext = null;
/** Creates a new instance of ImageShrink */
public ImageShrink(com.sun.star.uno.XComponentContext XComponentContext xContext) {
this.xComponentContext = xContext;
}
...
}
XServiceInfo
If the implementation only supports one service, use the following code to implement XServiceInfo: [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
...
//XServiceInfo implementation
// hold the service name in a private static member variable of the class
protected static final String __serviceName = "org.openoffice.test.ImageShrink";
public String getImplementationName( ) {
return getClass().getName();
}
public boolean supportsService(String serviceName) {
if ( serviceName.equals( __serviceName))
return true;
return false;
}
public String[] getSupportedServiceNames( ) {
return new String[] { __serviceName };
}
...
An implementation of more than one service in one UNO object is more complex. It has to return all supported service names in getSupportedServiceNames(), furthermore it must check all supported service names in supportsService(). Note that several services packaged in one component file are not discussed here, but objects supporting more than one service. Refer to 4.1: A Component implementing three UNO objects for the implementation of srv3_4.
Implementing your own Interfaces
The functionality of a component is accessible only by its interfaces. When writing a component, choose one of the available API interfaces or define an interface. UNO types are used as method arguments to other UNO objects. Java does not support unsigned integer types, so their use is discouraged. In the chapter [CHAPTER:Components.UNOIDL], the org.openoffice.test.XImageShrinkFilter interface specification was written and an interface class file was created. Its implementation is straightforward, you create a class that implements your interfaces: [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
package org.openoffice.comp.test;
public class ImageShrink extends com.sun.star.lib.uno.helper.WeakBase
implements com.sun.star.lang.XServiceInfo,
org.openoffice.test.XImageShrinkFilter {
...
String destDir = "";
String sourceDir = "";
boolean cancel = false;
com.sun.star.awt.Size dimension = new com.sun.star.awt.Size();
// XImageShrink implementation (a sub-interface of XImageShrinkFilter)
public void cancel() {
cancel = true;
}
public boolean filter(com.sun.star.beans.PropertyValue[] propertyValue) {
// while cancel = false,
// scale images found in sourceDir according to dimension and
// write them to destDir, using the image file format given in
// []propertyValue
// (implementation omitted)
cancel = false;
return true;
}
// XIMageShrink implementation
public String getDestinationDirectory() {
return destDir;
}
public com.sun.star.awt.Size getDimension() {
return dimension;
}
public String getSourceDirectory() {
return sourceDir;
}
public void setDestinationDirectory(String str) {
destDir = str;
}
public void setDimension(com.sun.star.awt.Size size) {
dimension = size;
}
public void setSourceDirectory(String str) {
sourceDir = str;
}
...
}
For the component to run, the new interface class file must be accessible to the Java Virtual Machine. Unlike stand-alone Java applications, it is not sufficient to set the CLASSPATH environment variable. Instead, the class path is passed to the VM when it is created. Prior to [PRODUCTNAME][OO1.1], one could modify the class path by editing the SystemClasspath entry of the java(.ini|rc) which was located in the folder <officepath>\user\config. Another way was to use the Options dialog. To navigate to the class path settings, one had to expand the [PRODUCTNAME] node in the tree on the left-hand side and chose Security. On the right-hand side, there was a field called User Classpath.
As of [PRODUCTNAME][OO1.1] the component, class files, and type librarys are packed into a UNO Package Bundleextension, which is then registered by the pkgchk executable. And as of [PRODUCTNAME][OO1.2], the unopkg tool is used to to thisinstall the bundles. The jar files are then automatically added to the class path.
Note graphics marks a special text section
It is also important that the binary type library of the new interfaces are provided together with the component, otherwise the component is not accessible from [PRODUCTNAME] Basic. Basic uses the UNO core reflection service to get type information at runtime. The core reflection is based on the binary type library.
Providing a Single Factory Using Helper Method
The component must be able to create single factories for each service implementation it contains and return them in the static component operation __getServiceFactory(). The [PRODUCTNAME] Java UNO environment provides a Java class com.sun.star.comp.loader.FactoryHelper that creates a default implementation of a single factory through its method getServiceFactory(). The following example could be written: [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
package org.openoffice.comp.test;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.comp.loader.FactoryHelper;
public class ImageShrink ... {
...
// static __getServiceFactory() implementation
// static member __serviceName was introduced above for XServiceInfo implementation
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
com.sun.star.registry.XRegistryKey regKey) {
com.sun.star.lang.XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals( ImageShrink.class.getName()) )
xSingleServiceFactory = FactoryHelper.getServiceFactory(ImageShrink.class,
ImageShrink.__serviceName, multiFactory, regKey);
return xSingleServiceFactory;
}
...
}
The FactoryHelper is contained in the jurt jar file. The getServiceFactory() method takes as a first argument a Class object. When createInstance() is called on the default factory, it creates an instance of that Class using newInstance() on it and retrieves the implementation name through getName(). The second argument is the service name. The multiFactory and regKey arguments were received in __getServiceFactory() and are passed to the FactoryHelper.
Note graphics marks a special text section
In this case, the implementation name, which the default factory finds through Class.getName() is org.openoffice.comp.test.ImageShrink and the service name is org.openoffice.test.ImageShrink. The implementation name and the service name are used for the separate XServiceInfo implementation within the default factory. Not only do you support the XServiceInfo interface in your service implementation, but the single factory must implement this interface as well.
The default factory created by the FactoryHelper expects a public constructor in the implementation class of the service and calls it when it instantiates the service implementation. The constructor can be a default constructor, or it can take a [IDL:com.sun.star.uno.XComponentContext] or a [IDL:com.sun.star.lang.XMultiServiceFactory] as an argument. Refer to [CHAPTER:Components.Java.Init] for other arguments that are possible.
Java components are housed in jar files. When a component has been registered, the registry contains the name of the jar file, so that the service manager can find it. However, because a jar file can contain several class files, the service manager must be told which one contains the __getServiceFactory() method. That information has to be put into the jar's Manifest file, for example:
RegistrationClassName: org.openoffice.comp.test.ImageShrink
Write Registration Info Using Helper Method
UNO components have to be registered with the registry database of a service manager. In an office installation, this is the file types.rdb (up through [OO1.1], applicat.rdb) for all predefined services. A service manager can use this database to find the implementations for a service. For instance, if an instance of your component is created using the following call.
Object imageShrink = xRemoteServiceManager.createInstance("org.openoffice.test.ImageShrink");
Using the given service or implementation name, the service manager looks up the location of the corresponding jar file in the registry and instantiates the component.
Note graphics marks a special text section
If you want to use the service manager of the Java UNO runtime, com.sun.star.comp.servicemanager.ServiceManager (jurt.jar), to instantiate your service implementation, then you would have to create the service manager and add the factory for “org.openoffice.test.ImageShrink” programmatically, because the Java service manager does not use the registry.
Alternatively, you can use com.sun.star.comp.helper.RegistryServiceFactory from juh.jar which is registry-based. Its drawback is that it delegates to a C++ implementation of the service manager through the java-bridge.
During the registration, a component writes the necessary information into the registry. The process to write the information is triggered externally when a client calls the __writeRegistryServiceInfo() method at the component.
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey)
The caller passes an [IDL:com.sun.star.registry.XRegistryKey] interface that is used by the method to write the registry entries. Again, the FactoryHelper class offers a way to implement the method: [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
...
// static __writeRegistryServiceInfo implementation
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
return FactoryHelper.writeRegistryServiceInfo( ImageShrink.class.getName(),
__serviceName, regKey);
}
The writeRegistryServiceInfo method takes three arguments:
implementation name
service name
XRegistryKey
Use tools, such as regcomp or the Java application com.sun.star.tools.uno.RegComp to register a component. These tools take the path to the jar file containing the component as an argument. Since the jar can contain several classes, the class that implements the __writeRegistryServiceInfo() method must be pointed out by means of the manifest. Again, the RegistrationClassName entry determines the correct class. For example:
RegistrationClassName: org.openoffice.comp.test.ImageShrink
The above entry is also necessary to locate the class that provides __getServiceFactory(), therefore the functions __writeRegistryServiceInfo() and __getServiceFactory() have to be in the same class.
Implementing without Helpers
XInterface
As soon as the component implements any UNO interface, [IDL:com.sun.star.uno.XInterface] is included automatically. The Java interface definition generated by javamaker for [IDL:com.sun.star.uno.XInterface] only contains a TypeInfo member used by Java UNO internally to store certain UNO type information:
// source file com/sun/star/uno/XInterface.java gcorresponding to the class generated by
package com.sun.star.uno;
public interface XInterface
{
// static Member
public static final com.sun.star.lib.uno.typeinfo.TypeInfo UNOTYPEINFO[] = null;
}
Note that XInterface does not have any methods, in contrast to its IDL description. That means, if implements com.sun.star.uno.XInterface is added to a class definition, there is nothing to implement.
The method queryInterface() is unnecessary in the implementation of a UNO object, because the Java UNO runtime environment obtains interface references without support from the UNO objects themselves. Within Java, the method UnoRuntime.queryInterface() is used to obtain interfaces instead of calling [IDL:com.sun.star.uno.XInterface:queryInterface](), and the Java UNO language binding hands out interfaces for UNO objects to other processes on its own as well.
The methods acquire() and release() are used for reference counting and control the lifetime of an object, because the Java garbage collector does this, there is no reference counting in Java components.
XTypeProvider
Helper classes with default [IDL:com.sun.star.lang.XTypeProvider] implementations are still under development for Java. Meanwhile, every Java UNO object implementation can implement the XTypeProvider interface as shown in the following code. In your implementation, adjust getTypes(): [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
...
// XTypeProvider implementation
// maintain a static implementation id for all instances of ImageShrink
// initialized by the first call to getImplementationId()
protected static byte[] _implementationId;
public com.sun.star.uno.Type[] getTypes() {
// instantiate Type instances for each interface you support and place them in a Type[] array
// (this object supports XServiceInfo, XTypeProvider, and XImageShrinkFilter)
return new com.sun.star.uno.Type[] {
new com.sun.star.uno.Type(com.sun.star.lang.XServiceInfo.class),
new com.sun.star.uno.Type(com.sun.star.lang.XTypeProvider.class),
new com.sun.star.uno.Type(org.openoffice.test.XImageShrinkFilter.class) };
}
synchronized public byte[] getImplementationId() {
if (_implementationId == null) {
_implementationId= new byte[16];
int hash = hashCode(); // hashCode of this object
_implementationId[0] = (byte)(hash & 0xff);
_implementationId[1] = (byte)((hash >>> 8) & 0xff);
_implementationId[2] = (byte)((hash >>> 16) & 0xff);
_implementationId[3] = (byte)((hash >>>24) & 0xff);
}
return _implementationId;
}
...
The suggested implementation of the getImplementationId() method is not optimal, it uses the hashCode() of the first instance that initializes the static field. The future UNO helper class will improve this.
XComponent
XComponent is an optional interface that is useful when other objects hold references to the component. The notification mechanism of XComponent enables listener objects to learn when the component stops to provide its services, so that the objects drop their references to the component. This enables the component to delete itself when its reference count drops to zero. From section [CHAPTER:Components.CoreInterfaces], there must be three things done when dispose() is called at an XComponent:
Inform registered XEventListeners that the object is being disposed of by calling their method disposing().
Release all references the object holds, including all XEvenListener objects.
On further calls to the component, throw an [IDL:com.sun.star.lang.DisposedException] in case the required task can not be fulfilled anymore, because the component was disposed.
In Java, the object cannot be deleted, but the garbage collector will do this. It is sufficient to release all references that are currently being held to break the cyclic reference, and to call disposing() on all [IDL:com.sun.star.lang.XEventListener]s.
The registration and removal of listener interfaces is a standard procedure in Java. Some IDEs even create the necessary methods automatically. The following example could be written: [SOURCE:Components/Thumbs/org/openoffice/comp/test/ImageShrink.java]
...
//XComponent implementation
// hold a list of eventListeners
private java.util.ArrayList eventListeners = new java.util.ArrayList();
public void dispose {
java.util.ArrayList listeners;
synchronized (this) {
listeners = eventListeners;
eventListeners = null;
}
for (java.util.Iterator i = listeners.iterator(); i.hasNext();) {
fireDisposing((XEventListener) i.next());
}
releaseReferences();
}
public void addEventListener(XEventListener listener) {
bool fire = false;
synchronized (this) {
if (eventListeners == null) {
fire = true;
} else {
eventListeners.add(listener);
}
}
if (fire) {
fireDisposing(listener);
}
}
public synchronized void removeEventListener(XEventListener listener) {
if (eventListeners != null) {
int i = eventListeners.indexOf(listener);
if (i >= 0) {
eventListeners.remove(i);
}
}
}
private void fireDisposing(XEventListener listener) {
com.sun.star.uno.EventObject event = new com.sun.star.uno.EventObject(this);
try {
listener.disposing(event);
} catch (com.sun.star.uno.DisposedException e) {
// it is not an error if some listener is disposed simultaneously
}
}
private void releaseReferences() {
xComponentContext = null;
// ...
}
Storing the Service Manager for Further Use
A component usually runs in the office process. There is no need to create an interprocess channel explicitly. A component does not have to create a service manager, because it is provided to the single factory of an implementation by the service manager during a call to createInstance() or createInstanceWithContext(). The single factory receives an XComponentContext or an XMultiServiceFactory, and passes it to the corresponding constructor of the service implementation. From the component context, the implementation gets the service manager using getServiceManager() at the [IDL:com.sun.star.uno.XComponentContext] interface.
Create Instance with Arguments
A factory can create an instance of components and pass additional arguments. To do that, a client calls the createInstanceWithArguments() function of the [IDL:com.sun.star.lang.XSingleServiceFactory] interface or the createInstanceWithArgumentsAndContext() of the [IDL:com.sun.star.lang.XSingleComponentFactory] interface.
//javamaker generated interface
//XSingleServiceFactory interface
public java.lang.Object createInstanceWithArguments(java.lang.Object[] aArguments)
throws com.sun.star.uno.Exception;
//XSingleComponentFactory
public java.lang.Object createInstanceWithArgumentsAndContext(java.lang.Object[] Arguments,
com.sun.star.uno.XComponentContext Context)
throws com.sun.star.uno.Exception;
Both functions take an array of values as an argument. A component implements the [IDL:com.sun.star.lang.XInitialization] interface to receive the values. A factory passes the array on to the single method initialize() supported by XInitialization.
public void initialize(java.lang.Object[] aArguments) throws com.sun.star.uno.Exception;
Alternatively, a component may also receive these arguments in its constructor. If a factory is written, determine exactly which arguments are provided by the factory when it instantiates the component. When using the FactoryHelper, implement the constructors with the following arguments:
First Argument
Second Argument
Third Argument
com.sun.star.uno.XComponentContext
com.sun.star.registry.XRegistryKey
java.lang.Object[]
com.sun.star.uno.XComponentContext
com.sun.star.registry.XRegistryKey
com.sun.star.uno.XComponentContext
java.lang.Object[]
com.sun.star.uno.XComponentContext
java.lang.Object[]
The FactoryHelper automatically passes the array of arguments it received from the createInstanceWithArguments[AndContext]() call to the appropriate constructor. Therefore, it is not always necessary to implement XInitialization to use arguments.
Possible Structures for Java Components
The implementation of a component depends on the needs of the implementer. The following examples show some possible ways to assemble a component. There can be one implemented object or several implemented objects per component file.
One Implementation per Component File
There are additional options if implementing one service per component file:
Use a flat structure with the static component operations added to the service implementation class directly.
Reserve the class with the implementation name for the static component operation and use an inner class to implement the service.
Implementation Class with Component Operations
An implementation class contains the static component operations. The following sample implements an interface com.sun.star.test.XSomething in an implementation class JavaComp.TestComponent:
// UNOIDL: interface example specification
module com { module sun { module star { module test {
interface XSomething: com::sun::star::uno::XInterface
{
string methodOne([in]string val);
};
}; }; }; };
A component that implements only one service supporting XSomething can be assembled in one class as follows:
package JavaComp;
...
public class TestComponent implements XSomething, XTypeProvider, XServiceInfo {
public static final String __serviceName="com.sun.star.test.JavaTestComponent";
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory, XRegistryKey regKey) {
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals( TestComponent.class.getName()) )
xSingleServiceFactory = FactoryHelper.getServiceFactory( TestComponent.class,
TestComponent.__serviceName, multiFactory, regKey);
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey){
return FactoryHelper.writeRegistryServiceInfo( TestComponent.class.getName(),
TestComponent.__serviceName, regKey);
}
// XSomething
string methodOne(String val) {
return val;
}
//XTypeProvider
public com.sun.star.uno.Type[] getTypes( ) {
...
}
// XTypeProvider
public byte[] getImplementationId( ) {
...
}
//XServiceInfo
public String getImplementationName( ) {
...
}
// XServiceInfo
public boolean supportsService( /*IN*/String serviceName ) {
...
}
//XServiceInfo
public String[] getSupportedServiceNames( ) {
...
}
}
The class implements the XSomething interface. The IDL description and documentation provides information about its functionality. The class also contains the functions for factory creation and registration, therefore the manifest entry must read as follows:
RegistrationClassName: JavaComp.TestComponent
Implementation Class with Component Operations and Inner Implementation Class
To implement the component as inner class of the one that provides the service factory through __getServiceFactory(), it must be a static inner class, otherwise the factory provided by the FactoryHelper cannot create the component. An example for an inner implementation class is located in the sample com.sun.star.comp.demo.DemoComponent.java provided with the SDK. The implementation of __getServiceFactory() and __writeRegistryServiceInfo() is omitted here, because they act the same as in the implementation class with component operations above.
package com.sun.star.comp.demo;
public class DemoComponent {
...
// static inner class implements service com.sun.star.demo.DemoComponent
static public class _Implementation implements XTypeProvider,
XServiceInfo, XInitialization, XWindowListener,
XActionListener, XTopWindowListener {
static private final String __serviceName = "com.sun.star.demo.DemoComponent";
private XMultiServiceFactory _xMultiServiceFactory;
// Constructor
public _Implementation(XMultiServiceFactory xMultiServiceFactory) {
}
}
// static method to get a single factory creating the given service from the factory helper
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey) {
...
}
// static method to write the service information into the given registry key
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
...
}
}
The manifest entry for this implementation structure again has to point to the class with the static component operations:
RegistrationClassName: com.sun.star.comp.demo.DemoComponent
Multiple Implementations per Component File
To assemble several service implementations in one component file, implement each service in its own class and add a separate class containing the static component operations. The following code sample features two services: TestComponentA and TestComponentB implementing the interfaces XSomethingA and XSomethingB with a separate static class TestServiceProvider containing the component operations.
The following are the UNOIDL specifications for XSomethingA and XSomethingB:
module com { module sun { module star { module test {
interface XSomethingA: com::sun::star::uno::XInterface
{
string methodOne([in]string value);
};
}; }; }; };
module com { module sun { module star { module test {
interface XSomethingB: com::sun::star::uno::XInterface
{
string methodTwo([in]string value);
};
}; }; }; };
TestComponentA implements XSomethingA: [SOURCE:Components/JavaComponent/TestComponentA.java]:
package JavaComp;
public class TestComponentA implements XTypeProvider, XServiceInfo, XSomethingA {
static final String __serviceName= "JavaTestComponentA";
static byte[] _implementationId;
public TestComponentA() {
}
// XSomethingA
public String methodOne(String val) {
return val;
}
//XTypeProvider
public com.sun.star.uno.Type[] getTypes( ) {
Type[] retValue= new Type[3];
retValue[0]= new Type( XServiceInfo.class);
retValue[1]= new Type( XTypeProvider.class);
retValue[2]= new Type( XSomethingA.class);
return retValue;
}
//XTypeProvider
synchronized public byte[] getImplementationId( ) {
if (_implementationId == null) {
_implementationId= new byte[16];
int hash = hashCode();
_implementationId[0] = (byte)(hash & 0xff);
_implementationId[1] = (byte)((hash >>> 8) & 0xff);
_implementationId[2] = (byte)((hash >>> 16) & 0xff);
_implementationId[3] = (byte)((hash >>>24) & 0xff);
}
return _implementationId;
}
//XServiceInfo
public String getImplementationName( ) {
return getClass().getName();
}
// XServiceInfo
public boolean supportsService( /*IN*/String serviceName ) {
if ( serviceName.equals( __serviceName))
return true;
return false;
}
//XServiceInfo
public String[] getSupportedServiceNames( ) {
String[] retValue= new String[0];
retValue[0]= __serviceName;
return retValue;
}
}
TestComponentB implements XSomethingB. Note that it receives the component context and initialization arguments in its constructor. [SOURCE:Components/JavaComponent/TestComponentB.java]
package JavaComp;
public class TestComponentB implements XTypeProvider, XServiceInfo, XSomethingB {
static final String __serviceName= "JavaTestComponentB";
static byte[] _implementationId;
private XComponentContext context;
private Object[] args;
public TestComponentB(XComponentContext context, Object[] args) {
this.context= context;
this.args= args;
}
// XSomethingB
public String methodTwo(String val) {
if (args.length > 0 && args[0] instanceof String )
return (String) args[0];
return val;
}
//XTypeProvider
public com.sun.star.uno.Type[] getTypes( ) {
Type[] retValue= new Type[3];
retValue[0]= new Type( XServiceInfo.class);
retValue[1]= new Type( XTypeProvider.class);
retValue[2]= new Type( XSomethingB.class);
return retValue;
}
//XTypeProvider
synchronized public byte[] getImplementationId( ) {
if (_implementationId == null) {
_implementationId= new byte[16];
int hash = hashCode();
_implementationId[0] = (byte)(hash & 0xff);
_implementationId[1] = (byte)((hash >>> 8) & 0xff);
_implementationId[2] = (byte)((hash >>> 16) & 0xff);
_implementationId[3] = (byte)((hash >>>24) & 0xff);
}
return _implementationId;
}
//XServiceInfo
public String getImplementationName( ) {
return getClass().getName();
}
// XServiceInfo
public boolean supportsService( /*IN*/String serviceName ) {
if ( serviceName.equals( __serviceName))
return true;
return false;
}
//XServiceInfo
public String[] getSupportedServiceNames( ) {
String[] retValue= new String[0];
retValue[0]= __serviceName;
return retValue;
}
}
TestServiceProvider implements __getServiceFactory() and __writeRegistryServiceInfo(): [SOURCE:Components/JavaComponent/TestServiceProvider.java]
package JavaComp;
...
public class TestServiceProvider
{
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey) {
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals( TestComponentA.class.getName()) )
xSingleServiceFactory = FactoryHelper.getServiceFactory( TestComponentA.class,
TestComponentA.__serviceName, multiFactory, regKey);
else if (implName.equals(TestComponentB.class.getName()))
xSingleServiceFactory= FactoryHelper.getServiceFactory( TestComponentB.class,
TestComponentB.__serviceName, multiFactory, regKey);
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey){
boolean bregA= FactoryHelper.writeRegistryServiceInfo( TestComponentA.class.getName(),
TestComponentA.__serviceName, regKey);
boolean bregB= FactoryHelper.writeRegistryServiceInfo( TestComponentB.class.getName(),
TestComponentB.__serviceName, regKey);
return bregA && bregB;
}
}
The corresponding manifest entry must point to the static class with the component operations, in this case JavaComp.TestServiceProvider:
RegistrationClassName: JavaComp.TestServiceProvider
Running and Debugging Java Components
RegistrationIn order to run a Java component within an office, it needs to be registered first. During the process of registration, the location of the component, its service name and implementation name, are written into a registry database – the services.rdb.
Pay attention to the following important text section
As of [PRODUCTNAME][OO1.1] the registration database (applicat.rdb) was split into the services.rdb and the types.rdb. As the names suggest, the services.rdb contains information about services (location, names, ect), whereas the types.rdb holds type descriptions (interfaces, enumerations, etc.)
Formerly the regcomp tool was used for registering components. However, it was superseded by pkgchk, which came which will be delivered along with [PRODUCTNAME][OO1.1] and later by unopkg which came with [PRODUCTNAME][OO1.2].. For more details about pkgchk unopkg refer to chapter [CHAPTER:ExtensionsComponents.Deployment.PackageInstall].
By using regcomp you have the option of registering components so that the information is kept in a separate database (other then the services.rdb). This might come in handy if you do not want to clutter up the services.rdb while developing components. Then, however, the office needs to be told to use that .rdb, which is done by modifying the uno(.ini|rc).
If the component uses new types, then they must be made available to the office by merging the type information into the services.rdb. Again, you have the option of using a different database as long as the uno.(ini|rc) is modified accordingly. This step can be omitted if pkgchkunopkg is being used.
The following is a step by step description of the registration process using regcomp:
Note, if errors are encountered, refer to the troubleshooting section at the end of this chapter.
Register Component File
This step creates a registry file that contains the location of the component file and all the necessary type information. To register, place a few files to the proper locations:
Copy the regcomp tool from the SDK distribution to <OfficePath>/program.
Copy the component jar to <OfficePath>/program/classes.
Copy the .rdb file containing the new types created to <OfficePath>/program. If new types were not defined, dismiss this step. In this case, regcomp automatically creates a new rdb file with registration information.
On the command prompt, change to <OfficePath>/program, then run regcomp with the following options. Line breaks were applied to improve readability, but the command must be entered in a single line:
$ regcomp -register -r <your_registry>.rdb -br services.rdb
-br types.rdb
-l com.sun.star.loader.Java
-c file:///<OfficePath>/program/classes/<your_component>.jar
For the org.openoffice.test.ImageShrink service whose type description was merged into thumbs.rdb , which is implemented in thumbs.jar, the corresponding command would be:
$ regcomp -register -r thumbs.rdb
-br services.rdb
-br types.rdb
-l com.sun.star.loader.Java
-c file:///i:/StarOffice6.0/program/classes/thumbs.jar
Instead of regcomp, there is also a Java tool to register components, however, it can only write to the same registry it reads from. It cannot be used to create a separate registry database. For details, see the section [CHAPTER:Components.Deployment].
Make Registration available to [PRODUCTNAME]
[PRODUCTNAME] must be told to use the registry. Close all [PRODUCTNAME] parts, including the Quickstarter that runs in the Windows task bar. Edit the file uno(.ini|rc) in <OfficePath>/program as follows:
[Bootstrap]UNO_TYPES=$SYSBINDIR/types.rdb $SYSBINDIR/<your_registry>.rdbUNO_SERVICES=$SYSBINDIR/services.rdb $SYSBINDIR/<your_registry>.rdb
For details about the syntax of uno(.ini|rc) and alternative registration procedures, refer to the section [CHAPTER:Components.Deployment]. If [PRODUCTNAME] is restarted, the component should be available.
Test the Registration
A short [PRODUCTNAME] Basic program indicates if the program runs went smoothly, by selecting Tools – Macro and entering a new macro name on the left, such as TestImageShrink and click New to create a new procedure. In the procedure, enter the appropriate code of the component. The test routine for ImageShrink would be:
Sub TestImageShrink
oTestComp = createUnoService("org.openoffice.test.ImageShrink")
MsgBox oTestComp.dbg_methods
MsgBox oTestComp.dbg_properties
MsgBox oTestComp.dbg_supportedInterfaces
end sub
The result should be three dialogs showing the methods, properties and interfaces supported by the implementation. Note that the interface attributes do not appear as get/set methods, but as properties in Basic. If the dialogs do not show what is expected, refer to the section [CHAPTER:Components.Java.TestDebug.Troubleshooting].
Debugging
To increase turnaround cycles and source level debugging, configure the IDE to use GNU makefiles for code generation and prepare [PRODUCTNAME] for Java debugging. If NetBeans are used, the following steps are necessary:
Support for GNU make
A NetBeans extension, available on makefile.netbeans.org, that adds basic support for GNU makefiles. When it is enabled, edit the makefile in the IDE and use the makefile to build. To install and enable this module, select Tools – Setup Wizard and click Next to go to the Module installation page. Find the module Makefiles and change the corresponding entry to True in the Enabled column. Finish using the setup wizard. If the module is not available in the installation, use Tools – Update Center to get the module from www.netbeans.org. A new entry, Makefile Support, appears in the online help when Help – Contents is selected. Makefile Support provides further configuration options. The settings Run a Makefile and Test a Makefile can be found in Tools – Options – Uncategorized – Compiler Types and – Execution Types.
Put the makefile into the project source folder that was mounted when the project was created. To build the project using the makefile, highlight the makefile in the Explorer and press F11.
Documentation for GNU make command-line options and syntax are available at www.gnu.org. The sample Thumbs in the samples folder along with this manual contains a makefile that with a few adjustments is useful for Java components.
Component Debugging
If NetBeans or Forte for Java is used, the Java Virtual Machine (JVM) that is launched by [PRODUCTNAME] can be attached. Configure the JVM used by [PRODUCTNAME] to listen for debugger connections. Prior to [PRODUCTNAME][OO2.0] this was done by adding these lines to the java(.ini|rc) in <OfficePath>/user/config:
-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
As of PRODUCTNAME][OO2.0], these lines are added in the options dialog: expand the [PRODUCTNAME] node in the tree on the left-hand side and chose Java. On the right-hand side, push the Parameters button to open a dialog. In this dialog, enter the debug options as two separate entries. Note that the parameters have to entered the same way as they would be provided on the command line when starting the Java executable. That is, retain the leading '-' and spaces, if necessary.
Tip graphics marks a hint section in the text
The additional entries correspond exactly to the options you would use when running the java executable from the command line in debug mode. For more information refer to the Java SDK documentation.
The last line causes the JVM to listen for a debugger on port 8000. The JVM starts listening as soon as it runs and does not wait until a debugger connects to the JVM. Launch the office and instantiate the Java component, so that the office invokes the JVM in listening mode.
Once a Java component is instantiated, the JVM keeps listening even if the component goes out of scope. Open the appropriate source file in the NetBeans editor and set breakpoints as needed. Choose Debug - Attach, select Java Platform Debugger Architecture (JPDA) as debugger type and SocketAttach (Attaches by socket to other VMs) as the connector. The Host should be localhost and the Port must be 8000. Click OK to connect the Java Debugger to the JVM the office has started previously step.
Once the debugger connects to the running JVM, NetBeans switches to debug mode, the output windows shows a message that a connection on port 8000 is established and threads are visible, as if the debugging was local. If necessary, start your component once again. As soon as the component reaches a breakpoint in the source code, the source editor window opens with the breakpoint highlighted by a green arrow.
The Java Environment in [PRODUCTNAME]
When UNO components written in Java are to be used within the office, the office has to be configured appropriately. Prior to [PRODUCTNAME][OO2.0], this configuration happened during the installation, when the Java setup was performed. Then, a user could choose a Java Runtime Environment or choose to install a JRE. After installing the office, the selected JRE could still be changed with the jvmsetup program, which was located in the program folder. The data for running the Java Virtual Machine was stored in the java(.ini|rc) file and other configuration files.
Note graphics marks a special text section
The java(.ini|rc) actually is an implementation detail. Unfortunately, it needs to be modified under some rare circumstances, for example for debugging purposes. You must not rely on the existence of the file nor should you make assumptions about its contents.
In an office with a lower version than [OO2.0], the java(.ini|rc) is located in the <officepath>\user\config directory. A client can use that file to pass additional properties to the Java Virtual Machine, which are then available as system properties. For example, to pass the property MyAge, invoke Java like this:
java -DMyAge=30 RunClass
If you want to have that system property accessible by your Java component you can put that property into java(ini|rc) within the [Java] section. For example:
[Java]Home=file:///C:/Program%20Files/Java/j2re1.4.2
VMType=JREVersion=1.4.2RuntimeLib=file:///C:/Program%20Files/Java/j2re1.4.2/bin/client/jvm.dllSystemClasspath=d:\645m15\program\classes\classes.jar;; ...Java=1JavaScript=1Applets=1MyAge=27
To debug a Java component, it is necessary to start the JVM with additional parameters. The parameters can be put in the java.ini the same way as they would appear on the command-line. For example , add those lines to the [Java] section:
-Xdebug-Xrunjdwp:transport=dt_socket,server=y,address=8000
More about debugging can be found in the JDK documentation and in the [PRODUCTNAME] Software Development Kit.
Java components are also affected by the following configuration settings. They can be changed in the Tools - Options dialog. In the dialog, expand the [PRODUCTNAME] node on the left-hand side and choose Security. This brings up a new pane on the right-hand side that allows Java specific settings:
Java Setting
Description
Enable
If checked, Java is used with the office. This affects Java components, as well as applets.
Security checks
If checked, the security manager restricts resource access of applets.
Net access
Determines where an applet can connect.
ClassPath
Additional jar files and directories where the JVM should search for classes. Also known as user classpath.
Applets
If checked, applets are executed.
In [PRODUCTNAME][OO2.0] there is no java(.ini|rc) anymore. All basic Java settings are set in the options dialog: tree node [PRODUCTNAME]->Java. The Parameters dialog can be used to specify the debug options and other arguments.
For applets there are still a few settings on the security panel (tree node [PRODUCTNAME]->Security).
Troubleshooting
If the component encounters problems, review the following checklist to check if the component is configured correctly.
Check Registry Keys
To check if the registry database is correctly set up, run regview against the three keys that make up a registration in the /UCR, /SERVICES and /IMPLEMENTATIONS branch of a registry database. The following examples show how to read the appropriate keys and how a proper configuration should look. In our example, service ImageShrink, and the key /UCR/org/openoffice/test/XImageShrink contain the type information specified in UNOIDL (the exact output from regview might differ between versions of [PRODUCTNAME]):
# dump XImageShrink type information
$ regview thumbs.rdb /UCR/org/openoffice/test/XImageShrink
Registry "file:///X:/office60eng/program/thumbs.rdb":
/UCR/org/openoffice/test/XImageShrink
Value: Type = RG_VALUETYPE_BINARY
Size = 364
Data = minor version: 0
major version: 1
type: 'interface'
uik: { 0x00000000-0x0000-0x0000-0x00000000-0x00000000 }
name: 'org/openoffice/test/XImageShrink'
super name: 'com/sun/star/uno/XInterface'
Doku: ""
IDL source file: "X:\SO\sdk\examples\java\Thumbs\org\openoffice\test\XImageShrink.idl"
number of fields: 3
field #0:
name='SourceDirectory'
type='string'
access=READWRITE
Doku: ""
IDL source file: ""
field #1:
name='DestinationDirectory'
type='string'
access=READWRITE
Doku: ""
IDL source file: ""
field #2:
name='Dimension'
type='com/sun/star/awt/Size'
access=READWRITE
Doku: ""
IDL source file: ""
number of methods: 0
number of references: 0
The /SERVICES/org.openoffice.test.ImageShrink key must point to the implementation name org.openoffice.comp.test.ImageShrink that was chosen for this service:
# dump service name registration
$ regview thumbs.rdb /SERVICES/org.openoffice.test.ImageShrink
Registry "file:///X:/office60eng/program/thumbs.rdb":
/SERVICES/org.openoffice.test.ImageShrink
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 45
Len = 1
Data = 0 = "org.openoffice.comp.test.ImageShrink"
Finally, the /IMPLEMENTATIONS/org.openoffice.comp.test.ImageShrink key must contain the loader and the location of the component jar:
# dump implementation name registration
$ regview thumbs.rdb /IMPLEMENTATIONS/org.openoffice.comp.test.ImageShrink
Registry "file:///X:/office60eng/program/thumbs.rdb":
/IMPLEMENTATIONS/org.openoffice.comp.test.ImageShrink
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 26
Data = "com.sun.star.loader.Java2"
/ SERVICES
/ org.openoffice.test.ImageShrink
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 50
Data = "file:///X:/office60eng/program/classes/thumbs.jar"
If the UCR key is missing, the problem is with regmerge. The most probable cause are missing .urd files. Be careful when writing the makefile. If .urd files are missing when regmerge is launched by the makefile, regmerge continues and creates a barebone .rdb file, sometimes without any type info.
If regview can not find the /SERVICES and /IMPLEMENTATIONS keys or they have the wrong content, the problem occurred when regcomp was run. This can be caused by wrong path names in the regcomp arguments.
Also, a wrong SystemClasspath setup in java(.ini|rc) (prior to [PRODUCTNAME][OO2.0])could be the cause of regcomp error messages about missing classes. Check what the SystemClasspath entry in java(.ini|rc) specifies for the Java UNO runtime jars.
Ensure that regcomp is being run from the current directory when registering Java components. In addition, ensure <OfficePath>/program is the current folder when regcomp is run. Verify that regcomp is in the current folder.
Check the Java VM settings
Whenever the VM service is instantiated by [PRODUCTNAME], it uses the Java configuration settings in [PRODUCTNAME]. This happens during the registration of Java components, therefore make sure that Java is enabled. Choose Tools-Options in [PRODUCTNAME], so that the dialog appears. Expand the [PRODUCTNAME] node and select Security. Select the Enable checkbox in the Java section and click OK.
Check the Manifest
Make sure the manifest file contains the correct entry for the registration class name. The file must contain the following line:
RegistrationClassName: <full name of package and class>
Please make sure that the manifest file ends up with a new line. The registration class name must be the one that implements the __writeRegistryServiceInfo() and __getServiceFactory() methods. The RegistrationClassName to be entered in the manifest for our example is org.openoffice.comp.test.ImageShrink.
Adjust CLASSPATH for Additional Classes
[PRODUCTNAME] maintains its own system classpath and a user classpath when it starts the Java VM for Java components. The jar file that contains the service implementation is not required in the system or user classpath. If a component depends on jar files or classes that are not part of the Java UNO runtime jars, then they must be put on the classpath. This can be achieved by editing the classpath in the options dialog (Tools – Options – [PRODUCTNAME] – Security) .
Disable Debug Options
If the debug options (-Xdebug, -Xrunjdwp) are in the java(.ini|rc) (prior to [PRODUCTNAME][OO2.0]) file, disable them by putting semicolons at the beginning of the respective lines. For [PRODUCTNAME][OO2.0] and later, make sure the debug options are removed in the Parameters dialog. This dialog can be found in the options dialog (Tools – Options – [PRODUCTNAME] – Java). The regcomp or pkgchk tool or the Extension Manager may hang, because the JVM is waiting for a debugger to be attached.
C++ Component
In this section, a sample component containing two service implementations with helpers and without helpers implemented are presented. The complete source code and the gnu makefile are in samples/simple_cpp_component.
The first step for the C++ component is to define a language-independent interface, so that the UNO object can communicate with others. The IDL specification for the component defines one interface my_module.XSomething and two old-style services implementing this interface (if new-style services were used instead, the example would not be much different). In addition, the second service called my_module.MyService2 implements the [IDL:com.sun.star.lang.XInitialization] interface, so that MyService2 can be instantiated with arguments passed to it during runtime.
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XInitialization.idl>
module my_module
{
interface XSomething : com::sun::star::uno::XInterface
{
string methodOne( [in] string val );
};
service MyService1
{
interface XSomething;
};
service MyService2
{
interface XSomething;
interface com::sun::star::lang::XInitialization;
};
};
This IDL is compiled to produce a binary type library file (.urd file), by executing the following commands. The types are compiled and merged into a registry simple_component.rdb, that will be linked into the [PRODUCTNAME] installation later.
$ idlc -I<SDK>/idl some.idl$ regmerge simple_component.rdb /UCR some.urd
The cppumaker tool must be used to map IDL to C++:
$ cppumaker -BUCR -Tmy_module.XSomething <officepath>/program/types.rdb simple_component.rdb
For each given type, a pair of header files is generated, a .hdl and a .hpp file. To avoid conflicts, all C++ declarations of the type are in the .hdl and all definitions, such as constructors, are in the .hpp file. The .hpp is the one to include for any type used in C++.
The next step is to implement the core interfaces, and the implementation of the component operations component_getFactory(), component_writeInfo() and component_getImplementationEnvironment()with or without helper methods.
Class Definition with Helper Template Classes
XInterface, XTypeProvider and XWeak
The SDK offers helpers for ease of developing. There are implementation helper template classes that deal with the implementation of [IDL:com.sun.star.uno.XInterface] and [IDL:com.sun.star.lang.XTypeProvider], as well as [IDL:com.sun.star.uno.XWeak]. These classes let you focus on the interfaces you want to implement.
The implementation of my_module.MyService2 uses the ::cppu::WeakImplHelper3<> helper. The “3” stands for the number of interfaces to implement. The class declaration inherits from this template class which takes the interfaces to implement as template parameters. [SOURCE:Components/CppComponent/service2_impl.cxx]
#include <cppuhelper/implbase3.hxx> // "3" implementing three interfaces
#include <cppuhelper/factory.hxx>
#include <cppuhelper/implementationentry.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <my_module/XSomething.hpp>
using namespace ::rtl; // for OUString
using namespace ::com::sun::star; // for sdk interfaces
using namespace ::com::sun::star::uno; // for basic types
namespace my_sc_impl {
class MyService2Impl : public ::cppu::WeakImplHelper3< ::my_module::XSomething,
lang::XServiceInfo,
lang::XInitialization >
{
...
};
}
The next section focusses on coding [IDL:com.sun.star.lang.XServiceInfo], [IDL:com.sun.star.lang.XInitialization] and the sample interface my_module.XSomething.
The cppuhelper shared library provides additional implementation helper classes, for example, supporting [IDL:com.sun.star.lang.XComponent]. Browse the ::cppu namespace in the C++ reference of the SDK or on udk.openoffice.org.
XServiceInfo
An UNO service implementation supports [IDL:com.sun.star.lang.XServiceInfo] providing information about its implementation name and supported services. The implementation name is a unique name referencing the specific implementation. In this case, my_module.my_sc_impl.MyService1 and my_module.my_sc_impl.MyService2 respectively. The implementation name is used later when registering the implementation into the simple_component.rdb registry used for [PRODUCTNAME]. It links a service name entry to one implementation, because there may be more than one implementation. Multiple implementations of the same service may have different characteristics, such as runtime behavior and memory footprint.
Our service instance has to support the [IDL:com.sun.star.lang.XServiceInfo] interface. This interface has three methods, and can be coded for one supported service as follows: [SOURCE:Components/CppComponent/service2_impl.cxx]
// XServiceInfo implementation
OUString MyService2Impl::getImplementationName()
throw (RuntimeException)
{
// unique implementation name
return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") );
}
sal_Bool MyService2Impl::supportsService( OUString const & serviceName )
throw (RuntimeException)
{
// this object only supports one service, so the test is simple
return serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("my_module.MyService2") );
}
Sequence< OUString > MyService2Impl::getSupportedServiceNames()
throw (RuntimeException)
{
return getSupportedServiceNames_MyService2Impl();
}
Implementing your own Interfaces
For the my_module.XSomething interface, add a string to be returned that informs the caller when methodOne() was called successfully . [SOURCE:Components/CppComponent/service2_impl.cxx]
OUString MyService2Impl::methodOne( OUString const & str )
throw (RuntimeException)
{
return OUString( RTL_CONSTASCII_USTRINGPARAM(
"called methodOne() of MyService2 implementation: ") ) + str;
}
Providing a Single Factory Using a Helper Method
C++ component libraries must export an external "C" function called component_getFactory() that supplies a factory object for the given implementation. Use ::cppu::component_getFactoryHelper() to create this function. The declarations for it are included through cppuhelper/implementationentry.hxx.
The component_getFactory() method appears at the end of the following listing. This method assumes that the component includes a static ::cppu::ImplementationEntry array s_component_entries[], which contains a number of function pointers. The listing shows how to write the component, so that the function pointers for all services of a multi-service component are correctly initialized. [SOURCE:Components/CppComponent/service2_impl.cxx]
#include <cppuhelper/implbase3.hxx> // "3" implementing three interfaces
#include <cppuhelper/factory.hxx>
#include <cppuhelper/implementationentry.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <my_module/XSomething.hpp>
using namespace ::rtl; // for OUString
using namespace ::com::sun::star; // for sdk interfaces
using namespace ::com::sun::star::uno; // for basic types
namespace my_sc_impl
{
class MyService2Impl : public ::cppu::WeakImplHelper3<
::my_module::XSomething, lang::XServiceInfo, lang::XInitialization >
{
OUString m_arg;
public:
// focus on three given interfaces,
// no need to implement XInterface, XTypeProvider, XWeak
// XInitialization will be called upon createInstanceWithArguments[AndContext]()
virtual void SAL_CALL initialize( Sequence< Any > const & args )
throw (Exception);
// XSomething
virtual OUString SAL_CALL methodOne( OUString const & str )
throw (RuntimeException);
// XServiceInfo
virtual OUString SAL_CALL getImplementationName()
throw (RuntimeException);
virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
throw (RuntimeException);
virtual Sequence< OUString > SAL_CALL getSupportedServiceNames()
throw (RuntimeException);
};
// Implementation of XSomething, XServiceInfo and XInitilization omitted here:
...
// component operations from service1_impl.cxx
extern Sequence< OUString > SAL_CALL getSupportedServiceNames_MyService1Impl();
extern OUString SAL_CALL getImplementationName_MyService1Impl();
extern Reference< XInterface > SAL_CALL create_MyService1Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () );
// component operations for MyService2Impl
static Sequence< OUString > getSupportedServiceNames_MyService2Impl()
{
Sequence<OUString> names(1);
names[0] = OUString(RTL_CONSTASCII_USTRINGPARAM("my_module.MyService2"));
return names;
}
static OUString getImplementationName_MyService2Impl()
{
return OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.my_sc_implementation.MyService2") );
}
Reference< XInterface > SAL_CALL create_MyService2Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyService2Impl() );
}
{
{
create_MyService1Impl, getImplementationName_MyService1Impl,
getSupportedServiceNames_MyService1Impl, ::cppu::createSingleComponentFactory,
0, 0
},
{
create_MyService2Impl, getImplementationName_MyService2Impl,
getSupportedServiceNames_MyService2Impl, ::cppu::createSingleComponentFactory,
0, 0
},
{ 0, 0, 0, 0, 0, 0 }
};
}
extern "C"
{
void * SAL_CALL component_getFactory(
sal_Char const * implName, lang::XMultiServiceFactory * xMgr,
registry::XRegistryKey * xRegistry )
{
return ::cppu::component_getFactoryHelper(
implName, xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
// getImplementationEnvironment and component_writeInfo are described later, we omit them here
...
}
The static variable s_component_entries defines a null-terminated array of entries concerning the service implementations of the shared library. A service implementation entry consists of function pointers for
object creation: create_MyServiceXImpl()
implementation name: getImplementationName_MyServiceXImpl()
supported service names: getSupportedServiceNames_MyServiceXImpl()
factory helper to be used: ::cppu::createComponentFactory()
The last two values are reserved for future use and therefore can be 0.
Write Registration Info Using a Helper Method
Use ::cppu::component_writeInfoHelper() to implement component_writeInfo(): This function is called by regcomp during the registration process. [ScOURCE:Components/simple_cpp_component/service2_impl.cxx]
extern "C" sal_Bool SAL_CALL component_writeInfo(
lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry )
{
return ::cppu::component_writeInfoHelper(
xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
Note that component_writeInfoHelper() uses the same array of ::cppu::ImplementationEntry structs as component_getFactory(),that is, s_component_entries.
Provide Implementation Environment
The function called component_getImplementationEnvironment() tells the shared library component loader which compiler was used to build the library. This information is required if different components have been compiled with different compilers. A specific C++-compiler is called an environment. If different compilers were used, the loader has to bridge interfaces from one compiler environment to another, building the infrastructure of communication between those objects. It is mandatory to have the appropriate C++ bridges installed into the UNO runtime. In most cases, the function mentioned above can be implemented this way: [SOURCE:Components/CppComponent/service2_impl.cxx]
extern "C" void SAL_CALL component_getImplementationEnvironment(
sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv )
{
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
The macro CPPU_CURRENT_LANGUAGE_BINDING_NAME is a C string defined by the compiling environment, if you use the SDK compiling environment. For example, when compiling with the Microsoft Visual C++ compiler, it defines to "msci", but when compiling with the GNU gcc 3, it defines to "gcc3".
Implementing without Helpers
In the following section, possible implementations without helpers are presented. This is useful if more interfaces are to be implemented than planned by the helper templates. The helper templates only allow up to ten interfaces. Also included in this section is how the core interfaces work.
XInterface Implementation
Object lifetime is controlled through the common base interface [IDL:com.sun.star.uno.XInterface] methods acquire() and release(). These are implemented using reference-counting, that is, upon each acquire(), the counter is incremented and upon each release(), it is decreased. On last decrement, the object dies. Programming in a thread-safe manner, the modification of this counter member variable is commonly performed by a pair of sal library functions called osl_incrementInterlockedcount() and osl_decrementInterlockedcount() (include osl/interlck.h). [SOURCE:Components/CppComponent/service1_impl.cxx]
Pay attention to the following important text section
Be aware of symbol conflicts when writing code. It is common practice to wrap code into a separate namespace, such as "my_sc_impl". The problem is that symbols may clash during runtime on Unix when your shared library is loaded.
namespace my_sc_impl
{
class MyService1Impl
...
{
oslInterlockedCount m_refcount;
public:
inline MyService1Impl() throw ()
: m_refcount( 0 )
{}
// XInterface
virtual Any SAL_CALL queryInterface( Type const & type )
throw (RuntimeException);
virtual void SAL_CALL acquire()
throw ();
virtual void SAL_CALL release()
throw ();
...
};
void MyService1Impl::acquire()
throw ()
{
// thread-safe incrementation of reference count
::osl_incrementInterlockedCount( &m_refcount );
}
void MyService1Impl::release()
throw ()
{
// thread-safe decrementation of reference count
if (0 == ::osl_decrementInterlockedCount( &m_refcount ))
{
delete this; // shutdown this object
}
}
In the queryInterface() method, interface pointers have to be provided to the interfaces of the object. That means, cast this to the respective pure virtual C++ class generated by the cppumaker tool for the interfaces. All supported interfaces must be returned, including inherited interfaces like XInterface. [SOURCE:Components/CppComponent/service1_impl.cxx]
Any MyService1Impl::queryInterface( Type const & type )
throw (RuntimeException)
{
if (type.equals( ::cppu::UnoType< Reference< XInterface > >::get()))
{
// return XInterface interface (resolve ambiguity caused by multiple inheritance from
// XInterface subclasses by casting to lang::XTypeProvider)
Reference< XInterface > x( static_cast< lang::XTypeProvider * >( this ) );
return makeAny( x );
}
if (type.equals( ::cppu::UnoType< Reference< lang::XTypeProvider > >::get()))
{
// return XInterface interface
Reference< XInterface > x( static_cast< lang::XTypeProvider * >( this ) );
return makeAny( x );
}
if (type.equals(( ::cppu::UnoType< Reference< lang::XServiceInfo > >::get()))
{
// return XServiceInfo interface
Reference< lang::XServiceInfo > x( static_cast< lang::XServiceInfo * >( this ) );
return makeAny( x );
}
if (type.equals( ::cppu::UnoType< Reference< ::my_module::XSomething > >::get()))
{
// return sample interface
Reference< ::my_module::XSomething > x( static_cast< ::my_module::XSomething * >( this ) );
return makeAny( x );
}
// querying for unsupported type
return Any();
}
XTypeProvider Implementation
When implementing the [IDL:com.sun.star.lang.XTypeProvider] interface, two methods have to be coded. The first one, getTypes() provides all implemented types of the implementation, excluding base types, such as [IDL:com.sun.star.uno.XInterface]. The second one, getImplementationId() provides a unique ID for this set of interfaces. A thread-safe implementation of the above mentioned looks like the following example: [SOURCE:Components/CppComponent/service1_impl.cxx]
Sequence< Type > MyService1Impl::getTypes()
throw (RuntimeException)
{
Sequence< Type > seq( 3 );
seq[ 0 ] = ::cppu::UnoType< (Reference< lang::XTypeProvider > >::get();
seq[ 1 ] = ::cppu::UnoType< (Reference< lang::XServiceInfo > >::get();
seq[ 2 ] = ::cppu::UnoType< (Reference< ::my_module::XSomething > >::get();
return seq;
}
Sequence< sal_Int8 > MyService1Impl::getImplementationId()
throw (RuntimeException)
{
static Sequence< sal_Int8 > * s_pId = 0;
if (! s_pId)
{
// create unique id
Sequence< sal_Int8 > id( 16 );
::rtl_createUuid( (sal_uInt8 *)id.getArray(), 0, sal_True );
// guard initialization with some mutex
::osl::MutexGuard guard( ::osl::Mutex::getGlobalMutex() );
if (! s_pId)
{
static Sequence< sal_Int8 > s_id( id );
s_pId = &s_id;
}
}
return *s_pId;
}
Pay attention to the following important text section
In general, do not acquire() mutexes when calling alien code if you do not know what the called code is doing. You never know what mutexes the alien code is acquiring which can lead to deadlocks. This is the reason, why the latter value (uuid) is created before the initialization mutex is acquired. After the mutex is successfully acquired, the value of s_pID is checked again and assigned if it has not been assigned before. This is the design pattern known as “double-checked locking.”
The above initialization of the implementation ID does not work reliably on certain platforms. See [CHAPTER:AdvancedUNO.DesignPatterns.DCLP] for better ways to implemnt this.
Providing a Single Factory
The function component_getFactory() provides a single object factory for the requested implementation, that is, it provides a factory that creates object instances of one of the service implementations. Using a helper from cppuhelper/factory.hxx, this is implemented quickly in the following code: [SOURCE:Components/CppComponent/service1_impl.cxx]
#include <cppuhelper/factory.hxx>
namespace my_sc_impl
{
...
static Reference< XInterface > SAL_CALL create_MyService1Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyService1Impl() );
}
static Reference< XInterface > SAL_CALL create_MyService2Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyService2Impl() );
}
}
extern "C" void * SAL_CALL component_getFactory(
sal_Char const * implName, lang::XMultiServiceFactory * xMgr, void * )
{
Reference< lang::XSingleComponentFactory > xFactory;
if (0 == ::rtl_str_compare( implName, "my_module.my_sc_impl.MyService1" ))
{
// create component factory for MyService1 implementation
OUString serviceName( RTL_CONSTASCII_USTRINGPARAM("my_module.MyService1") );
xFactory = ::cppu::createSingleComponentFactory(
::my_sc_impl::create_MyService1Impl,
OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService1") ),
Sequence< OUString >( &serviceName, 1 ) );
}
else if (0 == ::rtl_str_compare( implName, "my_module.my_sc_impl.MyService2" ))
{
// create component factory for MyService12 implementation
OUString serviceName( RTL_CONSTASCII_USTRINGPARAM("my_module.MyService2") );
xFactory = ::cppu::createSingleComponentFactory(
::my_sc_impl::create_MyService2Impl,
OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") ),
Sequence< OUString >( &serviceName, 1 ) );
}
if (xFactory.is())
xFactory->acquire();
return xFactory.get(); // return acquired interface pointer or null
}
In the example above, note the function ::my_sc_impl::create_MyService1Impl() that is called by the factory object when it needs to instantiate the class. A component context [IDL:com.sun.star.uno.XComponentContext] is provided to the function, which may be passed to the constructor of MyService1Impl.
Write Registration Info
The function component_writeInfo() is called by the shared library component loader upon registering the component into a registry database file (.rdb). The component writes information about objects it can instantiate into the registry when it is called by regcomp. [SOURCE:Components/CppComponent/service1_impl.cxx]
extern "C" sal_Bool SAL_CALL component_writeInfo(
lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry )
{
if (xRegistry)
{
try
{
// implementation of MyService1A
Reference< registry::XRegistryKey > xKey(
xRegistry->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.my_sc_impl.MyService1/UNO/SERVICES") ) ) );
// subkeys denote implemented services of implementation
xKey->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.MyService1") ) );
// implementation of MyService1B
xKey = xRegistry->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.my_sc_impl.MyService2/UNO/SERVICES") ) );
// subkeys denote implemented services of implementation
xKey->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.MyService2") ) );
return sal_True; // success
}
catch (registry::InvalidRegistryException &)
{
// function fails if exception caught
}
}
return sal_False;
}
Storing the Service Manager for Further Use
The single factories expect a static create_<ImplementationClass>() function. For instance, create_MyService1Impl()takes a reference to the component context and instantiates the implementation class using new ImplementationClass(). A constructor can be written for <ImplementationClass> that expects a reference to an [IDL:com.sun.star.uno.XComponentContext] and stores the reference in the instance for further use.
static Reference< XInterface > SAL_CALL create_MyService2Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
// passing the component context to the constructor of MyService2Impl
return static_cast< lang::XTypeProvider * >( new MyService2Impl( xContext ) );
}
Create Instance with Arguments
If the service should be raised passing arguments through [IDL:com.sun.star.lang.XMultiComponentFactory:createInstanceWithArgumentsAndContext]() and [IDL:com.sun.star.lang.XMultiServiceFactory:createInstanceWithArguments](), it has to implement the interface [IDL:com.sun.star.lang.XInitialization]. The second service my_module.MyService2 implements it, expecting a single string as an argument. [SOURCE:Components/CppComponent/service2_impl.cxx]
// XInitialization implementation
void MyService2Impl::initialize( Sequence< Any > const & args )
throw (Exception)
{
if (1 != args.getLength())
{
throw lang::IllegalArgumentException(
OUString( RTL_CONSTASCII_USTRINGPARAM("give a string instanciating this component!") ),
(::cppu::OWeakObject *)this, // resolve to XInterface reference
0 ); // argument pos
}
if (! (args[ 0 ] >>= m_arg))
{
throw lang::IllegalArgumentException(
OUString( RTL_CONSTASCII_USTRINGPARAM("no string given as argument!") ),
(::cppu::OWeakObject *)this, // resolve to XInterface reference
0 ); // argument pos
}
}
Multiple Components in One Dynamic Link Library
The construction of C++ components allows putting as many service implementations into a component file as desired. Ensure that the component operations are implemented in such a way that component_writeInfo() and component_getFactory() handle all services correctly. Refer to the sample component simple_component to see an example on how to implement two services in one link library.
Building and Testing C++ Components
Build Process
For details about building component code, see the gnu makefile. It uses a number of platform dependent variables used in the SDK that are included from <SDK>/settings/settings.mk. For simplicity, details are omitted here, and the build process is just sketched in eight steps:
The UNOIDL compiler compiles the .idl file some.idl into an urd file.
The resulting binary .urd files are merged into a new simple_component.rdb.
The tool xml2cmp parses the xml component description simple_component.xml for types needed for compiling. This file describes the service implementation(s) for deployment, such as the purpose of the implementation(s) and used types. Visit http://udk.openoffice.org/common/man/module_description.html for details about the syntax of these XML files.
The types parsed in step 3 are passed to cppumaker, which generates the appropriate header pairs into the output include directory using simple_component.rdb and the [PRODUCTNAME] type library types.rdb that is stored in the program directory of your [PRODUCTNAME] installation.
Tip graphics marks a hint section in the text
For your own component you can simplify step 3 and 4, and pass the types used by your component to cppumaker using the -T option.
The source files service1_impl.cxx and service2_impl.cxx are compiled.
The shared library is linked out of object files, linking dynamically to the UNO base libraries sal, cppu and cppuhelper. The shared library's name is libsimple_component.so on Unix and simple_component.dll on Windows.
Pay attention to the following important text section
In general, the shared library component should limit its exports to only the above mentioned functions (prefixed with component_) to avoid symbol clashes on Unix. In addition, for the gnu gcc3 C++ compiler, it is necessary to export the RTTI symbols of exceptions, too.
The shared library component is registered into simple_component.rdb. This can also be done manually running
$ regcomp -register -r simple_component.rdb -c simple_component.dll
Test Registration and Use
The component's registry simple_component.rdb has entries for the registered service implementations. If the library is registered successfully, run:
$ regview simple_component.rdb
The result should look similar to the following:
/
/ UCR
/ my_module
/ XSomething
... interface information ...
/ IMPLEMENTATIONS
/ my_module.my_sc_impl.MyService2
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 34
Data = "com.sun.star.loader.SharedLibrary"
/ SERVICES
/ my_module.MyService2
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 21
Data = "simple_component.dll"
/ my_module.my_sc_impl.MyService1
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 34
Data = "com.sun.star.loader.SharedLibrary"
/ SERVICES
/ my_module.MyService1
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 21
Data = "simple_component.dll"
/ SERVICES
/ my_module.MyService1
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 40
Len = 1
Data = 0 = "my_module.my_sc_impl.MyService1"
/ my_module.MyService2
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 40
Len = 1
Data = 0 = "my_module.my_sc_impl.MyService2"
[PRODUCTNAME] recognizes registry files being inserted into the unorc file (on Unix, uno.ini on Windows) in the program directory of your [PRODUCTNAME] installation. Extend the types and services in that file by simple_component.rdb. The given file has to be an absolute file URL, but if the rdb is copied to the [PRODUCTNAME] program directory, a $ORIGIN macro can be used, as shown in the following unorc file:
[Bootstrap]
UNO_TYPES=$ORIGIN/types.rdb $ORIGIN/simple_component.rdbUNO_SERVICES=$ORIGIN/services.rdb $ORIGIN/simple_component.rdb
Second, when running [PRODUCTNAME], extend the PATH (Windows) or LD_LIBRARY_PATH (Unix), including the output path of the build, so that the loader finds the component. If the shared library is copied to the program directory or a link is created inside the program directory (Unix only), do not extend the path.
Launching the test component inside a [PRODUCTNAME] Basic script is simple to do, as shown in the following code:
Sub Main
REM calling service1 impl
mgr = getProcessServiceManager()
o = mgr.createInstance("my_module.MyService1")
MsgBox o.methodOne("foo")
MsgBox o.dbg_supportedInterfaces
REM calling service2 impl
dim args( 0 )
args( 0 ) = "foo"
o = mgr.createInstanceWithArguments("my_module.MyService2", args())
MsgBox o.methodOne("bar")
MsgBox o.dbg_supportedInterfaces
End Sub
This procedure instantiates the service implementations and performs calls on their interfaces. The return value of the methodOne() call is brought up in message boxes. The Basic object property dbg_supportedInterfaces retrieves its information through the [IDL:com.sun.star.lang.XTypeProvider] interfaces of the objects.
Integrating Components into [PRODUCTNAME]
If a component needs to be called from the [PRODUCTNAME] user interface, it must be able to take part in the communication between the UI layer and the application objects. [PRODUCTNAME] uses command URLs for this purpose. When a user chooses an item in the user interface, a command URL is dispatched to the application framework and processed in a chain of responsibility until an object accepts the command and executes it, thus consuming the command URL. This mechanism is known as the dispatch framework, it is covered in detail in chapter [CHAPTER:OfficeDev.AppEnv.UsingDispatch].
From version [OO1.1], [PRODUCTNAME] provides user interface support for custom components by two basic mechanisms:
Components can be enabled to process command URLs. There are two ways to accomplish this. You can either make them a protocol handler for command URLs or integrate them into the job execution environment of [PRODUCTNAME]. The protocol handler technique is simple, but it can only be used with command URLs in the dispatch framework. A component for the job execution environment can be used with or without command URLs, and has comprehensive support when it comes to configuration, job environment, and lifetime issues.
The user interface can be adjusted to new components. On the one hand, you can add new menus and toolbar items and configure them to send the command URLs needed for your component. On the other hand, it is possible to disable existing commands. All this is possible by adding certain files to the extensionUNO package distribution. When users of your component deploy theinstall the extensionpackage into an individual or a network [PRODUCTNAME] installation, the GUI is adjusted automatically.
The left side of Illustration 4.2 shows the two possibilities for processing command URLs: either custom protocol handlers or the specialized job protocol. On the right, you see the job execution environment, which is used by the job protocol, but can also be used without command URLs from any source code.
Overview graphics of processing command URLs in the the context of a job execution environment
Illustration 4.2: Processing command URLs and the job execution environment
This section describes how to use these mechanisms. It discusses protocol handlers and jobs, then describes how to customize the [PRODUCTNAME] user interface for components.
Protocol Handler
The dispatch framework binds user interface controls, such as menu or toolbar items, to the functionality of [PRODUCTNAME]. Every function that is reachable in the user interface is described by a command URL and corresponding parameters.
The protocol handler mechanism is an API that enables programmers to add arbitrary URL schemas to the existing set of command URLs by writing additional protocol handlers for them. Such a protocol handler must be implemented as a UNO component and registered in the [PRODUCTNAME] configuration for the new URL schema.
Overview
To issue a command URL, the first step is to locate a dispatch object that is responsible for the URL. Start with the frame that contains the document for which the command is meant. Its interface method [IDL:com.sun.star.frame.XDispatchProvider:queryDispatch]()is called with a URL and special search parameters to locate the correct target. This request is passed through the following instances:
disabling commands
Checks if command is on the list of disabled commands, described in [CHAPTER:Components.Integrating.DisableCommands]
interception
Intercepts command and re-routes it, described in [CHAPTER:OfficeDev.AppEnv.UsingDispatch.Interception]
targeting
Determines target frame for command, described in [CHAPTER:OfficeDev.AppEnv.HandleDocuments.Loading.TargetFrame]
controller
Lets the controller of the frame try to handle the command, described in [CHAPTER:OfficeDev.AppEnv.UsingDispatch.Chain]
protocol handler
Determines if there is a custom handler for the command, described in this section
interpret as loadable content
Loads content from file, described in [CHAPTER:OfficeDev.AppEnv.HandleDocuments.Loading.URLParam]. Generally contents are loaded into a frame by a [IDL:com.sun.star.frame.FrameLoader], but if a content (e.g. a sound) needs no frame, a [IDL:com.sun.star.frame.ContentHandler] service is used, which needs no target frame for its operation.
The list shows that the protocol handler will only be used if the URL has not been called before. Because targeting has already been done, it is clear that the command will run in the located target frame environment, which is usually "_self".
Note graphics marks a special text section
The target "_blank" cannot be used for a protocol handler. Since "_blank" leads to the creation of a new frame for a component, there would be no component yet for the protocol handler to work with.
A protocol handler decides by itself if it returns a valid dispatch object, that is, it is asked to agree with the given request by the dispatch framework. If a dispatch object is returned, the requester can use it to dispatch the URL by calling its dispatch() method.
Implementation
[TOPIC:com.sun.star.frame.ProtocolHandler;com.sun.star.frame.XDispatchProvider]A protocol handler implementation must follow the service definition [IDL:com.sun.star.frame.ProtocolHandler]. At least the interface [IDL:com.sun.star.frame.XDispatchProvider] must be supported.
UML diagram showing the com.sun.star.frame.ProtocolHandler service
Illustration 4.3: Protocol handler
The interface XDispatchProvider supports two methods:
XDispatch queryDispatch( [in] ::com::sun::star::util::URL URL,
[in] string TargetFrameName,
[in] long SearchFlags )
sequence< XDispatch > queryDispatches( [in] sequence< DispatchDescriptor > Requests )
The protocol handler is asked for its agreement to execute a given URL by a call to the interface method [IDL:com.sun.star.frame.XDispatchProvider:queryDispatch](). The incoming URL should be parsed and validated. If the URL is valid and the protocol handler is able to handle it, it should return a dispatch object, thus indicating that it accepts the request.
The dispatch object must support the interface [IDL:com.sun.star.frame.XDispatch] with the methods
[oneway] void dispatch( [in] ::com::sun::star::util::URL URL,
[in] sequence< ::com::sun::star::beans::PropertyValue > Arguments )
addStatusListener [oneway] void addStatusListener( [in] XStatusListener Control,
[in] ::com::sun::star::util::URL URL )
removeStatusListener [oneway] void removeStatusListener( [in] XStatusListener Control,
[in] ::com::sun::star::util::URL URL )
Optionally, the dispatch object can support the interface [IDL:com.sun.star.frame.XNotifyingDispatch], which derives from XDispatch and introduces a new method dispatchWithNotification(). This interface is preferred if it is present.
[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);
A basic protocol handler is free to implement XDispatch itself, so it can simply return itself in the queryDispatch() implementation. But it is advisable to return specialized helper dispatch objects instead of the protocol handler instance. This helps to decrease the complexity of status updates. It is easier to notify status listeners for a single-use dispatch object instead of multi-use dispatch objects, which have to distinguish the URLs given in addStatusListener() all the time.
Tip graphics marks a hint section in the text
To supply the UI with status information for a command, it is required to call back a [IDL:com.sun.star.frame.XStatusListener] during its registration immediately, for example:
public void addStatusListener(XStatusListener xControl, URL aURL) { FeatureStateEvent aState = new FeatureStateEvent(); aState.FeatureURL = aURL; aState.IsEnabled = true; aState.State = Boolean.TRUE; xControl.statusChanged(aState); m_lListenerContainer.add(xControl);}
A protocol handler can support the interface [IDL:com.sun.star.lang.XInitialization] if it wants to be initialized with a [IDL:com.sun.star.frame.Frame] environment to work with. XInitialization contains one method:
void initialize( [in] sequence< any > aArguments )
A protocol handler is generally used in a well known [IDL:com.sun.star.frame.Frame] context, therefore the dispatch framework always passes this frame context through initialize() as the first argument, if XInitialization is present. Its [IDL:com.sun.star.frame.XFrame] interface provides access to the controller, from which you can get the document model and have a good starting point to work with the document.
Illustration 4.3 shows how to get to the controller and the document model from an XFrame interface. The chapter [CHAPTER:OfficeDev.AppEnv.UsingCompFramework] describes the usage of frames, controllers and models in more detail.
UML diagram showing the frame-controller-model organisation
Illustration 4.4: Frame-controller-model organization
Note graphics marks a special text section
A protocol handler can be implemented as a singleton, but this poses multithreading difficulties. In a multithreaded environment it is most unlikely that the initial frame context matches every following dispatch request. So you have to be prepared for calls to initialize() by multiple threads for multiple frames. A dispatch object can also be used more then once, but must be bound to the target frame that was specified in the original queryDispatch()call. A change of the frame context can cause trouble if the protocol handler returns itself as a dispatch object. A protocol handler singleton must return new dispatch objects for every request, which has to be initialized with the current context of the protocol handler, and you have to synchronize between initialize() and queryDispatch(). The protocol handler would have to serve as a kind of factory for specialized dispatch objects. You can avoid these problems, if you write your protocol handler as a multi-instance service.
The opportunity to deny a queryDispatch() call allows you to register a protocol handler for a URL schema using wildcards, and to accept only a subset of all possible URLs. That way the handler object can validate incoming URLs and reject them if they appear to be invalid. However, this feature should not be used to register different protocol handlers for the same URL schema and accept different subsets by different handler objects, because it would be very difficult to avoid ambiguities.
Since a protocol handler is a UNO component, it must contain the component operations needed by a UNO service manager. These operations are certain static methods in Java or export functions in C++. It also has to implement the core interfaces used to enable communication with UNO and the application environment. For more information on the component operations and core interfaces, please see [CHAPTER:Components.Architecture] and [CHAPTER:Components.CoreInterfaces].
Java Protocol Handler - vnd.sun.star.framework.ExampleHandler
The following example shows a simple protocol handler implementation in Java. For simplicity, the component operations are omitted.
// imports
#import com.sun.star.beans.*;
#import com.sun.star.frame.*;
#import com.sun.star.uno.*;
#import com.sun.star.util.*;
// definition
public class ExampleHandler implements com.sun.star.frame.XDispatchProvider,
com.sun.star.lang.XInitialization {
// member
/** points to the frame context in which this handler runs, is set in initialize()*/
private com.sun.star.frame.XFrame m_xContext;
// Dispatch object as inner class
class OwnDispatch implements com.sun.star.frame.XDispatch {
/** the target frame, in which context this dispatch must work */
private com.sun.star.frame.XFrame m_xContext;
/** describe the function of this dispatch.
* Because a URL can contain e.g. optional arguments
* this URL means the main part of such URL sets only. */
private com.sun.star.util.URL m_aMainURL;
/** contains all interested status listener for this dispatch */
private java.lang.HashMap m_lListener;
/** take over all neccessary parameters from outside. */
public OwnDispatch(com.sun.star.frame.XFrame xContext, com.sun.star.util.URL aMainURL) {
m_xContext = xContext;
m_aMainURL = aMainURL;
}
/** execute the functionality, which is described by this URL.
*
* @param aURL
* this URL can describe the main function, we already know;
* but it can specify a sub function too! But queryDispatch()
* and dispatch() are used in a generic way ...
* m_aMainURL and aURL will be the same.
*
* @param lArgs
* optional arguments for this request
*/
public void dispatch(com.sun.star.util.URL aURL, com.sun.star.beans.PropertyValue lArgs)
throws com.sun.star.uno.RuntimeException {
// ... do function
// ... inform listener if neccessary
}
/** register a new listener and bind it toe given URL.
*
* Note: Because the listener does not know the current state
* and may nobody change it next time, it is neccessary to inform it
* immediatly about this current state. So the listener is up to date.
*/
public void addStatusListener(com.sun.star.frame.XStatusListener xListener,
com.sun.star.util.URL aURL) throws com.sun.star.uno.RuntimeException {
// ... register listener for given URL
// ... inform it immediatly about current state!
xListener.statusChanged(...);
}
/** deregister a listener for this URL. */
public void removeStatusListener(com.sun.star.frame.XStatusListener xListener,
com.sun.star.util.URLaURL) throws com.sun.star.uno.RuntimeException {
// ... deregister listener for given URL
}
}
/** set the target frame reference as context for all following dispatches. */
public void initialize(com.sun.star.uno.Any[] lContext) {
m_xContext = (com.sun.star.frame.XFrame)com.sun.star.uno.AnyConverter.toObject(lContext[0]);
}
/** should return a valid dispatch object for the given URL.
*
* In case the URL is not valid an empty reference can be returned.
* The parameter sTarget and nFlags can be ignored. The will be "_self" and 0
* everytime.
*/
public com.sun.star.frame.XDispatch queryDispatch(com.sun.star.util.URL aURL,
java.lang.String sTarget, int nFlags ) throws com.sun.star.uno.RuntimeException {
// check if given URL is valid for this protocol handler
if (!aURL.Main.startsWith("myProtocol_1://") && !aURL.Main.startsWith("myProtocol_2://"))
return null;
// and return a specialized dispatch object
// Of course "return this" would be possible too ...
return (com.sun.star.frame.XDispatch)(new OwnDispatch(m_xContext, aURL));
}
/** optimized API call for remote.
*
* It should be forwarded to queryDispatch() for every request item of the
* given DispatchDescriptor list.
*
* But note: it is not allowed to pack the return list of dispatch objects.
* Every request in source list must match to a reference (null or valid) in
* the destination list!
*/
public com.sun.star.frame.XDispatch[] queryDispatches(
com.sun.star.frame.DispatchDescriptor[] lRequests) throws com.sun.star.uno.RuntimeException {
int c = lRequests.length;
com.sun.star.frame.XDispatch[] lDispatches = new com.sun.star.frame.XDispatch[c];
for (int i=0; i<c; ++i)
lDispatches[i] = queryDispatch(lRequests[i].FeatureURL,
lRequests[i].FrameName, lRequests[i].SearchFlags);
return lDispatches;
}
}
C++ Protocol Handler - org.openoffice.Office.addon.example
The next example shows a protocol handler in C++. The section [CHAPTER:Components.Integrating.UIAddOns] below will integrate this example handler into the graphical user interface of [PRODUCTNAME].
The following code shows the UNO component operations that must be implemented in a C++ protocol handler example. The three C functions return vital information to the UNO environment:
component_getImplementationEnvironment()tells the shared library component loader which compiler was used to build the library.
component_writeInfo()is called during the registration process by the registration tool regcomp, or indirectly when you apply use the Extension Manager.pkgchk
component_getFactory()provides a single service factory for the requested implementation. This factory can be asked to create an arbitrary number of instances for only one service specification, therefore it is called a single service factory, as opposed to a multi-service factory, where you can order instances for many different service specifications. (A single service factory has nothing to do with a singleton).
#include <stdio.h>
#ifndef _RTL_USTRING_HXX_
#include <rtl/ustring.hxx>
#endif
#ifndef _CPPUHELPER_QUERYINTERFACE_HXX_
#include <cppuhelper/queryinterface.hxx> // helper for queryInterface() impl
#endif
#ifndef _CPPUHELPER_FACTORY_HXX_
#include <cppuhelper/factory.hxx> // helper for component factory
#endif
// generated c++ interfaces
#ifndef _COM_SUN_STAR_LANG_XSINGLESERVICEFACTORY_HPP_
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_LANG_XMULTISERVICEFACTORY_HPP_
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_LANG_XSERVICEINFO_HPP_
#include <com/sun/star/lang/XServiceInfo.hpp>
#endif
#ifndef _COM_SUN_STAR_REGISTRY_XREGISTRYKEY_HPP_
#include <com/sun/star/registry/XRegistryKey.hpp>
#endif
// include our specific addon header to get access to functions and definitions
#include <addon.hxx>
using namespace ::rtl;
using namespace ::osl;
using namespace ::cppu;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::registry;
//##################################################################################################
//#### EXPORTED ####################################################################################
//##################################################################################################
/**
* Gives the environment this component belongs to.
*/
extern "C" void SAL_CALL component_getImplementationEnvironment(const sal_Char ** ppEnvTypeName, uno_Environment ** ppEnv)
{
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
/**
* This function creates an implementation section in the registry and another subkey
*
* for each supported service.
* @param pServiceManager the service manager
* @param pRegistryKey the registry key
*/
extern "C" sal_Bool SAL_CALL component_writeInfo(void * pServiceManager, void * pRegistryKey) {
sal_Bool result = sal_False;
if (pRegistryKey) {
try {
Reference< XRegistryKey > xNewKey(
reinterpret_cast< XRegistryKey * >( pRegistryKey )->createKey(
OUString( RTL_CONSTASCII_USTRINGPARAM("/" IMPLEMENTATION_NAME "/UNO/SERVICES")) ) );
const Sequence< OUString > & rSNL = Addon_getSupportedServiceNames();
const OUString * pArray = rSNL.getConstArray();
for ( sal_Int32 nPos = rSNL.getLength(); nPos--; )
xNewKey->createKey( pArray[nPos] );
return sal_True;
}
catch (InvalidRegistryException &) {
// we should not ignore exceptions
}
}
return result;
}
/**
* This function is called to get service factories for an implementation.
*
* @param pImplName name of implementation
* @param pServiceManager a service manager, need for component creation
* @param pRegistryKey the registry key for this component, need for persistent data
* @return a component factory
*/
extern "C" void * SAL_CALL component_getFactory(const sal_Char * pImplName,
void * pServiceManager, void * pRegistryKey) {
void * pRet = 0;
if (rtl_str_compare( pImplName, IMPLEMENTATION_NAME ) == 0) {
Reference< XSingleServiceFactory > xFactory(createSingleFactory(
reinterpret_cast< XMultiServiceFactory * >(pServiceManager),
OUString(RTL_CONSTASCII_USTRINGPARAM(IMPLEMENTATION_NAME)),
Addon_createInstance,
Addon_getSupportedServiceNames()));
if (xFactory.is()) {
xFactory->acquire();
pRet = xFactory.get();
}
}
return pRet;
}
//##################################################################################################//#### Helper functions for the implementation of UNO component interfaces #########################//##################################################################################################
::rtl::OUString Addon_getImplementationName()
throw (RuntimeException) {
return ::rtl::OUString ( RTL_CONSTASCII_USTRINGPARAM ( IMPLEMENTATION_NAME ) );
}
sal_Bool SAL_CALL Addon_supportsService( const ::rtl::OUString& ServiceName )
throw (RuntimeException)
{
return ServiceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( SERVICE_NAME ) );
}
Sequence< ::rtl::OUString > SAL_CALL Addon_getSupportedServiceNames()
throw (RuntimeException)
{
Sequence < ::rtl::OUString > aRet(1);
::rtl::OUString* pArray = aRet.getArray();
pArray[0] = ::rtl::OUString ( RTL_CONSTASCII_USTRINGPARAM ( SERVICE_NAME ) );
return aRet;
}
Reference< XInterface > SAL_CALL Addon_createInstance( const Reference< XMultiServiceFactory > & rSMgr)
throw( Exception )
{
return (cppu::OWeakObject*) new Addon( rSMgr );
}
The C++ protocol handler in the example has the implementation name org.openoffice.Office.addon.example. It supports the URL protocol schema org.openoffice.Office.addon.example: and provides three different URL commands: Function1, Function2 and Help.
The protocol handler implements the [IDL:com.sun.star.frame.XDispatch] interface, so it can return a reference to itself when it is queried for a dispatch object that matches the given URL.
The implementation of the dispatch() method below shows how the supported commands are routed inside the protocol handler. Based on the path part of the URL, a simple message box displays which function has been called. The message box is implemented using the UNO toolkit and uses the container windows of the given frame as parent window.
#ifndef _Addon_HXX
#include <addon.hxx>
#endif
#ifndef _OSL_DIAGNOSE_H_
#include <osl/diagnose.h>
#endif
#ifndef _RTL_USTRING_HXX_
#include <rtl/ustring.hxx>
#endif
#ifndef _COM_SUN_STAR_LANG_XMULTISERVICEFACTORY_HPP_
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_BEANS_PROPERTYVALUE_HPP_
#include <com/sun/star/beans/PropertyValue.hpp>
#endif
#ifndef _COM_SUN_STAR_FRAME_XFRAME_HPP_
#include <com/sun/star/frame/XFrame.hpp>
#endif
#ifndef _COM_SUN_STAR_FRAME_XCONTROLLER_HPP_
#include <com/sun/star/frame/XController.hpp>
#endif
#ifndef _COM_SUN_STAR_AWT_XTOOLKIT_HPP_
#include <com/sun/star/awt/XToolkit.hpp>
#endif
#ifndef _COM_SUN_STAR_AWT_XWINDOWPEER_HPP_
#include <com/sun/star/awt/XWindowPeer.hpp>
#endif
#ifndef _COM_SUN_STAR_AWT_WINDOWATTRIBUTE_HPP_
#include <com/sun/star/awt/WindowAttribute.hpp>
#endif
#ifndef _COM_SUN_STAR_AWT_XMESSAGEBOX_HPP_
#include <com/sun/star/awt/XMessageBox.hpp>
#endif
using rtl::OUString;
using namespace com::sun::star::uno;
using namespace com::sun::star::frame;
using namespace com::sun::star::awt;
using com::sun::star::lang::XMultiServiceFactory;
using com::sun::star::beans::PropertyValue;
using com::sun::star::util::URL;
// This is the service name an Add-On has to implement
#define SERVICE_NAME "com.sun.star.frame.ProtocolHandler"
/**
* Show a message box with the UNO based toolkit
*/static void ShowMessageBox(const Reference< XToolkit >& rToolkit, const Reference< XFrame >& rFrame, const OUString& aTitle, const OUString& aMsgText)
{
if ( rFrame.is() && rToolkit.is() )
{
// describe window properties. WindowDescriptor aDescriptor; aDescriptor.Type = WindowClass_MODALTOP ; aDescriptor.WindowServiceName = OUString( RTL_CONSTASCII_USTRINGPARAM( "infobox" )); aDescriptor.ParentIndex = -1 ; aDescriptor.Parent = Reference< XWindowPeer >( rFrame->getContainerWindow(),
UNO_QUERY ) ; aDescriptor.Bounds = Rectangle(0,0,300,200) ; aDescriptor.WindowAttributes = WindowAttribute::BORDER | WindowAttribute::MOVEABLE | WindowAttribute::CLOSEABLE;
Reference< XWindowPeer > xPeer = rToolkit->createWindow( aDescriptor ); if ( xPeer.is() ) { Reference< XMessageBox > xMsgBox( xPeer, UNO_QUERY ); if ( xMsgBox.is() ) { xMsgBox->setCaptionText( aTitle ); xMsgBox->setMessageText( aMsgText ); xMsgBox->execute(); } } }
}
//##################################################################################################//#### Implementation of the ProtocolHandler and Dispatch Interfaces ###################//##################################################################################################
// XInitialization
/**
* Called by the Office framework.
* We store the context information
* given, like the frame we are bound to, into our members.
*/void SAL_CALL Addon::initialize( const Sequence< Any >& aArguments ) throw ( Exception, RuntimeException)
{
Reference < XFrame > xFrame;
if ( aArguments.getLength() )
{
aArguments[0] >>= xFrame;
mxFrame = xFrame;
}
// Create the toolkit to have access to it later
mxToolkit = Reference< XToolkit >( mxMSF->createInstance(
OUString( RTL_CONSTASCII_USTRINGPARAM(
"com.sun.star.awt.Toolkit" ))), UNO_QUERY );
}
// XDispatchProvider
/**
* Called by the Office framework.
* We are ask to query the given URL and return a dispatch object if the URL
* contains an Add-On command.
*/Reference< XDispatch > SAL_CALL Addon::queryDispatch( const URL& aURL, const ::rtl::OUString& sTargetFrameName, sal_Int32 nSearchFlags )
throw( RuntimeException )
{
Reference < XDispatch > xRet;
if ( aURL.Protocol.compareToAscii("org.openoffice.Office.addon.example:") == 0 )
{
if ( aURL.Path.compareToAscii( "Function1" ) == 0 )
xRet = this;
else if ( aURL.Path.compareToAscii( "Function2" ) == 0 )
xRet = this;
else if ( aURL.Path.compareToAscii( "Help" ) == 0 )
xRet = this;
}
return xRet;
}
/**
* Called by the Office framework.
* We are ask to query the given sequence of URLs and return dispatch objects if the URLs
* contain Add-On commands.
*/Sequence < Reference< XDispatch > > SAL_CALL Addon::queryDispatches(
const Sequence < DispatchDescriptor >& seqDescripts )
throw( RuntimeException )
{
sal_Int32 nCount = seqDescripts.getLength();
Sequence < Reference < XDispatch > > lDispatcher( nCount );
for( sal_Int32 i=0; i<nCount; ++i )
lDispatcher[i] = queryDispatch( seqDescripts[i].FeatureURL, seqDescripts[i].FrameName, seqDescripts[i].SearchFlags );
return lDispatcher;
}
// XDispatch
/**
* Called by the Office framework.
* We are ask to execute the given Add-On command URL.
*/
void SAL_CALL Addon::dispatch( const URL& aURL, const Sequence < PropertyValue >& lArgs ) throw (RuntimeException)
{
if ( aURL.Protocol.compareToAscii("org.openoffice.Office.addon.example:") == 0 )
{
if ( aURL.Path.compareToAscii( "Function1" ) == 0 )
{
ShowMessageBox( mxToolkit, mxFrame,
OUString( RTL_CONSTASCII_USTRINGPARAM( "SDK Add-On example" )),
OUString( RTL_CONSTASCII_USTRINGPARAM( "Function 1 activated" )) );
}
else if ( aURL.Path.compareToAscii( "Function2" ) == 0 )
{
ShowMessageBox( mxToolkit, mxFrame,
OUString( RTL_CONSTASCII_USTRINGPARAM( "SDK Add-On example" )),
OUString( RTL_CONSTASCII_USTRINGPARAM( "Function 2 activated" )) );
}
else if ( aURL.Path.compareToAscii( "Help" ) == 0 )
{
// Show info box
ShowMessageBox( mxToolkit, mxFrame,
OUString( RTL_CONSTASCII_USTRINGPARAM( "About SDK Add-On example" )),
OUString( RTL_CONSTASCII_USTRINGPARAM( "This is the SDK Add-On example")));
}
}
}
/**
* Called by the Office framework.
* We are asked to store a status listener for the given URL.
*/
void SAL_CALL Addon::addStatusListener( const Reference< XStatusListener >& xControl, const URL& aURL ) throw (RuntimeException)
{
}
/**
* Called by the Office framework.
* We are asked to remove a status listener for the given URL.
*/
void SAL_CALL Addon::removeStatusListener( const Reference< XStatusListener >& xControl,
const URL& aURL )
throw (RuntimeException)
{
}
//##################################################################################################//#### Implementation of the recommended/mandatory interfaces of a UNO component ###################//##################################################################################################
// XServiceInfo
::rtl::OUString SAL_CALL Addon::getImplementationName( )
throw (RuntimeException)
{
return Addon_getImplementationName();
}
sal_Bool SAL_CALL Addon::supportsService( const ::rtl::OUString& rServiceName )
throw (RuntimeException)
{
return Addon_supportsService( rServiceName );
}
Sequence< ::rtl::OUString > SAL_CALL Addon::getSupportedServiceNames( )
throw (RuntimeException)
{
return Addon_getSupportedServiceNames();
}
Configuration
A protocol handler needs configuration entries, which provide the framework with the necessary information to find the handler. The schema of the configuration branch org.openoffice.Office.ProtocolHandler defines how to bind handler instances to their URL schemas:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE oor:component-schema SYSTEM "../../../../component-schema.dtd">
<oor:component-schema xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="ProtocolHandler" oor:package="org.openoffice.Office" xml:lang="en-US">
<templates>
<group oor:name="Handler">
<prop oor:name="Protocols" oor:type="oor:string-list"/>
</group>
</templates>
<component>
<set oor:name="HandlerSet" oor:node-type="Handler"/>
</component>
</oor:component-schema>
Each set node entry specifies one protocol handler, using its UNO implementation name. The only property it has is the Protocols item. Its type must be [string-list] and it contains a list of URL schemas bound to the handler. Wildcards are allowed, otherwise the entire string must match the dispatched URL.
Configuration for vnd.sun.star.framework.ExampleHandler
The following example ProtocolHandler.xcu contains the protocol handler configuration for the example's Java protocol handler:
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data oor:name="ProtocolHandler" oor:package="org.openoffice.Office" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="HandlerSet">
<node oor:name="vnd.sun.star.framework.ExampleHandler" oor:op="replace">
<prop oor:name="Protocols">
<value>myProtocol_1://* myProtocol_2://*</value>
</prop>
</node>
</node>
</oor:component-data>
The example adds two new URL protocols using wildcards:
myProtocol_1://*
myProtocol_2://*
Both protocols are bound to the handler implementation vnd.sun.star.framework.ExampleHandler. Note that this must be the implementation name of the handler, not the name of the service [IDL:com.sun.star.frame.ProtocolHandler] it implements. Because all implementations of the service [IDL:com.sun.star.frame.ProtocolHandler] share the same UNO service name, you cannot use this name in the configuration files.
To prevent ambiguous implementation names, the following naming schema for implementation names is frequently used:
vnd.<namespace_of_company>.<namespace_of_implementation>.<class_name>
e.g. vnd.sun.star.framework.ExampleHandler<namespace_of_company>= sun.star<namespace_of_implementation>= framework<class_name>= ExampleHandler
An alternative would be the naming convention proposed in [CHAPTER:Components.CoreInterfaces.XServiceInfo]:
<namespace_of_creator>.comp.<namespace_of_implementation>.<class_name>
e.g. org.openoffice.comp.framework.OProtocolHandler
All of these conventions are proposals; what matters is:
use the implementation name in the configuration file, not the general service name "com.sun.star.frame.ProtocolHandler"
be careful to choose an implementation name that is likely to be unique, and be aware that your handler ceases to function when another developer adds a handler with the same name.
Configuration for org.openoffice.Office.addon.example
The following ProtocolHandler.xcu file configures the example's C++ protocol handler with the implementation name org.openoffice.Office.addon.example in the configuration branch org.openoffice.Office.ProtocolHandler following the same schema.
<?xml version="1.0" encoding="UTF-8"?><oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="ProtocolHandler" oor:package="org.openoffice.Office"> <node oor:name="HandlerSet"> <node oor:name="org.openoffice.Office.addon.example" oor:op="replace"> <prop oor:name="Protocols" oor:type="oor:string-list"> <value>org.openoffice.Office.addon.example:*</value> </prop> </node> </node></oor:component-data>
The configuration adds one new URL protocol using wildcards:
org.openoffice.Office.addon.example:*
Based on this URL protocol, the C++ protocol handler can route, for example, a dispatched URL
org.openoffice.Office.addon.example:Function1
to the corresponding target routine. See the implementation of the dispatch() method in the XDispatch interface of the C++ source fragment above.
Installation
When the office finds a protocol handler implementation for a URL in the configuration files, it asks the global service manager to instantiate that implementation. All components must be registered with the service manager before they can be instantiated. This happens automatically when an extension is being installed (see chapter [CHAPTER:Extensions]).How this is done is described in section [CHAPTER:Components.Deployment.PackageInstall].
The easiest method to configure and register a new protocol handler in a single step is therefore to use the Extension Managerpackage installation tool pkchkg. An suitable package file extension for the example protocol handler could contain the following directory structure:
[jl: make it an .oxt with manifest.xml, and use unopkg]
ExampleHandler.zip:
ProtocolHandler.xcu
windows.plt/
examplehandler.dll
solaris_sparc.plt/
libexamplehandler.so
linux_x86.plt/
libexamplehandler.so
The .xcu file goes into the root of the packageextension, the shared libraries for the various platforms go to their respective .plt directories.
The package installation is as simple as changing to the <OfficePath>/program directory with a command-line shell and running
$ pkgchkunopkg add /foo/bar/ExampleHandler.zip
For an explanation of the packageextension structure and more deployment options please refer to [CHAPTER:ExtensionsComponents.Deployment].
Jobs
Overview
[TOPIC:com.sun.star.task.JobExecutor]A job in [PRODUCTNAME] is a UNO component that can be executed by the job execution environment upon an event. It can read and write its own set of configuration data in the configuration branch org.openoffice.Office.Jobs, and it can be activated and deactivated from a certain point in time using special time stamps. It may be started with or without an environment, and it is protected against termination and lifetime issues.
The event that starts a job can be triggered by:
any code in [PRODUCTNAME] that detects a defined state at runtime and passes an event string to the service [IDL:com.sun.star.task.JobExecutor] through its interface method [IDL:com.sun.star.task.XJobExecutor:trigger](). The job executor looks in the configuration of [PRODUCTNAME] if there are any jobs registered for this event and executes them.
the global document event broadcaster
the dispatch framework, which provides for a vnd.star.sun.job: URL schema to start jobs using a command URL. This URL schema can execute jobs in three different ways: it can issue an event for job components that are configured to wait for it, it can call a component by an alias that has been given to the component in the configuration or it can execute a job component directly by its implementation name.
If you call trigger() at the job executor or employ the global event broadcaster, the office needs a valid set of configuration data for every job you want to run. The third approach, to use a vnd.star.sun.job: command URL, works with or without prior configuration.
Illustration 4.4 shows an example job that counts how many times it has been triggered by an event and deactivates itself when it has been executed twice. It uses its own job-specific configuration layer to store the number of times it has been invoked. This value is passed to each newly created job instance as an initialization argument, and can be checked and written back to the configuration. When the counter exceeds two, the job uses the special deactivation feature of the job execution environment. Each job can have a user time stamp and and administrator time stamp to control activation and deactivation. When a job is deactivated, the execution environment updates the user time stamp value, so that subsequent events do not start this job again. It can be enabled by a newer time stamp value in the administration layer.
Sequence diagram showing a job execution
Illustration 4.5: Flow diagram of an example job
Execution Environment
Jobs are executed in a job execution environment, which handles a number of tasks and problems that can occur when jobs are executed. In particular,
it initializes the job with all necessary data
it starts the job using the correct interfaces
it keeps the job alive by acquiring a UNO reference
it waits until the job finishes its work, including listening for asynchronous jobs
it updates the configuration of a job after it has finished
it informs listeners about the execution
it protects the job from office termination, or informs it when it is impossible to veto termination
For this purpose, the job execution environment creates special wrapper objects for jobs. This wrapper object implements mechanisms to support lifetime control. The wrapper vetoes termination of the [IDL:com.sun.star.frame.Desktop] and the closing of frames that contain document models as long as there are dependent active jobs. It might also register as a [IDL:com.sun.star.util.XCloseListener] at a [IDL:com.sun.star.frame.Frame] or [IDL:com.sun.star.document.OfficeDocument] to handle the close communication on behalf of the job. It also listens for asynchronous job instances, and it is responsible for updates to the configuration data after a job has finished (see [CHAPTER:Components.Integrating.Jobs.Results]).
A central problem of external components in [PRODUCTNAME] is their lifetime control. Every external component must deal with the possibility that the environment will terminate. It is not efficient to implement lifetime strategies in every job, so the job execution environment takes care of this problem. That way, a job can execute, while difficult situations are handled by the execution environment.
Another advantage of this approach is that it ensures future compatibility. If the mechanism changes in the future, termination is detected and prevented, and it is unnecessary to adapt every existing job implementation.
Implementation
[TOPIC:com.sun.star.task.Job;com.sun.star.task.AsyncJob;com.sun.star.task.XJob;com.sun.star.task.XAsyncJob]A job must implement the service [IDL:com.sun.star.task.Job] if it needs to block the thread in which it is executed or [IDL:com.sun.star.task.AsyncJob] if the current state of the office is unimportant for the job. The service that a job implementation supports is detected at runtime. If both are available, the synchronous service [IDL:com.sun.star.task.Job] is preferred by the job execution environment.
UML diagram showing the com.sun.star.task.Job and com.sun.star.task.AsyncJob services
Illustration 4.6: Job framework
A synchronous job must not make assumptions about the environment, neither that it is the only job that runs currently nor that another object waits for its results. Only the thread context of a synchronous job is blocked until the job finishes its work.
An asynchronous job is not allowed to use threads internally, because [PRODUCTNAME] needs to control thread creation. How asynchronous jobs are executed is an implementation detail of the global job execution environment.
Jobs that need a user interface must proceed with care, so that they do not interfere with the message loop of [PRODUCTNAME]. The following rules apply:
You cannot display any user interface from a synchronous job, because repaint errors and other threading issues will occur.
The easiest way to have a user interface for an asynchronous job is to use a non-modal dialog. If you need a modal dialog to get user input, problems can occur. The best way is to use the frame reference that is part of the job environment initialization data, and to get its container window as a parent window. This parent window can be used to create a dialog with the user interface toolkit [IDL:com.sun.star.awt.Toolkit]. The C++ protocol handler discussed in [CHAPTER:Components.Integrating.ProtocolHandler.Implementation] shows how a modal message box uses this approach.
Using a native toolkit or the Java AWT for your GUI can lead to a non-painting [PRODUCTNAME]. To avoid this, the user interface must be non-modal and the implementation must allow the office to abort the job by supporting [IDL:com.sun.star.lang.XComponent] or [IDL:com.sun.star.util.XCloseable].
The optional interfaces [IDL:com.sun.star.lang.XComponent] or [IDL:com.sun.star.util.XCloseable] should be supported so that jobs can be disposed of in a controlled manner. When these interfaces are present, the execution environment can call dispose() or close() rather than waiting for a job to finish. Otherwise [PRODUCTNAME] must wait until the job is done. Invisible jobs can be especially problematic, because they cannot be recognized as the reason why [PRODUCTNAME] refuses to exit.
Initialization
A job is initialized by a call to its main interface method, which starts the job. For synchronous jobs, the execution environment calls [IDL:com.sun.star.task.XJob:execute](), whereas asynchronous jobs are run through [IDL:com.sun.star.task.XAsyncJob:executeAsync]().
Both methods take one parameter Arguments, which is a sequence of [IDL:com.sun.star.beans.NamedValue] structs. This sequence describes the job context.
It contains the environment where the job is running, which tells if the job was called by the job executor, the dispatch framework or the global event broadcaster service, and possibly provides a frame or a document model for the job to work with.
Tip graphics marks a hint section in the text
Section [CHAPTER:Components.Integrating.ProtocolHandler.Implementation] shows how to use a frame to get its associated document model.
The Arguments parameter also yields configuration data, if the job has been configured in the configuration branch org.openoffice.Office.Jobs. This data is separated into basic configuration and additional arguments stored in the configuration. The job configuration is described in section [CHAPTER:Components.Integrating.Jobs.Configuration].
Finally, Arguments can contain dynamic parameters given to the job at runtime. For instance, if a job has been called by the dispatch framework, and the dispatched command URL used parameters, these parameters can be passed on to the job through the execution arguments.
The following table shows the exact specification for the execution Arguments:
Elements of the Execution Arguments Sequence
Environment
sequence< [IDL:com.sun.star.beans.NamedValue] >. Contains environment data. The following named values are defined:
EnvType
string. Determines in which environment a job is executed. Defined Values:"EXECUTOR": job has been executed by a call to trigger() at the job executor"DISPATCH": job is dispatched as vnd.sun.star.job: URL"DOCUMENTEVENT": job has been executed by the global event broadcaster mechanism
EventName
[optional] string. Only exists, if EnvType is "EXECUTOR" or "DOCUMENTEVENT". Contains the name of the event for which this job was registered in configuration. During runtime, this information can be used to handle different function sets by the same component implementation.
Frame
[optional] [IDL:com.sun.star.frame.XFrame]. Only exists, if EnvType is "DISPATCH". Contains the frame context of this job. Furthermore, the sub list DynamicData can contain the optional argument list of the corresponding [IDL:com.sun.star.frame.XDispatch:dispatch]() request.
Model
[optional] [IDL:com.sun.star.frame.XModel]. Only exists, if EnvType is "DOCUMENTEVENT". Contains the document model that can be used by the job.
Config
[optional] [sequence< [IDL:com.sun.star.beans.NamedValue] >]. Contains the generic set of job configuration properties as described in [CHAPTER:Components.Integrating.Jobs.Configuration] but not the job specific data set. That is, this sub list only includes the properties Alias and Service, not the property Arguments. The property Arguments is reflected in the element JobConfig (see next element below)Note: this sub list only exists if the job is configured with this data.
Alias
string. This property is declared as the name of the corresponding set node in the configuration set Jobs. It must be a unique name, which represents the structured information of a job.
Service
string. Represents the UNO implementation name of the job component.
JobConfig
[optional] [sequence< [IDL:com.sun.star.beans.NamedValue] >]This sub list contains the job-specific set of configuration data as specified in the Arguments property of the job configuration. Its items depend on the job implementation. Note: this sub list only exists if the job is configured with this data.
DynamicData
[optional] [sequence< [IDL:com.sun.star.beans.NamedValue] >]. Contains optional parameters of the call that started the execution of this job. In particular, it can include the parameters of a [IDL:com.sun.star.frame.XDispatch:dispatch]() request, if Environment-EnvType is "DISPATCH"
The following example shows how a job can analyze the given arguments and how the environment in which the job is executed can be detected:
public synchronized java.lang.Object execute(com.sun.star.beans.NamedValue[] lArgs)
throws com.sun.star.lang.IllegalArgumentException, com.sun.star.uno.Exception {
// extract all possible sub list of given argument list
com.sun.star.beans.NamedValue[] lGenericConfig = null;
com.sun.star.beans.NamedValue[] lJobConfig = null;
com.sun.star.beans.NamedValue[] lEnvironment = null;
com.sun.star.beans.NamedValue[] lDispatchArgs = null;
int c = lArgs.length;
for (int i=0; i<c; ++i) {
if (lArgs[i].Name.equals("Config"))
lGenericConfig = (com.sun.star.beans.NamedValue[])
com.sun.star.uno.AnyConverter.toArray(lArgs[i].Value);
else
if (lArgs[i].Name.equals("JobConfig"))
lJobConfig = (com.sun.star.beans.NamedValue[])
com.sun.star.uno.AnyConverter.toArray(lArgs[i].Value);
else
if (lArgs[i].Name.equals("Environment"))
lEnvironment = (com.sun.star.beans.NamedValue[])
com.sun.star.uno.AnyConverter.toArray(lArgs[i].Value);
else
if (lArgs[i].Name.equals("DynamicData"))
lDispatchArgs = (com.sun.star.beans.NamedValue[])
com.sun.star.uno.AnyConverter.toArray(lArgs[i].Value);
else
// It is not realy an error – because unknown items can be ignored ...
throw new com.sun.star.lang.IllegalArgumentException("unknown sub list detected");
}
// Analyze the environment info. This sub list is the only guarenteed one!
if (lEnvironment==null)
throw new com.sun.star.lang.IllegalArgumentException("no environment");
java.lang.String sEnvType = null;
java.lang.String sEventName = null;
com.sun.star.frame.XFrame xFrame = null; com.sun.star.frame.XModel xModel = null;
c = lEnvironment.length;
for (int i=0; i<c; ++i) {
if (lEnvironment[i].Name.equals("EnvType"))
sEnvType = com.sun.star.uno.AnyConverter.toString(lEnvironment[i].Value);
else
if (lEnvironment[i].Name.equals("EventName"))
sEventName = com.sun.star.uno.AnyConverter.toString(lEnvironment[i].Value);
else
if (lEnvironment[i].Name.equals("Frame"))
xFrame = (com.sun.star.frame.XFrame)com.sun.star.uno.AnyConverter.toObject(
new com.sun.star.uno.Type(com.sun.star.frame.XFrame.class), lEnvironment[i].Value); else
if (lEnvironment[i].Name.equals("Model"))
xModel = (com.sun.star.frame.XModel)com.sun.star.uno.AnyConverter.toObject(
new com.sun.star.uno.Type(com.sun.star.frame.XModel.class),
lEnvironment[i].Value); }
// Further the environment property "EnvType" is required as minimum.
if (
(sEnvType==null) ||
(
(!sEnvType.equals("EXECUTOR" )) &&
(!sEnvType.equals("DISPATCH" )) &&
(!sEnvType.equals("DOCUMENTEVENT"))
)
)
{
throw new com.sun.star.lang.IllegalArgumentException("no valid value for EnvType");
}
// Analyze the set of shared config data.
java.lang.String sAlias = null;
if (lGenericConfig!=null) {
c = lGenericConfig.length;
for (int i=0; i<c; ++i) {
if (lGenericConfig[i].Name.equals("Alias"))
sAlias = com.sun.star.uno.AnyConverter.toString(lGenericConfig[i].Value);
}
}
}
Returning Results
Once a synchronous job has finished its work, it returns its result using the any return value of the [IDL:com.sun.star.task.XJob:execute]() method. An asynchronous jobs send back the result through the callback method jobFinished() to its [IDL:com.sun.star.task.XJobListener]. The returned any parameter must contain a sequence< [IDL:com.sun.star.beans.NamedValue] > with the following elements:
Elements of the Job Return Value
Deactivate
boolean. Asks the job executor to disable a job from further execution. Note that this feature is only available if the next event is triggered by the job executor or the event broadcaster. If it comes, for example, from the dispatch framework using an URL with an <alias> argument, the deactivation will be ignored.This value should be used carefully if the Environment-EnvType is "DISPATCH", because users will be irritated if clicking a UI element, such as an Add-On menu entry, has no effect.
SaveArguments
sequence< [IDL:com.sun.star.beans.NamedValue] >. Must contain a list of job specific data, which are written directly to the Arguments list into the job configuration. Note: Merging is not supported. The list must be complete and replaces all values in the configuration. The necessary data can be copied and adjusted from the JobConfig element of the execution arguments.
SendDispatchResult
[IDL:com.sun.star.frame.DispatchResultEvent]. If a job is designed to be usable in the dispatch framework, this contains a struct, which is send to all interested dispatch result listeners.
Tip: This value should be omitted if Environment-EnvType is not "DISPATCH".
Configuration
Although jobs that are called through a vnd.sun.star.jobs: URL by their implementation name do not require it, a job usually has configuration data. The configuration package org.openoffice.Office.Jobs contains all necessary information:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE oor:component-schema SYSTEM "../../../../component-schema.dtd">
<oor:component-schema xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="Jobs" oor:package="org.openoffice.Office" xml:lang="en-US">
<templates>
<group oor:name="Job">
<prop oor:name="Service" oor:type="xs:string"/>
<group oor:name="Arguments" oor:extensible="true"/>
</group>
<group oor:name="TimeStamp">
<prop oor:name="AdminTime" oor:type="xs:string"/>
<prop oor:name="UserTime" oor:type="xs:string"/>
</group>
<group oor:name="Event">
<set oor:name="JobList" oor:node-type="TimeStamp"/>
</group>
</templates>
<component>
<set oor:name="Jobs" oor:node-type="Job"/>
<set oor:name="Events" oor:node-type="Event"/>
</component>
</oor:component-schema>
The Job template contains all properties that describe a job component. Instances of this template are located inside the configuration set Jobs.
Properties of the Job template
Alias
string. This property is declared as the name of the corresponding set node inside the configuration set Jobs. It must be a unique name, which represents the structured information of a job. In the example .xcu file below its value is "SyncJob". In the job execution arguments this property is passed as Config - Alias
Service
string. Represents the UNO implementation name of the job component. In the job execution arguments this property is passed as Config - Service
Arguments
set of any entries. This list can be filled with any values and represents the private set of configuration data for this job. In the job execution arguments this property is passed as JobConfig
The job property Alias was created to provide you with more flexibility for a developing components. You can use the same UNO implementation, but register it with different Aliases. At runtime the job instance will be initialized with its own configuration data and can detect which representation is used.
Pay attention to the following important text section
You cannot use the generic UNO service names [IDL:com.sun.star.task.Job] or [IDL:com.sun.star.task.AsyncJob] for the Service job property, because the job executor cannot identify the correct job implementation. To avoid ambiguities, it is necessary to use the UNO implementation name of the component.
Every job instance can be bound to multiple events. An event indicates a special office state, which can be detected at runtime (for example, OnFirstVisibleTask ), and which can be triggered by a call to the job executor when the first document window is displayed.
Properties of the Event template
EventName
string. This property is declared as the name of the corresponding set node inside the configuration set Events. It must be a unique name, which describes a functional state. In the example .xcu file below its value is "onFirstVisibleTask".
Section [CHAPTER:Components.Integrating.Jobs.SupportedEvents] summarizes the events currently triggered by the office. In addition, developers can use arbitrary event strings with the vnd.sun.star.jobs: URL or in calls to trigger() at the [IDL:com.sun.star.task.JobExecutor] service.
JobList
set of TimeStamp entries. This set contains a list of all Alias names of jobs that are bound to this event. Every job registration can be combined with time stamp values. Please refer to the description of the template TimeStamp below for details
As an optional feature, every job registration that is bound to an event can be enabled or disabled by two time stamp values. In a shared installation of [PRODUCTNAME], an administrator can use the AdminTime value to reactivate jobs for every newly started user office instance; regardless of earlier executions of these jobs. That can be useful, for example, for updating user installations if new functions have been added to the shared installation.
Properties of the TimeStamp template
AdminTime
string. This value must be formatted according to the ISO 8601. It contains the time stamp, which can only be adjusted by an administrator, to reactivate this job.
UserTime
string. This value must be formatted according to the ISO 8601. It contains the time, when this job was finished successfully last time upon the configured event.
Using this time stamp feature can sometimes be complicated. For example, assume that there is a job that was installed using the Extension Manager uses the pkgchk mechanism of [PRODUCTNAME] for installation. The job is enabled for a registered event by default, but after the first execution it is disabled. By default, both values (AdminTime and UserTime) do not exist for a configured event. A Jobs.xcu fragment, as part of the package fileextension, must also not contain the AdminTime and UserTime entries. Because both values are not there, no check can be made and the job is enabled. A job can be deactivated by the global job executor once it has finished its work successfully (depending on the Deactivate return value). In that case, the UserTime entry is generated and set to the current time. An administrator can set a newer and valid AdminTime value in order to reactivate the job again, or the user can remove his UserTime entry manually from the configuration file of the user installation.
The following Jobs.xcu file shows an example job configuration:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE oor:component-data SYSTEM "../../../../component-update.dtd">
<oor:component-data oor:name="Jobs" oor:package="org.openoffice.Office" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="Jobs">
<node oor:name="SyncJob" oor:op="replace">
<prop oor:name="Service">
<value>com.sun.star.comp.framework.java.services.SyncJob</value>
</prop>
<node oor:name="Arguments">
<prop oor:name=”arg_1” oor:type=”xs:string” oor:op="replace">
<value>val_1</value>
</prop>
</node>
</node>
</node>
<node oor:name="Events">
<node oor:name="onFirstVisibleTask" oor:op="fuse">
<node oor:name="JobList">
<node oor:name="SyncJob" oor:op="replace"/>
</node>
</node>
</node>
</oor:component-data>
This example job has the following characteristics:
Its alias name is "SyncJob"
The UNO implementation name of the component is com.sun.star.comp.framework.java.services.SyncJob.
The job has its own set of configuration data with one item. It is a string, its name is arg_1 and its value is "val_1".
The job is bound to the global event onFirstVisibleTask, which is triggered when the first document window of a new [PRODUCTNAME] instance is displayed. The next execution of this job is guaranteed, because there are no time stamp values present.
When specifying the event to which the job is bound (onFirstVisibleTask in the above example), it is important to use oor:op="fuse", so that multiple Jobs.xcu particles merge losslessly. but note that oor:op="fuse" is only available since OpenOffice.org 2.0.3, and that a Jobs.xcu file that uses it cannot be used with older versions of OpenOffice.org. With older versions of OpenOffice.org, it was common to use oor:op="replace" instead of oor:op="fuse", which potentially caused event bindings to get lost when multiple Jobs.xcu particles were merged.
Pay attention to the following important text section
A job is not executed when it has deactivated itself and is called afterwards by a vnd.sun.star.jobs:event=... command URL. This can be confusing to users, especially with add-ons, since it would seem that the customized UI items do not function.
Installation
The easiest way to register an external job component is to use the Extension Manager already mentioned pkgchk mechanism of [PRODUCTNAME], described in section(see chapter [CHAPTER:Extensions]). [CHAPTER:Components.Deployment.PackageInstall]. An package file extension for the example job of this chapter can have the following directory structure:
[jl: make it an oxt or simply refer to Extensions chapter]
SyncJob.zip:
Jobs.xcu
windows.plt/
SyncJob.jar
Using the vnd.sun.star.jobs: URL Schema
This section describes the necessary steps to execute a job by issuing a command URL at the dispatch framework. Based upon the protocol handler mechanism, a specialized URL schema has been implemented in [PRODUCTNAME]. It is registered for the URL schema "vnd.sun.star.jobs:*" which uses the following syntax:
vnd.sun.star.jobs:{[event=<name>]}{,[alias=<name>]}{,[service=<name>]}
Elements of a vnd.sun.star.jobs: URL
event=<name>
string. Contains an event string, which can also be used as parameter of the interface method [IDL:com.sun.star.task.XJobExecutor:trigger](). It corresponds to the node name of the set Events in the configuration package org.openoffice.Office.Jobs. Using the event parameter of a vnd.sun.star.jobs: URL will start all jobs that are registered for this event in the configuration.Note: Disabled jobs, that is jobs with a user time stamp that is newer than the administrator time stamp, are not triggered by event URLs.
alias=<name>
string. Contains an alias name of a configured job. This name is not used by the job execution API. It is a node name of the set Jobs in the configuration package org.openoffice.Office.Jobs. Using the alias part of a vnd.sun.star.jobs: URL only starts the requested job.
service=<name>
string. Contains the UNO implementation name of a configured or unconfigured [IDL:com.sun.star.task.Job] or [IDL:com.sun.star.task.AsyncJob] service. It is not necessary that such jobs are registered in the configuration, provided that they work without configuration data or implements necessary configuration on their own.
It is possible to combine elements so as to start several jobs at once with a single URL. For instance, you could dispatch a URL vnd.sun.star.jobs:event=e1,alias=a1,event=e2 ,.... However, URLs that start several jobs at once should be used carefully, since there is no check for double or concurrent requests. If a service is designed asynchronously, it will be run concurrently with another, synchronous job. If both services work at the same area, there might be race conditions and they must synchronize their work. The generic job execution mechanism does not provide this functionality.
The following configuration file for the configuration package org.openoffice.Office.Jobs shows two jobs, which are registered for different events:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE oor:component-data SYSTEM "../../../../component-update.dtd">
<oor:component-data oor:name="Jobs" oor:package="org.openoffice.Office" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="Jobs">
<node oor:name="Job_1" oor:op="replace">
<prop oor:name="Service">
<value>vnd.sun.star.jobs.Job_1</value>
</prop>
<node oor:name="Arguments">
<prop oor:name=”arg_1” oor:type=”xs:string” oor:op="replace">
<value>val_1</value>
</prop>
</node>
</node>
<node oor:name="Job_2" oor:op="replace">
<prop oor:name="Service">
<value>vnd.sun.star.jobs.Job_2</value>
</prop>
<node oor:name="Arguments"/>
</node>
</node>
<node oor:name="Events">
<node oor:name="onFirstVisibleTask" oor:op="replace">
<node oor:name="JobList">
<node oor:name="Job_1" oor:op="replace">
<prop oor:name="AdminTime">
<value>01.01.2003/00:00:00</value>
</prop>
<prop oor:name="UserTime">
<value>01.01.2003/00:00:01</value>
</prop>
</node>
<node oor:name="Job_2" oor:op="replace"/>
</node>
</node>
</node>
</oor:component-data>
The first job can be described by the following properties:
Properties of “Job_1”
alias
Job_1
UNO implementation name
vnd.sun.star.jobs.Job_1
activation state
Disabled for job execution (because its AdminTime is older than its UserTime)
own configuration
contains one string item arg1 with the value "val1"
event registration
job is registered for the event string "onFirstVisibleTask"
The second job can be described by these properties:
Properties of “Job_2”
alias
Job_2
UNO implementation name
vnd.sun.star.jobs.Job_2
activation state
Enabled for job execution (because it uses default values for AdminTime and UserTime)
own configuration
no own configuration items registered
event registration
job is registered for the event string "onFirstVisibleTask"
The following demonstrates use cases for all possible vnd.sun.star.job: URLs. Not all possible scenarios are shown here. The job dispatch can be used in different ways and the combination of jobs can produce different results:
vnd.sun.star.jobs:event=onFirstVisibleTask
This URL starts Job_2 only, Job_1 is marked DISABLED, since its AdminTime stamp is older than its UserTime stamp.
The job is initialized with environment information through the Environment sub list, as shown in section [CHAPTER:Components.Integrating.Jobs.Initialization]. Optional dispatch arguments are passed in DynamicData, and generic configuration data, including the event string, is received in Config. However, it is not initialized with configuration data of its own in JobConfig because Job_2 is not configured with such information. On the other hand, Job_2 may return data after finishing its work, which will be written back to the configuration.
Furthermore, the job instance can expect that the Frame property from the Environment sub list points to the frame in which the dispatch request is to be executed.
vnd.sun.star.jobs:alias=Job_1
This starts Job_1 only. It is initialized with an environment, and optionally initialized with dispatch arguments, generic configuration data, and configuration data of its own. However, the event name is not set here because this job was triggered directly, not using an event name.
vnd.sun.star.jobs:service=vnd.sun.star.jobs.Job_3
A vnd.sun.star.jobs.Job_3 is not registered in the job configuration package. However, if this implementation was registered with the global service manager, and if it provided the [IDL:com.sun.star.task.XJob] or [IDL:com.sun.star.task.XAsyncJob] interfaces, it would be executed by this URL. If both interfaces are present, the synchronous version is preferred.
The given UNO implementation name vnd.sun.star.jobs.Job_3 is used directly for creation at the UNO service manager. In addition, this job instance is only initialized with an environment and possibly with optional dispatch arguments—there is no configuration data for the job to use.
List of supported Events
Supported events triggered by code
onFirstRunInitialization
Called on startup once after [PRODUCTNAME] is installed. Should be used for post-setup operations.
onFirstVisibleTask
Called after a document window has been shown for the first time after launching the application. Note: The quickstarter influences this behavior. With the quickstarter, closing the last document does not close the application. Opening a new document in this situation does not trigger this event.
onDocumentOpened
Indicates that a new document was opened. It does not matter if a new or an existing document was opened. Thus it represents the combined OnNew and OnLoad events of the global event broadcaster.
Supported events triggered by the global event broadcaster
OnStartApp
Application has been started
OnCloseApp
Application is going to be closed
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
OnUnload
Document has been removed
OnFocus
Document was activated
OnUnfocus
Document was deactivated
OnPrint
Document will be printed
OnModifyChange
Modified state of the document has changed
Pay attention to the following important text section
Event names are case sensitive.
Add-Ons
A [PRODUCTNAME] add-on is an external UNO component extension providing one or more functions through the user interface of [PRODUCTNAME]. A typical add-on is available as an extension UNO package for easier deployment with the Extension Managerpkgchk tool. An add-onIn addition to an ordinary UNO package an add-on package contains configuration files which specify the user interface, registration for a protocol schema and first-time instantiation.
The Extension Managerpackage installation tool pkgchk merges the configuration files with the menu and toolbar items for an add-on directly into the [PRODUCTNAME] configuration files.
Overview
[PRODUCTNAME] supports the integration of add-ons into the following areas of the GUI.
Menu items for add-ons can be added to an Add-Ons submenu of the Tools menu and a corresponding add-ons popup toolbar icon:
Screenshot showing the addon popup menu entries
Illustration 4.7: Add-Ons submenu and toolbar popup
It is also possible to create custom menus in the Menu Bar. You are free to choose your own menu title, and you can create menu items and submenus for your add-on. Custom menus are inserted between the Tools and Window menus. Separators are supported as well:
Screenshot showing a new menu entry
Illustration 4.8: Custom top-level menu
You can create toolbar icons in the Function Bar, which is usually the topmost toolbar. Below you see two toolbar items, an icon for Function 1 and a text item for Function 2:
Screenshot showing a new toolbar entry
Illustration 4.9: Toolbar icons for Function 1 and Function 2
The Help menu offers support for add-ons through help menu items that open the online help of an add-on. They are inserted below the Help - Registration item under a separator.
Guidelines
For a smooth integration, a developer should be aware of the following guidelines:
Add-Ons Submenu
Since the Tools - Add-Ons menu is shared by all installed add-ons, an add-on should save space and use a submenu when it has more than two functions. The name of the add-on should be part of the menu item names or the submenu title.
If your add-on has many menu items, use additional submenus to enhance the overview. Use four to seven entries for a single menu. If you exceed this limit, start creating submenus.
Custom Top-Level Menu
Only frequently used add-ons or add-ons that offer very important functions in a user environment should use their own top-level menu.
Use submenus to enhance the overview. Use four to seven entries for a single menu. If you exceed this limit, start creating submenus.
Use the option to group related items by means of separator items.
Toolbar
Only important functions should be integrated into the toolbar.
Use the option to group functions by means of separator items.
Add-On Help menu
Every add-on should provide help to user. This help has to be made available through an entry in the [PRODUCTNAME] Help menu. Every add-on should only use a single Help menu item.
If the add-on comes with its own dialogs, it should also offer Help buttons in the dialogs.
Configuration
The user interface definitions of all add-ons are stored in the special configuration branch org.openoffice.Office.Addons.
The schema of the configuration branch org.openoffice.Office.Addons specifies how to define a user interface extension.
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-schema oor:name="Addons" 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="MenuItem">
<prop oor:name="URL" oor:type="xs:string"/>
<prop oor:name="Title" oor:type="xs:string" oor:localized="true"/>
<prop oor:name="ImageIdentifier" oor:type="xs:string"/>
<prop oor:name="Target" oor:type="xs:string"/>
<prop oor:name="Context" oor:type="xs:string"/>
<set oor:name="Submenu" oor:node-type="MenuItem"/>
</group>
<group oor:name="PopupMenu">
<prop oor:name="Title" oor:type="xs:string" oor:localized="true"/>
<prop oor:name="Context" oor:type="xs:string"/>
<set oor:name="Submenu" oor:node-type="MenuItem"/>
</group>
<group oor:name="ToolBarItem">
<prop oor:name="URL" oor:type="xs:string"/>
<prop oor:name="Title" oor:type="xs:string" oor:localized="true"/>
<prop oor:name="ImageIdentifier" oor:type="xs:string"/>
<prop oor:name="Target" oor:type="xs:string"/>
<prop oor:name="Context" oor:type="xs:string"/>
</group>
<group oor:name="UserDefinedImages">
<prop oor:name="ImageSmall" oor:type="xs:hexBinary"/>
<prop oor:name="ImageBig" oor:type="xs:hexBinary"/>
<prop oor:name="ImageSmallHC" oor:type="xs:hexBinary"/>
<prop oor:name="ImageBigHC" oor:type="xs:hexBinary"/>
<prop oor:name=”ImageSmallURL” oor:type=”xs:string”/>
<prop oor:name=”ImageBigURL” oor:type=”xs:string”/>
<prop oor:name=”ImageSmallHCURL” oor:type=”xs:string”/>
<prop oor:name=”ImageBigHCURL” oor:type=”xs:string”/>
</group>
<group oor:name="Images">
<prop oor:name="URL" oor:type="xs:string"/>
<node-ref oor:name="UserDefinedImages" oor:node-type="UserDefinedImages"/>
</group>
<set oor:name="ToolBarItems" oor:node-type="ToolBarItem"/>
</templates>
<component>
<group oor:name="AddonUI">
<set oor:name="AddonMenu" oor:node-type="MenuItem"/>
<set oor:name="Images" oor:node-type="Images"/>
<set oor:name="OfficeMenuBar" oor:node-type="PopupMenu"/>
<set oor:name="OfficeToolBar" oor:node-type="ToolBarItems"/>
<set oor:name="OfficeHelp" oor:node-type="MenuItem"/>
</group>
</component>
</oor:component-schema>
Menus
As explained in the previous section, [PRODUCTNAME] supports two menu locations where an add-on can be integrated: a top-level menu or the Tools - Add-Ons submenu. The configuration branch org.openoffice.Office.Addons provides two different nodes for these locations:
Supported sets of org.openoffice.Office.Addons to define an Add-On menu
OfficeMenuBar
A menu defined in this set will be a top-level menu in the [PRODUCTNAME] Menu Bar.
AddonMenu
A menu defined in this set will be a pop-up menu which is part of the Add-Ons menu item located on the bottom position of the Tools menu.
Submenu in Tools - Add-Ons
To integrate add-on menu items into the Tools – Add-Ons menu, use the AddonMenu set. The AddonMenu set consists of nodes of type MenuItem. The MenuItem node-type is also used for the submenus of a top-level add-on menu.
Properties of template MenuItem
oor:name
string. The name of the configuration node. The name must begin with an ASCII letter character. It must be unique within the OfficeMenuBar set. Therefore, it is mandatory to use a schema such as org.openoffice.<developer>.<product>.<addon name> or com.<company>.<product>.<addon name> to avoid name conflicts. Keep in mind that your configuration file will be merged into the [PRODUCTNAME] configuration branch. You do not know which add-ons, or how many add-ons, are currently installed.The node name of menu items of a submenu must be unique only within their submenu. A configuration set cannot guarantee the order of its entries, so you should use a schema such as string + number, for example “m1”, as the name is used to sort the entries.
URL
string. Specifies the command URL that should be dispatched when the user activates the menu entry. It will be ignored if the MenuItem is the title of a a submenu.To define a separator you can use the special command URL "private:separator". A separator ignores all other properties.
Title
string. Contains the title of a top-level menu item. This property supports localization: The default string, which is used when [PRODUCTNAME] cannot find a string definition for its current language, uses the value element without an attribute. You define a string for a certain language with the xml:lang attribute. Assign the language/locale to the attribute, for example <value xml:lang="en-US">string</value>.
ImageIdentifier
string. Defines an optional image URL that could address an internal [PRODUCTNAME] image or an external user-defined image. The syntax of an internal image URL is: private:image/<number> where number specifies the image.
External user-defined images are supported using the placeholder variable %origin% representing the folder where the component will be installed by the pkgchkExtension Manager tool. The pkgchkExtension Manager tool will exchanges %origin% by another placeholder, which is substituted during runtime by [PRODUCTNAME] to the real installation folder. Since [PRODUCTNAME] supports two different configuration folders (user and share) this mechanism is necessary to determine the installation folder of a component.
For example the URL %origin%/image will be substituted to something like
vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/component.zip.1051610942/image .
The placeholder vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE will then be substituted during runtime by the real path.
As the ImageIdentifier property can only hold one URL but [PRODUCTNAME] supports four different images (small/large image and both as high contrast), a naming schema is used to address them. [PRODUCTNAME] adds _16.bmp and _26.bmp to the provided URL to address the small and large image. _16h.bmp and _26h.bmp is added to address the high contrast images. If the high contrast images are omitted the normal images are used instead.
[PRODUCTNAME] supports bitmaps with 1, 4, 8, 16, 24 bit color depth. Magenta (color value red=0xffff, green=0x0000, blue=0xffff) is used as the transparent color, which means that the background color of the display is used instead of the image pixel color when the image is drawn.
For optimal results the size of small images should be 16x16 pixel and for big images 26x26 pixel. Other image sizes are scaled automatically by [PRODUCTNAME].If no high contrast image is provided, [PRODUCTNAME] uses the normal image for high contrast environments. Images that are not valid will be ignored.This property has a higher priority than the Images set when [PRODUCTNAME] searches for images.
Target
string. Specifies the target frame for the command URL. Normally an add-on will use one of the predefined target names:
_topReturns the top frame of the called frame, which is the first frame where isTop() returns true when traversing up the hierarchy.
_parentReturns the next frame above in the frame hierarchy.
_selfReturns 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.
_blankCreates a new top-level frame whose parent is the desktop frame.
Context
string. A list of service names, separated by a comma, that specifies in which context the add-on menu function should be visible. An empty Context means that the function should visible in all contexts. The [PRODUCTNAME] application modules use the following services names:
Writer:com.sun.star.text.TextDocument
Spreadsheet:com.sun.star.sheet.SpreadsheetDocument
Presentation:com.sun.star.presentation.PresentationDocument
Draw:com.sun.star.drawing.DrawingDocument
Formula:com.sun.star.formula.FormulaProperties
Chart:com.sun.star.chart.ChartDocument
Bibliography:com.sun.star.frame.Bibliography
The context service name for add-ons is determined by the service name of the model that is bound to the frame, which is associated with UI element (toolbar, menu bar, ...). Thus the service name of the Writer model is com.sun.star.text.TextDocument. That means, the context name is bound to the model of an application module. If a developer implements a new desktop component that has a model, it is possible to use its service name as a context for add-on UI items.
Submenu
A set of MenuItem entries. Optional to define a submenu for the menu entry.
The next examples shows a configuration file specifying a single menu item titled Add-On Function 1. The unique node name of the add-on is called org.openoffice.example.addon.example.function1.
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="AddonMenu">
<node oor:name="org.openoffice.Office.addon.example.function1" oor:op="replace"> <prop oor:name="URL" oor:type="xs:string"> <value>org.openoffice.Office.addon.example:Function1</value> </prop>
<prop oor:name="ImageIdentifier" oor:type="xs:string"
<value/> </prop>
<prop oor:name="Title" oor:type="xs:string"> <value/> <value xml:lang="en-US">Add-On Function 1</value> </prop> <prop oor:name="Target" oor:type="xs:string"> <value>_self</value> </prop> <prop oor:name="Context" oor:type="xs:string"> <value>com.sun.star.text.TextDocument</value> </prop> </node> </node>
</node>
Top-level Menu
If you want to integrate an add-on into the [PRODUCTNAME] Menu Bar, you have to use the OfficeMenuBar set. An OfficeMenuBar set consists of nodes of type PopupMenu.
Properties of template PopupMenu
oor:name
string. The name of the configuration node. The name must begin with an ASCII letter character. It must be unique within the OfficeMenuBar set. Therefore, it is mandatory to use a schema such as org.openoffice.<developer>.<product>.<addon name> or com.<company>.<product>.<addon name> to avoid name conflicts. Please keep in mind that your configuration file will be merged into the [PRODUCTNAME] configuration branch. You do not know what add-ons, or how many add-ons, are currently installed.
Title
string. Contains the title of a top-level menu item. This property supports localization: The default string, which is used when [PRODUCTNAME] cannot find a string definition for its current language, uses the value element without an attribute. You define a string for a certain language with the xml:lang attribute. Assign the language/locale to the attribute, for example <value xml:lang="en-US">string</value>.
Context
string. A list of service names, separated by a comma, that specifies in which context the add-on menu should be visible. An empty context means that the function should be visible in all contexts. The [PRODUCTNAME] application modules use the following services names:
Writer:com.sun.star.text.TextDocument
Spreadsheet:com.sun.star.sheet.SpreadsheetDocument
Presentation:com.sun.star.presentation.PresentationDocument
Draw:com.sun.star.drawing.DrawingDocument
Formula:com.sun.star.formula.FormulaProperties
Chart:com.sun.star.chart.ChartDocument
Bibliography:com.sun.star.frame.Bibliography
The context service name for add-ons is determined by the service name of the model that is bound to the frame, which is associated with UI element (toolbar, menu bar, ...). Thus the service name of the Writer model is com.sun.star.text.TextDocument. That means, the context name is bound to the model of an application module. If a developer implements a new desktop component that has a model it is possible to use its service name as a context for add-on UI items.
Submenu
A set of MenuItem entries. Defines the submenu of the top-level menu. It must be defined on a top-level menu otherwise the whole menu will be ignored.For more information how to define a submenu please refer to section [CHAPTER:Components.Integrating.UIAddOns.Guidelines] where the MenuItem template is described.
The following example defines a top-level menu titled Add-On example with a single menu item titled Add-On Function 1. The menu item has a self-defined image used for displaying it next to the menu title.In the example the nodes are called oor:name="org.openoffice.example.addon" and oor:name="m1".
Do not forget to specify the oor:op="replace" attribute in your self-defined nodes. The replace operation must be used to add a new node to a set or extensible node. Thus the real meaning of the operation is "add or replace". Dynamic properties can only be added once and are then considered mandatory, so during layer merging the replace operation always means "add" for them.For more details about the configuration and their file formats please read [CHAPTER:Config].
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="OfficeMenuBar">
<node oor:name="org.openoffice.example.addon" oor:op="replace">
<prop oor:name="Title" oor:type="xs:string">
<value/>
<value xml:lang="en-US">Add-On example</value>
<value xml:lang=”de”>Add-On Beispiel</value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.text.TextDocument</value>
</prop>
<node oor:name="Submenu">
<node oor:name="m1" oor:op="replace">
<prop oor:name="URL" oor:type="xs:string">
<value>org.openoffice.Office.addon.example:Function1</value>
</prop>
<prop oor:name="Title" oor:type="xs:string">
<value/>
<value xml:lang=”en-US”>Add-On Function 1</value>
<value xml:lang="de">Add-On Funktion 1</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
</node>
</node>
</node>
</node>
</node>
</oor:component-data>
Toolbars
An add-on can also be integrated into the Function Bar of [PRODUCTNAME]. The org.openoffice.Office.Addons configuration branch has a set called OfficeToolBar where you can add toolbar items for an add-on. The toolbar structure uses an embedded set called ToolbarItems, which is used by [PRODUCTNAME] to group toolbar items from different add-ons. [PRODUCTNAME] automatically inserts a separator between different add-ons toolbar items.
Pay attention to the following important text section
The space of the Function Bar is limited, so only the most used/important functions should be added to the OfficeToolBar set. Otherwise [PRODUCTNAME] will add scroll-up/down buttons at the end of the Function Bar and the user has to scroll the toolbar to have access to all toolbar buttons.
Properties of template ToolBarItems
oor:name
string. The name of the configuration node. The name must begin with an ASCII letter character. It must be unique within the OfficeMenuBar set. Therefore it is mandatory to use a schema such as org.openoffice.<developer>.<product>.<addon name> or com.<company>.<product>.<addon name> to avoid name conflicts. Please keep in mind that your configuration file will be merged into the [PRODUCTNAME] configuration branch. You do not know what add-ons, or how many add-ons, are currently installed.
The ToolBarItems set is a container for the ToolBarItem nodes.
Properties of template ToolBarItem
oor:name
string. The name of the configuration node. It must be unique inside your own ToolBarItems set. A configuration set cannot guarantee the order of its entries, therefore use a schema such as string + number, for example "m1", as the name is used to sort the entries. Please be aware that the name must begin with an ASCII letter character.
URL
string. Specifies the command URL that should be dispatched when the user activates the menu entry. To define a separator you can use the special command URL "private:separator". A separator ignores all other properties.
Title
string. Contains the title of a top-level menu item. This property supports localization: The default string, which is used when [PRODUCTNAME] cannot find a string definition for its current language, uses the value element without an attribute. You define a string for a certain language with the xml:lang attribute. Assign the language/locale to the attribute, for example <value xml:lang="en-US">string</value>.
ImageIdentifier
string. Defines an optional image URL that could address an internal [PRODUCTNAME] image or an external user-defined image. The syntax of an internal image URL is: private:image/<number> where number specifies the image.
External user-defined images are supported using the placeholder variable %origin%, representing the folder where the component will be installed by the pkgchkExtension Manager tool. The pkgchkExtension Manager tool exchanges %origin% with another placeholder, which is substituted during runtime by [PRODUCTNAME] to the real installation folder. Since [PRODUCTNAME] supports two different configuration folders (user and share) this mechanism is necessary to determine the installation folder of a component.
For example the URL %origin%/image will be substituted with something like
vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/component.zip.1051610942/image .
The placeholder vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE is then substituted during runtime with the real path.
Since the ImageIdentifier property can only hold one URL but [PRODUCTNAME] supports four different images (small/large image, and both as high contrast), a naming schema is used to address them. [PRODUCTNAME] adds _16.bmp and _26.bmp to the provided URL to address the small and large image. _16h.bmp and _26h.bmp is added to address the high contrast images. If the high contrast images are omitted, the normal images are used instead.
[PRODUCTNAME] supports bitmaps with 1, 4, 8, 16, 24 bit color depth. Magenta (color value red=0xffff, green=0x0000, blue=0xffff) is used as the transparent color, which means that the background color of the display is used instead of the image pixel color when the image is drawn.
For optimal results, the size of small images should be 16x16 pixel, and for big images 26x26 pixel. Other image sizes are scaled automatically by [PRODUCTNAME].If no high contrast image is provided, [PRODUCTNAME] uses the normal image for high contrast environments. Images that are not valid are ignored.This property has a higher priority than the Images set when [PRODUCTNAME] searches for images.
Target
string. Specifies the target frame for the command URL. Normally an add-on will use one of the predefined target names:
_topReturns the top frame of the called frame, which is the first frame where isTop() returns true when traversing up the hierarchy.
_parentReturns the next frame above in the frame hierarchy.
_selfReturns 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.
_blankCreates a new top-level frame whose parent is the desktop frame.
Context
string. A list of service names, separated by a comma, that specifies in which context the add-on menu should be visible. An empty context means that the function should be visible in all contexts. The [PRODUCTNAME] application modules use the following services names:
Writer:com.sun.star.text.TextDocument
Spreadsheet:com.sun.star.sheet.SpreadsheetDocument
Presentation:com.sun.star.presentation.PresentationDocument
Draw:com.sun.star.drawing.DrawingDocument
Formula:com.sun.star.formula.FormulaProperties
Chart:com.sun.star.chart.ChartDocument
Bibliography:com.sun.star.frame.Bibliography
The context service name for add-ons is determined by the service name of the model that is bound to the frame, which is associated with an UI element (toolbar, menu bar, ...). Thus the service name of the Writer model is com.sun.star.text.TextDocument. That means, the context name is bound to the model of an application module. If you implement a new desktop component that has a model, it is possible to use its service name as a context for add-on UI items.
The following example defines one toolbar button for the function called org.openoffice.Office.addon.example:Function1. The toolbar button is only visible when using the [PRODUCTNAME] Writer module.
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="OfficeToolBar">
<node oor:name="org.openoffice.Office.addon.example" oor:op="replace">
<node oor:name=”m1”>
<prop oor:name="URL" oor:type="xs:string">
<value>org.openoffice.Office.addon.example:Function1</value>
</prop>
<prop oor:name="Title" oor:type="xs:string">
<value/>
<value xml:lang=”en-US”>Function 1</value>
<value xml:lang="de">Funktion 1</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.text.TextDocument</value>
</prop>
</node>
</node>
</node>
</node>
</oor:component-data>
Images for Toolbars and Menus
[PRODUCTNAME] supports images in menus and toolboxes. In addition to the property ImageIdentifier, the add-ons configuration branch has a fourth set called Images that let developers define and use their own images. The image data can be integrated into the configuration either as hex encoded binary data or as references to external bitmap files. The Images set binds a command URL to user defined images.
Properties of template Images
oor:name
string. The name of the configuration node. It must be unique inside the configuration branch. Therefore it is mandatory to use a schema such as org.openoffice.<developer>.<add-on name> or com.<company>.<product>.<add-on name> to avoid name conflicts. Please keep in mind that your configuration file will be merged into the [PRODUCTNAME] configuration branch. You do not know how many or which add-ons were installed before by the user.Please be aware that the name must begin with an ASCII letter character.
URL
string. Specifies the command URL that should be bound to the defined images. [PRODUCTNAME] searches for images with the command URL that a menu item/toolbox item contains.
UserDefinedImages
Group of properties. This optional group provides self-defined images data to [PRODUCTNAME]. There are two different groups of properties to define the image data. One property group provides the image data as ongoing hex values specifying an uncompressed bitmap format stream. The other property group uses URLs to external bitmap files. The names of these properties end with 'URL'. [PRODUCTNAME] supports bitmap streams with 1, 4, 8, 16, 24 bit color depth. Magenta (color value red=0xffff, green=0x0000, blue=0xffff) is used as the transparent color, meaning that the background color of the display will be used instead of the image pixel color when the image is drawn.For best quality, the size of small images should be 16x16 pixel, and for big images 26x26 pixel. Other image sizes will be scaled automatically by [PRODUCTNAME].If no high contrast image data is provided, [PRODUCTNAME] uses the normal image for high contrast environments. Image data that is not valid will be ignored.
An Images node uses a second node called UserDefinedImages where the user defined images data are stored.
Properties of template UserDefinedImages
ImageSmall
HexBinary. Used for normal menu/toolbar items, standard size is 16x16 pixel.
ImageBig
HexBinary. Only toolbars can use big images. Standard size is 26x26 pixel. The user can activate large buttons with the Tools – Options – View – Large Buttons check box.
ImageSmallHC
HexBinary. Used for high contrast environments, which means that the background color of a menu or toolbar is below a certain threshold value for the brightness.
ImageBigHC
HexBinary. Only toolbars can use big images. Used for high contrast environments, which means that the background color of a toolbar is below a certain threshold value for the brightness.
ImageSmallURL
string. An URL to an external image which is used for menu items and normal toolbar buttons. External user-defined images are supported using the placeholder variable %origin%, representing the folder where the component will be installed by the pkgchkExtension Manager tool. The pkgchkExtension Manager tool exchanges %origin% with another placeholder, which is substituted during runtime by [PRODUCTNAME] to the real installation folder. Since [PRODUCTNAME] supports two different configuration folders (user and share) this mechanism is necessary to determine the installation folder of a component.
For example the URL %origin%/image will be substituted with something like
vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/component.zip.1051610942/image .
The placeholder vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE is then substituted during runtime with the real path.
ImageBigURL
string. An URL to an external image which is used for big toolbar buttons.
ImageSmallHCURL
string. An URL to an external image which is used for menu items and normal toolbar button in a high contrast environment.
ImageBigHCURL
string. An URL to an external image which is used for big toolbar buttons in a high contrast environment.
The embedded image data have a higher priority when used in conjunction with the URL properties. The embedded and URL properties can be mixed without a problem.
The next example creates two user-defined images for the function org.openoffice.Office.addon.example:Function1. The normal image is defined using the embedded image data property ImageSmall and has a size of 16x16 pixel and a 4-bit color depth. The other one uses the URL property ImageSmallHCURL to reference an external bitmap file for the high contrast image.
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="Images">
<node oor:name="com.sun.star.comp.framework.addon.image1" oor:op="replace">
<prop oor:name="URL" oor:type="xs:string">
<value>org.openoffice.Office.addon.example:Function1</value>
</prop>
<node oor:name=”UserDefinedImages”>
<prop oor:name=”ImageSmall”>
<value>424df80000000000000076000000280000001000000010000000010004000000000000000000120b0000120b000000000000000000000000ff0000ffff0000ff0000ffff0000ff000000ff00ff00ffffff00c0c0c0008080800000000000000080000080800000800000808000008000000080008000cccccccccccccccc2c266b181b666c2c5cc66b818b6665c555566b181b66655555566b818b66655555566b181b6665555a8666bbb6668a55a0a866666668a0a5000a8666668a000a6000a86668a000a556000a868a000a55556000a8a000a5555556000a000a55555555600000a55555555556000a55555555555560a55555550000</value>
</prop>
<prop oor:name=”ImageSmallHCURL”>
<value>%origin%/function1.bmp</value>
</prop>
</node>
</node>
</node>
</node>
</oor:component-data>
Help Integration
[PRODUCTNAME] supports the integration of add-ons into its Help menu. The add-on help menu items are inserted below the Registration menu item, guarded by separators. This guarantees that users have quick access to the add-on help. The OfficeHelp set uses the same MenuItem node-type as the AddonMenu set, but there are some special treatments of the properties.
Properties of template MenuItem
oor:name
string. The name of the configuration node. It must be unique inside the configuration branch. Therefore it is mandatory to use a schema such as org.openoffice.<developer>.<add-on name> or com.<company>.<product>.<add-on name> to avoid name conflicts. Please keep in mind that your configuration file will be merged into the [PRODUCTNAME] configuration branch. You do not know how many or which add-ons were installed before by the user.Please be aware that the name must begin with an ASCII letter character.
URL
string. Specifies the help command URL that should be dispatched when the user activates the menu entry.Separators defined by the special command URL "private:separator" are supported, but should not be used in the help menu, because every add-on should only use one menu item.
Title
string. Contains the title of a top-level menu item. This property supports localization: The default string, which is used when [PRODUCTNAME] cannot find a string definition for its current language, uses the value element without an attribute. You define a string for a certain language with the xml:lang attribute. Assign the language/locale to the attribute, for example <value xml:lang="en-US">string</value>.
ImageIdentifier
string. Defines an optional image URL that could address an internal [PRODUCTNAME] image or an external user-defined image. The syntax of an internal image URL is: private:image/<number> where number specifies the image.
External user-defined images are supported using the placeholder variable %origin%, representing the folder where the component will be installed by the pkgchkExtension Manager tool. The pkgchkExtension Manager tool exchanges %origin% with another placeholder, which is substituted during runtime by [PRODUCTNAME] to the real installation folder. Since [PRODUCTNAME] supports two different configuration folders (user and share), this mechanism is necessary to determine the installation folder of a component.
For example the URL %origin%/image is substituted with something like
vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/component.zip.1051610942/image .
The placeholder vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE is then substituted during runtime by the real path.
Since the ImageIdentifier property can only hold one URL but [PRODUCTNAME] supports four different images (small/large image and both as high contrast), a naming schema is used to address them. [PRODUCTNAME] adds _16.bmp and _26.bmp to the provided URL to address the small and large image. _16h.bmp and _26h.bmp is added to address the high contrast images. If the high contrast images are omitted, the normal images are used instead.
[PRODUCTNAME] supports bitmaps with 1, 4, 8, 16, 24 bit color depth. Magenta (color value red=0xffff, green=0x0000, blue=0xffff) is used as the transparent color, which means that the background color of the display is used instead of the image pixel color when the image is drawn.
For optimal results the size of small images should be 16x16 pixel and for big images 26x26 pixel. Other image sizes will be scaled automatically by [PRODUCTNAME].If no high contrast image is provided, [PRODUCTNAME] uses the normal image for high contrast environments. Images that are not valid are ignored.This property has a higher priority than the Images set when [PRODUCTNAME] searches for images.
Target
string. Specifies the target frame for the command URL. Normally an add-on will use one of the predefined target names:
_topReturns the top frame of the called frame, which is the first frame where isTop() returns true when traversing up the hierarchy.
_parentReturns the next frame above in the frame hierarchy.
_selfReturns 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.
_blankCreates a new top-level frame whose parent is the desktop frame.
Context
string. A list of service names, separated by a comma, that specifies in which context the add-on menu should be visible. An empty context means that the function is visible in all contexts. The [PRODUCTNAME] application modules use the following services names:
Writer:com.sun.star.text.TextDocument
Spreadsheet:com.sun.star.sheet.SpreadsheetDocument
Presentation:com.sun.star.presentation.PresentationDocument
Draw:com.sun.star.drawing.DrawingDocument
Formula:com.sun.star.formula.FormulaProperties
Chart:com.sun.star.chart.ChartDocument
Bibliography:com.sun.star.frame.Bibliography
The context service name for add-ons is determined by the service name of the model that is bound to the frame, which is associated with an UI element (toolbar, menu bar, ...). Thus the service name of the Writer model is com.sun.star.text.TextDocument. That means, the context name is bound to the model of an application module. If a developer implements a new desktop component that has a model, it is possible to use its service name as a context for add-on UI items.
Submenu
A set of MenuItem entries. Not used for OfficeHelp MenuItems, any definition inside will be ignored.
The following example shows the single help menu item for the add-on example.
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="OfficeHelp"> <node oor:name="com.sun.star.comp.framework.addon" oor:op="replace"> <prop oor:name="URL" oor:type="xs:string"
<value>org.openoffice.Office.addon.example:Help</value> </prop> <prop oor:name="ImageIdentifier" oor:type="xs:string">
<value/>
</prop> <prop oor:name="Title" oor:type="xs:string"> <value xml:lang="de">Über Add-On Beispiel</value> <value xml:lang="en-US">About Add-On Example</value> </prop> <prop oor:name="Target" oor:type="xs:string"> <value>_self</value> </prop> </node> </node>
</node>
</oor:component-data>
Installation
After finishing the implementation of the UNO component and the definition of the user interface part you can create the UNO package an extension. An UNO packageextension can be used by an end-user to install the add-on into [PRODUCTNAME].
An UNO package is a zip file containing UNO components, type libraries or basic libraries. The pkgchk tool unzips all packages found in the package directory into the cache directory, preserving the file structure of the zip file. It also copies all single files recognized in the package directory to the cache directory.
[jl: make it a .oxt, manifest.xml etc.]
The configuration files that were created for the add-on, such as protocol handler, jobs, and user interface definition must be added to the root of the zip file. The structure of a zip file supporting Windows should resemble the following code:
example_addon.zip:
Addons.xcu
ProtocolHandler.xcu
windows.plt/
example_addon.dll
Before you install the packageextension, make absolutely sure there are no running instances of [PRODUCTNAME]. The pkchkunopkg tool recognizes a running [PRODUCTNAME] in a local installation, but not in a networked installation. Installing into a running office installation might cause inconsistencies and destroy your installation!
The packageextension installation for the example add-on is as simple as changing to the <OfficePath>/program directory with a command-line shell and running
[<OfficePath>/program] $ pkgchkunopkg add /foo/bar/example_addon.zip
For an explanation of otherthe package structure and more deployment options, please refer to [CHAPTER:Components.Deployment] .and for an explanation about extensions refer to [CHAPTER:Extensions].
Disable Commands
In [PRODUCTNAME], there may be situations where functions should be disabled to prevent users from changing or destroying documents inadvertently. [PRODUCTNAME] maintains a list of disabled commands that can be maintained by users and developers through the configuration API.
A command request can be created by any object, but in most cases, user interface objects create these requests. Consider, for instance, a toolbox where different functions acting on the office component are presented as buttons. Once a button is clicked, the desired functionality should be executed. If the code assigned to the button is provided with a suitable command URL, the dispatch framework can handle the user action by creating the request and finding a component that can handle it.
The dispatch framework works with the design pattern chain of responsibility: everything a component needs to know if it wants to execute a request is the last link in a chain of objects capable of executing requests. If this object gets the request, it checks whether it can handle it or otherwise passes it to the next chain member until the request is executed or the end of the chain is reached.The disable commands implementation is the first chain member and can therefore work as a wall for all disabled commands. They are not be sent to the next chain member, and disappear.
shows how the disable commands feature affects the normal command application flow.
Overview graphic showing how disabling commands works
Illustration 4.10: How the disable commands feature works
Pay attention to the following important text section
Since the disable commands implementation is the first part in the dispatch chain, there is no way to circumvent it. The disabled command must be removed from the list, otherwise it remains disabled.
Configuration
The disable commands feature uses the configuration branch org.openoffice.Office.Commands to read which commands should be disabled. The following schema applies:
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-schema oor:name="Commands" 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="CommandType">
<prop oor:name="Command" oor:type="xs:string"/>
</group>
</templates>
<component>
<group oor:name="Execute">
<set oor:name="Disabled" oor:node-type="CommandType"/>
</group>
</component>
</oor:component-schema>
The configuration schema for disabled commands is very simple. The org.openoffice.Office.Commands branch has a group called Execute. This group has only one set called Disabled. The Disabled set supports nodes of the type CommandType. The following table describes the supported properties of CommandType.
Properties of the CommandType group
oor:component-data
string. It must be unique inside the Disabled set, but has no additional meaning for the implementation of the disable commands feature. Use a consecutive numbering scheme; even numbers are allowed.
Command
string. This is the command name with the preceding protocol. That means the command URL .uno:Open (which shows the File – Open dialog) must be written as Open.The valid commands can be found in the document Index of Command Names in the Documentation section of the framework project on the OpenOffice.org web page. The [PRODUCTNAME] SDK also includes the latest list of command names.
The example below shows a configuration file that disables the commands for File – Open, Edit – Select All, Help – About [PRODUCTNAME] and File – Exit.
<?xml version="1.0" encoding="UTF-8" ?>
<oor:component-data oor:name="Commands" oor:package="org.openoffice.Office" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="Execute">
<node oor:name="Disabled">
<node oor:name="m1" oor:op="replace">
<prop oor:name="Command">
<value>Open</value>
</prop>
</node>
<node oor:name="m2" oor:op="replace">
<prop oor:name="Command">
<value>SelectAll</value>
</prop>
</node>
<node oor:name="m3" oor:op="replace">
<prop oor:name="Command">
<value>About</value>
</prop>
</node>
<node oor:name="m4" oor:op="replace">
<prop oor:name="Command">
<value>Quit</value>
</prop>
</node>
</node>
</node>
</oor:component-data>
Disabling Commands at Runtime
[TOPIC:com.sun.star.configuration.ConfigurationUpdateAccess;com.sun.star.configuration.ConfigurationProvider;com.sun.star.util.XChangesBatch]The following code example first removes all commands that were defined in the user layer of the configuration branch org.openoffice.Office.Commands as having a defined starting point. Then it checks if it can get dispatch objects for some pre-defined commands.Then the example disables these commands and tries to get dispatch objects for them again. At the end, the code removes the disabled commands again, otherwise [PRODUCTNAME] would not be fully useable any longer.
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.lang.XMultiServiceFactory;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.util.XURLTransformer;
import com.sun.star.frame.XDesktop;
import com.sun.star.beans.UnknownPropertyException;
/*
* Provides example code how to enable/disable
* commands.
*/
public class DisableCommandsTest extends java.lang.Object {
/*
* A list of command names
*/
final static private String[] aCommandURLTestSet =
{
new String( "Open" ),
new String( "About" ),
new String( "SelectAll" ),
new String( "Quit" ),
};
private static XComponentContext xRemoteContext = null;
private static XMultiComponentFactory xRemoteServiceManager = null;
private static XURLTransformer xTransformer = null;
private static XMultiServiceFactory xConfigProvider = null;
/*
* @param args the command line arguments
*/
public static void main(String[] args) {
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 transformer = xRemoteServiceManager.createInstanceWithContext(
"com.sun.star.util.URLTransformer", xRemoteContext);
xTransformer = (com.sun.star.util.XURLTransformer)UnoRuntime.queryInterface(
com.sun.star.util.XURLTransformer.class, transformer);
Object configProvider = xRemoteServiceManager.createInstanceWithContext(
"com.sun.star.configuration.ConfigurationProvider", xRemoteContext);
xConfigProvider = (com.sun.star.lang.XMultiServiceFactory)UnoRuntime.queryInterface(
com.sun.star.lang.XMultiServiceFactory.class, configProvider);
// First we need a defined starting point. So we have to remove
// all commands from the disabled set!
enableCommands();
// Check if the commands are usable
testCommands(false);
// Disable the commands
disableCommands();
// Now the commands should not be usable anymore
testCommands(true);
// Remove disable commands to make Office usable again
enableCommands();
}
catch (java.lang.Exception e){
e.printStackTrace();
}
finally {
System.exit(0);
}
}
/**
* Test the commands that we enabled/disabled
*/
private static void testCommands(boolean bDisabledCmds) throws com.sun.star.uno.Exception {
// We need the desktop to get access to the current frame
Object desktop = xRemoteServiceManager.createInstanceWithContext(
"com.sun.star.frame.Desktop", xRemoteContext );
com.sun.star.frame.XDesktop xDesktop = (com.sun.star.frame.XDesktop)UnoRuntime.queryInterface(
com.sun.star.frame.XDesktop.class, desktop);
com.sun.star.frame.XFrame xFrame = xDesktop.getCurrentFrame();
com.sun.star.frame.XDispatchProvider xDispatchProvider = null;
if (xFrame != null) {
// We have a frame. Now we need access to the dispatch provider.
xDispatchProvider = (com.sun.star.frame.XDispatchProvider)UnoRuntime.queryInterface(
com.sun.star.frame.XDispatchProvider.class, xFrame );
if (xDispatchProvider != null) {
// As we have the dispatch provider we can now check if we get a dispatch
// object or not.
for (int n = 0; n < aCommandURLTestSet.length; n++) {
// Prepare the URL
com.sun.star.util.URL[] aURL = new com.sun.star.util.URL[1];
aURL[0] = new com.sun.star.util.URL();
com.sun.star.frame.XDispatch xDispatch = null;
aURL[0].Complete = ".uno:" + aCommandURLTestSet[n];
xTransformer.parseSmart(aURL, ".uno:");
// Try to get a dispatch object for our URL
xDispatch = xDispatchProvider.queryDispatch(aURL[0], "", 0);
if (xDispatch != null) {
if (bDisabledCmds)
System.out.println("Something is wrong, I got dispatch object for "
+ aURL[0].Complete);
else
System.out.println("Ok, dispatch object for " + aURL[0].Complete);
}
else {
if (!bDisabledCmds)
System.out.println("Something is wrong, I cannot get dispatch object for "
+ aURL[0].Complete);
else
System.out.println("Ok, no dispatch object for " + aURL[0].Complete);
}
resetURL(aURL[0]);
}
}
else
System.out.println("Couldn't get XDispatchProvider from Frame!");
}
else
System.out.println("Couldn't get current Frame from Desktop!");
}
/**
* Ensure that there are no disabled commands in the user layer. The
* implementation removes all commands from the disabled set!
*/
private static void enableCommands() {
// Set the root path for our configuration access
com.sun.star.beans.PropertyValue[] lParams = new com.sun.star.beans.PropertyValue[1];
lParams[0] = new com.sun.star.beans.PropertyValue();
lParams[0].Name = new String("nodepath");
lParams[0].Value = "/org.openoffice.Office.Commands/Execute/Disabled";
try {
// Create configuration update access to have write access to the configuration
Object xAccess = xConfigProvider.createInstanceWithArguments(
"com.sun.star.configuration.ConfigurationUpdateAccess", lParams);
com.sun.star.container.XNameAccess xNameAccess = (com.sun.star.container.XNameAccess)
UnoRuntime.queryInterface(com.sun.star.container.XNameAccess.class, xAccess);
if (xNameAccess != null) {
// We need the XNameContainer interface to remove the nodes by name
com.sun.star.container.XNameContainer xNameContainer =
(com.sun.star.container.XNameContainer)
UnoRuntime.queryInterface(com.sun.star.container.XNameContainer.class, xAccess);
// Retrieves the names of all Disabled nodes
String[] aCommandsSeq = xNameAccess.getElementNames();
for (int n = 0; n < aCommandsSeq.length; n++) {
try {
// remove the node
xNameContainer.removeByName( aCommandsSeq[n]);
}
catch (com.sun.star.lang.WrappedTargetException e) {
}
catch (com.sun.star.container.NoSuchElementException e) {
}
}
}
// Commit our changes
com.sun.star.util.XChangesBatch xFlush =
(com.sun.star.util.XChangesBatch)UnoRuntime.queryInterface(
com.sun.star.util.XChangesBatch.class, xAccess);
xFlush.commitChanges();
}
catch (com.sun.star.uno.Exception e) {
System.out.println("Exception detected!");
System.out.println(e);
}
}
/**
* Disable all commands defined in the aCommandURLTestSet array
*/
private static void disableCommands() {
// Set the root path for our configuration access
com.sun.star.beans.PropertyValue[] lParams = new com.sun.star.beans.PropertyValue[1];
lParams[0] = new com.sun.star.beans.PropertyValue();
lParams[0].Name = new String("nodepath");
lParams[0].Value = "/org.openoffice.Office.Commands/Execute/Disabled";
try {
// Create configuration update access to have write access to the configuration
Object xAccess = xConfigProvider.createInstanceWithArguments(
"com.sun.star.configuration.ConfigurationUpdateAccess", lParams);
com.sun.star.lang.XSingleServiceFactory xSetElementFactory =
(com.sun.star.lang.XSingleServiceFactory)UnoRuntime.queryInterface(
com.sun.star.lang.XSingleServiceFactory.class, xAccess);
com.sun.star.container.XNameContainer xNameContainer =
(com.sun.star.container.XNameContainer)UnoRuntime.queryInterface(
com.sun.star.container.XNameContainer.class, xAccess );
if (xSetElementFactory != null && xNameContainer != null) {
Object[] aArgs = new Object[0];
for (int i = 0; i < aCommandURLTestSet.length; i++) {
// Create the nodes with the XSingleServiceFactory of the configuration
Object xNewElement = xSetElementFactory.createInstanceWithArguments( aArgs );
if (xNewElement != null) {
// We have a new node. To set the properties of the node we need
// the XPropertySet interface.
com.sun.star.beans.XPropertySet xPropertySet =
(com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xNewElement );
if (xPropertySet != null) {
// Create a unique node name.
String aCmdNodeName = new String("Command-");
aCmdNodeName += i;
// Insert the node into the Disabled set
xPropertySet.setPropertyValue("Command", aCommandURLTestSet[i]);
xNameContainer.insertByName(aCmdNodeName, xNewElement);
}
}
}
// Commit our changes
com.sun.star.util.XChangesBatch xFlush = (com.sun.star.util.XChangesBatch)
UnoRuntime.queryInterface(com.sun.star.util.XChangesBatch.class, xAccess);
xFlush.commitChanges();
}
}
catch (com.sun.star.uno.Exception e) {
System.out.println("Exception detected!");
System.out.println(e);
}
}
/**
* reset URL so it can be reused
*
* @param aURL
* the URL that should be reseted
*/
private static void resetURL(com.sun.star.util.URL aURL) {
aURL.Protocol = "";
aURL.User = "";
aURL.Password = "";
aURL.Server = "";
aURL.Port = 0;
aURL.Path = "";
aURL.Name = "";
aURL.Arguments = "";
aURL.Mark = "";
aURL.Main = "";
aURL.Complete = "";
}
}
Intercepting Context Menus
[TOPIC:com.sun.star.ui.XContextMenuInterception]A context menu is displayed when an object is right clicked. Typically, a context menu has context dependent functions to manipulate the selected object, such as cut, copy and paste. Developers can intercept context menus before they are displayed to cancel the execution of a context menu, add, delete, or modify the menu by replacing context menu entries or complete sub menus. It is possible to provide new customized context menus.
Context menu interception is implemented by the observer pattern. This pattern defines a one-to-many dependency between objects, so that when an object changes state, all its dependents are notified. The implementation supports more than one interceptor.The root access point for intercepting context menus is a [IDL:com.sun.star.frame.Controller] object. The controller implements the interface [IDL:com.sun.star.ui.XContextMenuInterception] to support context menu interception.
Register and Remove an Interceptor
The [IDL:com.sun.star.ui.XContextMenuInterception] interface enables the developer to register and remove the interceptor code. When an interceptor is registered, it is notified whenever a context menu is about to be executed. Registering an interceptor adds it to the front of the interceptor chain, so that it is called first. The order of removals is arbitrary. It is not necessary to remove the interceptor that registered last.
Writing an Interceptor
Notification
A context menu interceptor implements the [IDL:com.sun.star.ui.XContextMenuInterceptor]interface. This interface has one function that is called by the responsible controller whenever a context menu is about to be executed.
ContextMenuInterceptorAction notifyContextMenuExecute ( [in] ContextMenuExecuteEvent aEvent)
The [IDL:com.sun.star.ui.ContextMenuExecuteEvent] is a struct that holds all the important information for an interceptor.
Members of [IDL:com.sun.star.ui.ContextMenuExecuteEvent]
[IDLS:com.sun.star.ui.ContextMenuExecuteEvent:ExecutePosition]
[IDL:com.sun.star.awt.Point]. Contains the position the context menu will be executed.
[IDLS:com.sun.star.ui.ContextMenuExecuteEvent:SourceWindow]
[IDL:com.sun.star.awt.XWindow]. Contains the window where the context menu has been requested.
[IDLS:com.sun.star.ui.ContextMenuExecuteEvent:ActionTriggerContainer]
[IDL:com.sun.star.container.XIndexContainer]. The structure of the intercepted context menu. The member implements the [IDL:com.sun.star.ui.ActionTriggerContainer] service.
[IDLS:com.sun.star.ui.ContextMenuExecuteEvent:Selection]
[IDL:com.sun.star.view.XSelectionSupplier]. Provides the current selection inside the source window.
Querying a Menu Structure
The ActionTriggerContainer member is an indexed container of context menu entries, where each menu entry is a property set. It implements the [IDL:com.sun.star.ui.ActionTriggerContainer] service. The interface [IDL:com.sun.star.container.XIndexContainer] directly accesses the intercepted context menu structure through methods to access, insert, remove and replace menu entries.
All elements in an ActionTriggerContainer member support the [IDL:com.sun.star.beans.XPropertySet] interface to get and set property values. There are two different types of menu entries with different sets of properties:
Type of Menu Entry
Service Name
Menu entry
"com.sun.star.ui.ActionTrigger"
Separator
"com.sun.star.ui.ActionTriggerSeparator"
It is essential to determine the type of each menu entry be querying it for the interface [IDL:com.sun.star.lang.XServiceInfo] and calling
boolean supportsService ( [in] string ServiceName )
The following example shows a small helper class to determine the correct menu entry type. [SOURCE:OfficeDev/MenuElement.java]
// A helper class to determine the menu element type
public class MenuElement
{
static public boolean IsMenuEntry( com.sun.star.beans.XPropertySet xMenuElement ) {
com.sun.star.lang.XServiceInfo xServiceInfo =
(com.sun.star.lang.XServiceInfo)UnoRuntime.queryInterface(
com.sun.star.lang.XServiceInfo.class, xMenuElement );
return xServiceInfo.supportsService( "com.sun.star.ui.ActionTrigger" );
}
static public boolean IsMenuSeparator( com.sun.star.beans.XPropertySet xMenuElement ) {
com.sun.star.lang.XServiceInfo xServiceInfo =
(com.sun.star.lang.XServiceInfo)UnoRuntime.queryInterface(
com.sun.star.lang.XServiceInfo.class, xMenuElement );
return xServiceInfo.supportsService( "com.sun.star.ui.ActionTriggerSeparator" );
}
}
Figure 4.1: Determine the menu element type
The [IDL:com.sun.star.ui.ActionTrigger] service supported by selectable menu entries has the following properties:
Properties of [IDL:com.sun.star.ui.ActionTrigger]
[IDLS:com.sun.star.ui.ActionTrigger:Text]
string. Contains the text of the label of the menu entry.
[IDLS:com.sun.star.ui.ActionTrigger:CommandURL]
string. Contains the command URL that defines which function will be executed if the menu entry is selected by the user.
[IDLS:com.sun.star.ui.ActionTrigger:HelpURL]
string. This optional property contains a help URL that points to the help text.
[IDLS:com.sun.star.ui.ActionTrigger:Image]
[IDL:com.sun.star.awt.XBitmap]. This property contains an image that is shown left of the menu label. The use is optional so that no image is used if this member is not initialized.
[IDLS:com.sun.star.ui.ActionTrigger:SubContainer]
[IDL:com.sun.star.container.XIndexContainer]. This property contains an optional sub menu.
The [IDL:com.sun.star.ui.ActionTriggerSeparator] service defines only one optional property:
Property of [IDL:com.sun.star.ui.ActionTriggerSeparator]
[IDLS:com.sun.star.ui.ActionTriggerSeparator:SeparatorType]
[IDL:com.sun.star.ui.ActionTriggerSeparatorType]. Specifies a certain type of a separator. Currently the following types are possible:
const int LINE = 0const int SPACE = 1const int LINEBREAK = 2
Changing a Menu
It is possible to accomplish certain tasks without implementing code in a context menu interceptor, such as preventing a context menu from being activated. Normally, a context menu is changed to provide additional functions to the user.
As previously discussed, the context menu structure is queried through the ActionTriggerContainer member that is part of the [IDL:com.sun.star.ui.ContextMenuExecuteEvent] structure. The [IDL:com.sun.star.ui.ActionTriggerContainer] service has an additional interface [IDL:com.sun.star.lang.XMultiServiceFactory] that creates [IDL:com.sun.star.ui.ActionTriggerContainer], [IDL:com.sun.star.ui.ActionTrigger] and [IDL:com.sun.star.ui.ActionTriggerSeparator] objects. These objects are used to extend a context menu.
The [IDL:com.sun.star.lang.XMultiServiceFactory] implementation of the ActionTriggerContainer implementation supports the following strings:
String
Object
"com.sun.star.ui.ActionTrigger"
Creates a normal menu entry.
"com.sun.star.ui.ActionTriggerContainer"
Creates an empty sub menu1 .
"com.sun.star.ui.ActionTriggerSeparator"
Creates an unspecified separator2 .
1 A sub menu cannot exist by itself. It has to be inserted into a [IDL:com.sun.star.ui.ActionTrigger]!
2 The separator has no special type. It is the responsibility of the concrete implementation to render an unspecified separator.
Finishing Interception
Every interceptor that is called directs the controller how it continues after the call returns. The enumeration [IDL:com.sun.star.ui.ContextMenuInterceptorAction] defines the possible return values.
Values of [IDL:com.sun.star.ui.ContextMenuInterceptorAction]
IGNORED
Called object has ignored the call. The next registered [IDL:com.sun.star.ui.XContextMenuInterceptor] should be notified.
CANCELLED
The context menu must not be executed. No remaining interceptor will be called.
EXECUTE_MODIFIED
The context menu has been modified and should be executed without notifying the next registered [IDL:com.sun.star.ui.XContextMenuInterceptor].
CONTINUE_MODIFIED
The context menu was modified by the called object. The next registered [IDL:com.sun.star.ui.XContextMenuInterceptor] should be notified.
The following example shows a context menu interceptor that adds a a sub menu to a menu that has been intercepted at a controller, where this [IDL:com.sun.star.ui.XContextMenuInterceptor] has been registered. This sub menu is inserted ino the context menu at the topmost position. It provides help functions to the user that are reachable through the menu Help. [SOURCE:OfficeDev/ContextMenuInterceptor.java]
import com.sun.star.ui.*;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.XIndexContainer;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.Exception;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.lang.IllegalArgumentException;
public class ContextMenuInterceptor implements XContextMenuInterceptor {
public ContextMenuInterceptorAction notifyContextMenuExecute(
com.sun.star.ui.ContextMenuExecuteEvent aEvent ) throws RuntimeException {
try {
// Retrieve context menu container and query for service factory to
// create sub menus, menu entries and separators
com.sun.star.container.XIndexContainer xContextMenu = aEvent.ActionTriggerContainer;
com.sun.star.lang.XMultiServiceFactory xMenuElementFactory =
(com.sun.star.lang.XMultiServiceFactory)UnoRuntime.queryInterface(
com.sun.star.lang.XMultiServiceFactory.class, xContextMenu );
if ( xMenuElementFactory != null ) {
// create root menu entry for sub menu and sub menu
com.sun.star.beans.XPropertySet xRootMenuEntry =
(XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance ( "com.sun.star.ui.ActionTrigger " ));
// create a line separator for our new help sub menu
com.sun.star.beans.XPropertySet xSeparator =
(com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance( "com.sun.star.ui.ActionTriggerSeparator" ) );
Short aSeparatorType = new Short( ActionTriggerSeparatorType.LINE );
xSeparator.setPropertyValue( "SeparatorType", (Object)aSeparatorType );
// query sub menu for index container to get access
com.sun.star.container.XIndexContainer xSubMenuContainer =
(com.sun.star.container.XIndexContainer)UnoRuntime.queryInterface(
com.sun.star.container.XIndexContainer.class,
xMenuElementFactory.createInstance(
"com.sun.star.ui.ActionTriggerContainer" ));
// intialize root menu entry "Help"
xRootMenuEntry.setPropertyValue( "Text", new String( "Help" ));
xRootMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5410" ));
xRootMenuEntry.setPropertyValue( "HelpURL", new String( "5410" ));
xRootMenuEntry.setPropertyValue( "SubContainer", (Object)xSubMenuContainer );
// create menu entries for the new sub menu
// intialize help/content menu entry
// entry "Content"
XPropertySet xMenuEntry = (XPropertySet)UnoRuntime.queryInterface(
XPropertySet.class, xMenuElementFactory.createInstance (
"com.sun.star.ui.ActionTrigger " ));
xMenuEntry.setPropertyValue( "Text", new String( "Content" ));
xMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5401" ));
xMenuEntry.setPropertyValue( "HelpURL", new String( "5401" ));
// insert menu entry to sub menu
xSubMenuContainer.insertByIndex ( 0, (Object)xMenuEntry );
// intialize help/help agent
// entry "Help Agent"
xMenuEntry = (com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance (
"com.sun.star.ui.ActionTrigger " ));
xMenuEntry.setPropertyValue( "Text", new String( "Help Agent" ));
xMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5962" ));
xMenuEntry.setPropertyValue( "HelpURL", new String( "5962" ));
// insert menu entry to sub menu
xSubMenuContainer.insertByIndex( 1, (Object)xMenuEntry );
// intialize help/tips
// entry "Tips"
xMenuEntry = (com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance(
"com.sun.star.ui.ActionTrigger " ));
xMenuEntry.setPropertyValue( "Text", new String( "Tips" ));
xMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5404" ));
xMenuEntry.setPropertyValue( "HelpURL", new String( "5404" ));
// insert menu entry to sub menu
xSubMenuContainer.insertByIndex ( 2, (Object)xMenuEntry );
// add separator into the given context menu
xContextMenu.insertByIndex ( 0, (Object)xSeparator );
// add new sub menu into the given context menu
xContextMenu.insertByIndex ( 0, (Object)xRootMenuEntry );
// The controller should execute the modified context menu and stop notifying other
// interceptors.
return com.sun.star.ui.ContextMenuInterceptorAction.EXECUTE_MODIFIED ;
}
}
catch ( com.sun.star.beans.UnknownPropertyException ex ) {
// do something useful
// we used a unknown property
}
catch ( com.sun.star.lang.IndexOutOfBoundsException ex ) {
// do something useful
// we used an invalid index for accessing a container
}
catch ( com.sun.star.uno.Exception ex ) {
// something strange has happend!
}
catch ( java.lang.Throwable ex ) {
// catch java exceptions – do something useful
}
return com.sun.star.ui.ContextMenuInterceptorAction.IGNORED;
}
File Naming Conventions
As a recommendation, UNO component libraries and UNO packages should be named according to the following naming scheme:
<NAME>[<VERSION>].uno.(so|dll|dylib|jar|zip)
This recommendation applies to shared libraries and Java archives, as well as UNO packages which are deployed by the Extension Manager pkgchk as described in section [CHAPTER:ExtensionsComponents.Deployment.PackageInstall].
This file name convention results in file names such as:
component.uno.so
component1.uno.dll
component0.1.3.uno.dylib
component.uno.jar
component1.5.uno.zip
<NAME> should be a descriptive name, optionally extended by version information as shown below, followed by the characters .uno and the necessary file extension.
The term .uno is placed next to the platform-specific extension to emphasize that this is a special type of shared library, jar, or zip file.
Usually a shared library or jar has to be registered with UNO to be useful, as its shared library interface only consists of the component operations. Zipped files cannot easily be recognized as UNO packages. In both cases the .uno tag informs users that a component or package file is meant for use with UNO.
Since the given naming scheme is only a suggestion, there might be component shared libraries that do not contain the .uno addition in their names. Therefore, no tool should build assumptions on whether a shared library name contains .uno or not.
<VERSION> is optional and should be in the form:
<VERSION> = <MAJOR> [.<MINOR> [.<MICRO>]]
<MAJOR> = <NUMBER>
<MINOR> = <NUMBER>
<MICRO> = <NUMBER>
<NUMBER> = 0 | 1–9 0–9*
Using the version tag in the file name of a shared library or jar is primarily meant for simple components that are not part of an extension larger UNO package file deployed by the Extension Manager pkgchk. Such components are usually made up of a single shared library, and different file names for different versions can be useful, for instance in bug reports.
The version of a larger UNO package should be indicated in the package file name, as in component1.5.uno.zip., which results in a corresponding path name in the package cache.
The version of components that are part of the [PRODUCTNAME] installation is already well defined by the version and build number of the installed [PRODUCTNAME] itself.
It is up to the developer how the version scheme is used. You can count versions of a given component shared library using MAJOR alone, or add MINOR and MICRO as needed.
Note graphics marks a special text section
If version is used, it must be placed before the platform-specific extension, never after it. Under Linux and Solaris, there is a convention to add a version number after the .so, but that version number has different semantics than the version number used here. In short, those version numbers change whenever the shared library's interface changes, whereas the UNO component interface with the component operations component_getFactory() etc. never changes.
The following considerations give an overview of ways that a component can evolve:
A component shared library's interface, as defined by the component operations such as component_getFactory() is assumed to be stable.
The UNO services offered by a component can change:
compatibly: by changing an implementation in the component file but adhering to its specification, or by adding a new UNO service implementation to a component file
incompatibly: by removing an implementation, or by removing a UNO service from a component
indirectly compatibly: when one of the UNO services changes compatibility and the component is adapted accordingly. This can happen when a service specification is extended by additional optional interfaces, and the component is altered to support these interfaces.
When an implementation in a component file is changed, for instance when a bug is fixed, such a change will typically be compatible unless clients made themselves dependent on the bug. This can happen when clients considered the bug a feature or worked around the bug in a way that made them dependent on the bug. Therefore developers must be careful to program according to the specification, not the implementation.
Finally, a component shared library can change its dependencies on other shared libraries. Examples of such dependencies are:
C/C++ runtime libraries
such as libc.so.6, libstdc++.so.3.0.1, and libstlport_gcc.so
UNO runtime libraries
such as libcppu.so.3.1.0 and libcppuhelpergcc3.so.3.1.0
[PRODUCTNAME] libraries
such as libsvx644li.so
Dependency changes are typically incompatible, as they rely on compatible or incompatible changes of the component's environment.
Deployment Options for Components
Component are usually distributed and deployed as extensions (see chapter [CHAPTER:Extensions]). However, by using legacy tools, such as regcomp, and regmerge, it is also possible to install components, which can be more convenient during development.There are a number of opportunities to deploy components to a [PRODUCTNAME] environment. The options available depend on how the new component is to be deployed. If [PRODUCTNAME] is installed in a network mode, the new component could be available to an entire network or to certain users. Another option is to install the new component to individual desktop installations. Third, you may want to use UNO components without any local installation at all.
Pay attention to the following important text section
The use of pkgchk is deprecated, and therefore unopkg should be used from now on. Therefore using pkgchk is not described here anymore. Refer to the developer's guide for [PRODUCTNAME] [OO1.1] for information about pkgchk.
UNO Package Installation Using unopkg
[PRODUCTNAME] has a simple concept for adding components, UNO type libraries, configuration schema or data, [PRODUCTNAME] Basic, or Dialog libraries to an existing installation. Bringing a UNO component into a [PRODUCTNAME] installation involves the following steps:
Get a UNO package from a third party vendor or package your component as described below.
Run a command line shell, change to <OfficePath>/program and run the tool unopkg from the program directory as follows:
For a user package, run unopkg:
[<OfficePath>/program] $ unopkg add myPackage.uno.pkg
A shared package is installed using the option --shared:
[<OfficePath>/program] $ unopkg add –-shared myPackage.uno.pkg
The tool analyzes the given packages, and installs them, for example, registering components found in the packages and configures them as needed.
To remove a package from the [PRODUCTNAME] installation, the remove sub-command is used:
[<OfficePath>/program] $ unopkg remove myPackage.uno.pkg
Since there are many more commands, isee the command-line syntax, calling unopkg –help. You may notice that there is a Graphical User Interface (GUI) for unopkg as well. You can call it using unopkg gui or by way of the Tools menu bar.
Pay attention to the following important text section
Although it is now possible to deploy “live” into a running [PRODUCTNAME] process, there are some limitations you should be aware of: Removing a typelibrary from a running process is not possible, because this may lead to crashes when the type is needed. Thus if you, for example, uninstall a package that comes with a UNO typelibrary, these types will vanish upon next process startup, but not before.
There may also be problems with cached configuration data, because parts of the running process do not listen for configuration updates (for example, menu bars). Most often, those parts read the configuration just once upon startup.
Package Bundle Structure
A Package Bundle is a zip file having a name that ends on “.oxt“ (formerly ”.uno.pkg”). This zip file can contain UNO components, type libraries, configuration files, dialog or basic libraries (all of the latter are denoted to be packages, too). The unopkg tool inflates all deployed bundles into its cache directory, preserving the file structure of the zip file. It also copies all non-bundle files to its cache directory.
Note graphics marks a special text section
For backward compatibility, legacy bundles (extension uno.pkg, .zip) that have been formerly deployed using pkgchk are deployable, too. Migrate legacy bundles to the current .oxt format. This can easily be done using the GUI, exporting a legacy bundle as .an .oxt file. When a legacy bundle is exported, a manifest.xml file is generated, enumerating the detected items of the bundle.
The different items of a Package Bundle zip file are described in its META-INF/manifest.xml file, which lists all items and their media-type:
Shared Library UNO Components
The media-type for a shared library UNO component is “application/vnd.sun.star.uno-component;type=native”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=native" manifest:full-path="myComponent.uno.so"/>
RDB Type Library
The media-type for a UNO RDB typelibrary is “application/vnd.sun.star.uno-typelibrary;type=RDB”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=RDB" manifest:full-path="myTypes.uno.rdb"/>
Jar Type Library
The media-type for a UNO Jar typelibrary is “application/vnd.sun.star.uno-typelibrary;type=Java”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=Java" manifest:full-path="myTypes.uno.jar"/>
Keep in mind that the RDB variant of that typelibrary must be deployedalso. This is currently necessary, because your Java UNO types may be referenced from native UNO code.
Uno Jar Components
The media-type for a UNO Jar component is “application/vnd.sun.star.uno-component;type=Java”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=Java" manifest:full-path="myComponent.uno.jar"/>
UNO Python Components
The UNO deployment tool pkgchk now supports registration of Python components (.py files). Those files are registered using the com.sun.star.loader.Python loader. For details concerning Python-UNO, please refer to http://udk.openoffice.org/python/python-bridge.html.The media-type for a UNO Python component is “application/vnd.sun.star.uno-component;type=Python”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=Python" manifest:full-path="myComponent.uno.py"/>
[PRODUCTNAME] Basic Libraries
[PRODUCTNAME] Basic libraries are linked to the basic library container files. Refer to [CHAPTER:BasicAndDialogs] for additional information.The media-type for a [PRODUCTNAME] Basic Library is “application/vnd.sun.star.basic-library”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.basic-library" manifest:full-path="myBasicLib/"/>
Dialog Libraries
Dialog libraries are linked to the basic dialog library container files. Refer to [CHAPTER:BasicAndDialogs] for additional information.The media-type for a dialog library is “application/vnd.sun.star.dialog-library”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.dialog-library" manifest:full-path="myDialog/"/>
Configuration Data Files
The media-type for a configuration data file is “application/vnd.sun.star.configuration-data”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-data" manifest:full-path="myData.xcu"/>
Configuration Schema Files
The media-type for a configuration schema file is “application/vnd.sun.star.configuration-schema”, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-schema" manifest:full-path="mySchema.xcs"/>
Beware of adding concurring schemata, for example, two .xcs files defining the same schema name (oor:package, oor:name), but different definitions.
Package Bundle Description
If you want to add a package bundle description (which shows up in the balloon help of a bundle node in the GUI), then you can do so by specifying localized UTF-8 files, for example,
<manifest:file-entry manifest:media-type="application/vnd.sun.star.package-bundle-description;locale=en" manifest:full-path="readme.en" /><manifest:file-entry manifest:media-type="application/vnd.sun.star.package-bundle-description;locale=de" manifest:full-path="readme.de" />manifest:media-type="application/vnd.sun.star.package-bundle-description" manifest:full-path="readme.txt" />
The best matching locale (against the current installation's locale) is taken. The locale is of the form "locale=language-country-variant".
Arbitrary Files
Arbitrary files are inflated into the UNO packages cache, too. You can, for instance, deploy an image for add-on menus within a package, or any other file needed by your component. The [PRODUCTNAME] configuration is used to find out in which path this file is located in a particular installation.When you define a package containing additional files, include an .xcu configuration data file, which points to your files. Use a variable %origin% as a placeholder for the exact path where the file will be copied by the package installer. When unopkg installs the data, it resolves %origin% to a macrodified URL in the configuration that points to this path. The URL in the configuration contains a macro that has to be expanded before it is a valid file URL. This can be done using the [IDL:com.sun.star.util.MacroExpander] service. The %origin% variable is, for instance, used by the ImageIdentifier property of add-on menus and toolbar items, which is described in the [CHAPTER:Components.Integrating.UIAddOns.Configuration] section.
The description.xml
This file contain additional information about the content of the package bundle. And is described in the following chapter. It is not contained in the manifest.xml.
Platform strings
When you implement a UNO native component, for example, a .dll or .so file, then this file is only deployable on that specific platform. It is often convenient to package a bundle for different platforms. For instance, you compile your component for x86 Linux, Solaris SPARC and Windows. You have to tell unopkg which version of your component file corresponds to which platform via a platform attribute supplied with the media-type, for example,
<manifest:file-entry manifest:media-type= "application/vnd.sun.star.unocomponent;type=native;platform=Windows"
manifest:full-path="windows/mycomp.uno.dll"/>
<manifest:file-entry manifest:media-type= "application/vnd.sun.star.uno-component;type=native;platform=Linux_x86"
manifest:full-path="linux/myComp.uno.so"/>
<manifest:file-entry manifest:media-type= "application/vnd.sun.star.uno-component;type=native;platform=Solaris_SPARC"
manifest:full-path="solsparc/myComp.uno.so"/>
Please notice that the manifest.xml file currently only contains entries for files which require particular processing. For example, a component needs to be registered, a type library needs to be merged with all other type libraries, etc.
The description.xml
The description.xml is a means to provide additional useful information. At the time of its introduction it carried information about licenses and dependencies. It will be extended when needed by new features in the future. The file must be located in the root of the extension.
The XML document uses the namespace:
"http://openoffice.org/extensions/description/2006"
and the root element must be <description>
Simple License
This feature is about displaying a license text to the user during installation. The user can agree or decline the license, where in the latter case the installation will be aborted. It is called “Simple License” because there is no tamper resistent mechanism that prevents the installation in case the user does not agree to the license. It also does not do anything more than just displaying a license text. However it provides a way to use localized licenses. More on that later.
The license text is displayed either in a dialog or in the console dependent on the way the package manager was started. When it was started by the tools->Package Manager menu item or by invoking unopkg gui in the console then a dialog is used. By using unopkg add the license text will be displayed in the console and user input has to be done through the same.
The license dialog or the license text in the console is displayed when the extension is being installed. Currently there are two modes to install extensions, user mode and shared mode. An extension that was installed in user mode (let's call it a user extension) can only be used by just that person who installed it. If the extension was installed in shared mode (let's call it a shared extension), then it can be used by all users. Since the license text is only displayed during installation, all users who are using a shared extension will not see any license text (except the user who installed this shared extension). However, the publisher of the extension may think it necessary that everyone who wants to use it has to agree to the license first. For this purpose, he can mark the extension accordingly. This extension can then only be installed in user mode and not in shared mode. Likewise the extension can be marked indicating that only the person who installs it needs to agree to the license. Such an extension can be installed in both modes. But when installing in user mode then every user has to agree to the license nonetheless.
Here is an example of the description.xml:
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">
<registration>
<simple-license accept-by="user" default-license-id="de">
<license-text xlink:href="registration/license_de.txt" lang="de" license-id="de" />
<license-text xlink:href="registration/license_en_US.txt" lang="en-US" />
</simple-license>
</registration>
</description>
This is the RELAX NG schema of the relevant elements
default namespace = "http://openoffice.org/extensions/description/2006"
namespace xlink = "http://www.w3.org/1999/xlink"
element description {
element registration {
element simple-license {
attribute accept-by {"user" | "admin"} ,
attribute default-license-id {xsd:IDREF },
element license-text {
attribute xlink:href {xsd:anyURI} ,
attribute lang {xsd:language} ,
attribute license-id {xsd:ID} ?
} +
}
} ?
}
In this example, the license would have to be agreed to by all users (that means no shared mode installation). This is indicated by the value “user” of the attribute accept-by in the <simple-license> element. The attribute could also have the value “admin”, which would indicate that the license needs only be agreed to by the person who installs it.
The <license-text> elements contain information about the files which contain the text that is displayed. The content of these files must be UTF-8 encoded. It is displayed exactly as it is in the file. That is, no formatting occurs. There can be one to many <license-text> elements, where each element provides information about a different language of the license text. The attribute xlink:href contains a relative URL (relative to the root directory of the extension) which points to a file which countains the license text in exacty one language. Which language is indicated by lang attribute.
If the package manager does not find a <license-text> element which matches the locale of OOo then it will pick the <license-text> that is marked as the default language. This mark is expressed by the license-id attribute of <license-text> and the default-license-id attribute of the <simple-license> element. There must always be exactly one <license-text> whoose attribute value is the same as that from <simple-license>. This <license-text> element is then used as the default.
Determining the Locale of the License
The locale used by OOo and the license text files is expressed by a language string according to RFC 3066. This string contains the language and can optionally contain a country and further information. Let's assume that the office uses britisch english (en-GB) end the extension has two license text files, one in german (de), which is also the default, and the other in english from New Zealand (en-NZ). Obviously there is no perfekt match, since en-GB is not en-NZ. But we would not want to use the default yet, because en-NZ is most probably closer to en-GB as german. Therefore we use an algorithm that tries to find a “close match” of the local before it resorts to the default. Here is the algorithm:
In order to find the appropriate <license-text> element, the values of lang attribute are compared with the office's Locale. Both are represented as strings according to RFC3066. The comparison is done case sensitive.
Input to the algorithm:
All license-text elements.
The locale of the office
Output of the algoritm:
A license-text element
Algorithm:
The language, country and variant part of the office's locale are used to find a matching license-text. If there is an exact match then the respective license-text is selected as output and we are done. Only the first match is used.
The language and country part of the office's locale are used to find a matching license-text. If there is an exact match then the respective license-text is selected as output and we are done.
The language and country part of the office's locale are used to find a matching license-text. This time, we try to match only the language and country parts. For example, the office locale strings “en-US”, “en-US-east” match the lang attribute with the values “en-US-north”, “en-US-south”,etc. The first license-text with a matching lang attribute is selected as output. If there is a match then we are done.
Only the language part of the office's locale is used to find a matching license-text. If there is an exact match then the respective license-text is selected as output and we are done. Only the first match is used.
Only the language part of the office's locale is used to find a matching license-text. This time, we try to match only the language part. For example, the office locale strings “en”, “en-US”, “en-US-east” match the lang attribute with the values “en-GB”,“en-GB-north”, etc. The first license-text with a matching lang attributed is selected as output. If there is a match then we are done.
The license-text element which is marked as “default” will be selected. That is, the value of the attribute license-id must match the default-license-id of the simple-license element.
The following example show what values would match.
Example 1: Locale of OOo is en-US and the relevant part of the description.xml is:
<simple-license accept-by="user" default-license-id="en-US" >
<license-text xlink:href="lic_en-GB" lang="en-GB" />
<license-text xlink:href="lic_en-US" lang="en-US" license-id="en-US" />
</simple-license>
The <license-text> with lang=”en-US” will be selected.
Example 2: Locale of OOo is en-US and the relevant part of the description.xml is:
<simple-license accept-by="user" default-license-id="en-NZ" >
<license-text xlink:href="lic_en-GB" lang="en-GB" />
<license-text xlink:href="lic_en-NZ" lang="en-NZ" license-id="en-NZ" />
</simple-license>
The <license-text> with lang=”en-GB” will be selected.
Example 3: Locale of OOo is en-US and the relevant part of the description.xml is:
<simple-license accept-by="user" default-license-id="en-NZ" >
<license-text xlink:href="lic_en" lang="en" />
<license-text xlink:href="lic_en-GB" lang="en-GB" />
<license-text xlink:href="lic_en-NZ" lang="en-NZ" license-id="en-NZ" />
</simple-license>
The <license-text> with lang=”en” will be selected.
Example 4: Locale of OOo is de-DE and the relevant part of the description.xml is:
<simple-license accept-by="user" default-license-id="en-NZ" >
<license-text xlink:href="lic_en" lang="en" />
<license-text xlink:href="lic_en-GB" lang="en-GB" />
<license-text xlink:href="lic_en-NZ" lang="en-NZ" license-id="en-NZ" />
</simple-license>
The <license-text> with lang=”en-NZ” will be selected.
Description of XML Elements
<registration> Element
Contains the <simple-license> element.
XPath: /description/registration
Child elements:
<simple-license>
Remarks:
If the <registration> element existst, then it must have a child element.
<simple-license> Element
The element contains the <license-text> elements, determines if all user must agree to the license, or just the person who installs it, and determines a default <license-text> element .
XPath: /description/registration/simple-license
Child elements:
<license-text>
Attributes:
Attribute
Description
accept-by
Required.Value is either “user” or “admin”. “user” means that every user has to agree to the license. That is, the extension can only be installed as user extension but not as shared extension. If it has the value “admin” then it can be deployed as shared extension as well. In that case only the person who installs it has to agree to the license. Individual users will not be asked to accept the license. They can use the extension right away. In case the value is “user” and the extension is being installed as user extension then the user must always agree to the license.
default-license-id
Required. Determines what <license-text> is used if no <license-text> element has a lang attribute whoose value matches the locals of OOo. There must always be exactly one <license-text> element whith a license-id attribute whoose value matches that of the default-license-id. The type is xsd:IDREF
Remarks:
If the <simple-license> element existst, then it must have at least one child element.
<license-text> Element
The element contains information about where to find the file containing the license text, which language it uses, and if this element is the “default” <license-text>
XPath: /description/registration/simple-license/license-text
Attributes:
Attribute
Description
xlink:href
Required. The value is a relative URL to the file which contains the license text. The base URL is the URL of the root directory of the extension. That is, if the extension has been unzipped, then the resulting directory is the root directory.
lang
Required. A language identifier according to RFC 3066. Values can be for example: en, en-US, en-US-variant, etc. Currently OOo does not make use of variants.
license-id
Optional. However one license-text element must have this attribute and the value must match the value of the default-license-id attribute of the <simple-license> element. The type is xsd:ID.
Dependencies
One can imagine a large variety of dependencies an extension can have on its environment: availability of certain UNO types and services, availability of features only present since some specific version of OOo, availability of other installed extensions, availability of third-party libraries, etc.
To support this, a mechanism is introduced so that extensions can bring along a specification of their dependencies. When a user wants to install an extension, the application first checks whether all dependencies are met. If not, an error dialog is displayed informing the user that the extension could not be installed.
In OOo 2.0.4, no actual dependencies are defined yet. That is, extensions created today cannot specify any dependencies. Only future extensions will be able to specify dependencies (where the nature of those dependencies is not known yet).
OOo 2.0.3 and earlier are not prepared to correctly handle extensions with dependencies. In OOo 2.0.3 and earlier, if a .uno.pkg (or .zip) extension specifies any dependencies, they are effectively ignored and the extension is installed nonetheless. An .oxt extension cannot be installed at all in OOo 2.0.3 and earlier. So, if an extension shall run in any OOo version, it should be named .uno.pkg and should not specify any dependencies; if an extension shall only run in OOo 2.0.4 and later, it should be named .oxt and should not specify any dependencies; and if an extension shall only run in a future OOo version, it should be named .oxt and should specify the appropriate dependencies (which will be defined by the time the given OOo version is available).
There is a certain dilemma: On the one hand, nothing is yet known about the kinds of dependencies that will be defined in the future. On the other hand, at least some information about the unsatisfied dependencies of a future extension must be displayed in OOo 2.0.4. Therefore, each dependency specified by an extension must contain a human-readable (non-localized, English) name that can be displayed to the user, conveying at least rudimentary information about the nature of the unsatisfied dependency. Future versions of OOo that already know a certain kind of dependency are expected to display more detailed information.
Within the description.xml, dependencies are recorded as follows: An XML element whose name consists of the namespace name http://openoffice.org/extensions/description/2006 and the local part dependencies may appear at most once as a child of the root element. This element has as its element content an arbitrary number of child elements that are not further constrained expect for the following: Each such child element should have an attribute whose name consists of the namespace name http://openoffice.org/extensions/description/2006 and the local part name. Each such child element represents one dependency, and the value of its name attribute shall contain the human-readable dependency name (and the value, after normalization, should not be empty).
If an extensions is either not of type .oxt, .uno.pkg, or .zip, or does not contain a description.xml, or the description.xml does not contain a dependencies element, or the dependencies element does not contain any child elements, then the extension does not specify any dependencies.
Background: UNO Registries
This section explains the necessary steps to deploy new UNO components manually into an installed [PRODUCTNAME]. Background information is provided and the tools required to test deployment are described. The developer and deployer of the component should be familiar with this section. If the recommendations provided are accepted, interoperability of components of different vendors can be achieved easily.
UNO registries store binary data in a tree-like structure. The stored data can be accessed within a registry programmatically through the [IDL:com.sun.star.registry.SimpleRegistry] service, however this is generally not necessary. Note that UNO registries have nothing to do with the Windows registry, except that they follow a similar concept for data storage.
UNO-registries mainly store two types of data :
Type-library
To invoke UNO calls from BASIC or through an interprocess connection, the core UNO bridges need information about the used data types. UNO stores this information into a type library, so that the same data is reusable from any bridge. This is in contrast to the CORBA approach, where code is generated for each data type that needs to be compiled and linked into huge libraries. Every UNOIDL type description is stored as a binary large object (BLOB) that is interpreted by the [IDL:com.sun.star.reflection.TypeDescriptionProvider] service.
Information about registered components
One basic concept of UNO is to create an instance of a component simply by its service name through the ServiceManager. The association between the service name and the shared library or .jar-file where the necessary compiled code is found is stored into a UNO-registry.The structure of this data is provided below. Future versions of [PRODUCTNAME] will probably store this information in an XML file that will make it modifiable using a simple text editor.
Both types of data are necessary to run a UNO-C++ process. If the types of data are not present, it could lead to termination of the program. UNO processes in general open their registries during startup and close them when the process terminates. Both types of data are commonly stored in a file with an .rdb suffix ( rdb=registry database ), but this suffix is not mandatory.
UNO Type Library
All type descriptions must be available within the registry under the /UCR main key (UCR = Uno Core Reflection) to be usable in a UNO C++ process . Use the regview tool to view the file <officepath>/program/types.rdb. The regview tool comes with the [PRODUCTNAME] SDK.
For instance:
$ regview types.rdb /UCR
prints all type descriptions used within the office to stdout. To check if a certain type is included within the registry, invoke the following command:
$ regview types.rdb /UCR/com/sun/star/bridge/XUnoUrlResolver
/UCR/com/sun/star/bridge/XUnoUrlResolver Value: Type = RG_VALUETYPE_BINARY Size = 461 Data = minor version: 0 major version: 1 type: 'interface' name: 'com/sun/star/bridge/XUnoUrlResolver' super name: 'com/sun/star/uno/XInterface' Doku: "" number of fields: 0 number of methods: 1 method #0: com/sun/star/uno/XInterface resolve([in] string sUnoUrl) raises com/sun/star/connection/NoConnectException, com/sun/star/connection/ConnectionSetupException, com/sun/star/lang/IllegalArgumentException Doku: "" number of references: 0
The regview tool decodes the format of the BLOB containing the type description and presents it in a readable form.
Component Registration
The UNO component provides the data about what services are implemented. In order not to load all available UNO components into memory when starting a UNO process, the data is assembled once during setup and stored into the registry. The process of writing this information into a registry is called component registration. The tools used to perform this task are discussed below.
For an installed [PRODUCTNAME], the services.rdb contains the component registration information. The data is stored within the /IMPLEMENTATIONS and /SERVICES key. The code below shows a sample SERVICES key for the [IDL:com.sun.star.io.Pipe] service.
$ regview services.rdb /SERVICES/com.sun.star.io.Pipe
/SERVICES/com.sun.star.io.Pipe Value: Type = RG_VALUETYPE_STRINGLISTSize = 38Len = 1Data = 0 = "com.sun.star.comp.io.stm.Pipe"
The code above contains one implementation name, but it could contain more than one. In this case, only the first is used. The following entry can be found within the IMPLEMENTATIONS section:
$ regview services.rdb /IMPLEMENTATIONS/com.sun.star.comp.io.stm.Pipe
/IMPLEMENTATIONS/com.sun.star.comp.io.stm.Pipe / UNO / ACTIVATOR Value: Type = RG_VALUETYPE_STRING Size = 34 Data = "com.sun.star.loader.SharedLibrary"
/ SERVICES / com.sun.star.io.Pipe / LOCATION Value: Type = RG_VALUETYPE_STRING Size = 8 Data = "stm.dll"
The implementations section holds three types of data.
The loader to be used when the component is requested at runtime (here [IDL:com.sun.star.loader.SharedLibrary]).
The services supported by this implementation.
The URL to the file the loader uses to access the library (the url may be given relative to the [PRODUCTNAME] library directory for native components as it is in this case).
Command Line Registry Tools
There are various tools to create, modify and use registries. This section shows some common use cases. The regmerge tool is used to merge multiple registries into a sub-key of an existing or new registry. For instance:
$ regmerge new.rdb / test1.rdb test2.rdb
merges the contents of test1.rdb and test2.rdb under the root key / of the registry database new.rdb . The names of the keys are preserved, because both registries are merged into the root-key. In case new.rdb existed before, the previous contents remain in new.rdb unless an identical key names exist in test1.rdb and test2.rdb. In this case, the content of these keys is overwritten with the ones in test1.rdb or test2.rdb. So the above command is semantically identical to:
$ regmerge new.rdb / test1.rdb$ regmerge new.rdb / test2.rdb
The following command merges the contents of test1.urd and test2.urd under the key /UCR into the file myapp_types.rdb.
$ regmerge myapp_types.rdb /UCR test1.urd test2.urd
The names of the keys in test1.urd and test2.urd should only be added to the /UCR key. This is a real life scenario as the files produced by the idl-compiler have a .urd-suffix. The regmerge tool needs to be run before the type library can be used in a program, because UNO expects each type description below the /UCR key.
Component Registration Tool
Components can be registered using the regcomp tool. Below, the components necessary to establish an interprocess connection are registered into the myapp_services.rdb.
$ regcomp -register -r myapp_services.rdb \ -c uuresolver.dll \ -c brdgfctr.dll \ -c acceptor.dll \ -c connectr.dll \ -c remotebridge.dll
The \ means command line continuation. The option -r gives the registry file where the information is written to. If it does not exist, it is created, otherwise the new data is added. In case there are older keys, they are overwritten. The registry file (here myapp_services.rdb) must NOT be opened by any other process at the same time. The option -c is followed by a single name of a library that is registered. The -c option can be given multiple times. The shared libraries registered in the example above are needed to use the UNO interprocess bridge.
Registering a Java component is currently more complex. It works only in an installed office environment, the <OfficePath>/program must be the current working directory, the office setup must point to a valid Java installation that can be verified using jvmsetup from <OfficePath>/program, and Java must be enabled. See Tools - Options - General - Security. In [PRODUCTNAME][OO2.0], make sure that a Java is selected by using the Java panel of the options dialog (Tools-Options - [PRODUCTNAME] – Java).
The office must not run. On Unix, the LD_LIBRARY_PATH environment variable must additionally contain the directories listed by the javaldx tool (which is installed with the office).
Copy the regcomp executable into the <officepath>/program directory. The regcomp tool must then be invoked using the following parameters :
$ regcomp -register -r your_registry.rdb \
-br <officepath>/program/services.rdb \
-l com.sun.star.loader.Java2 \
-c file:///d:/test/JavaTestComponent.jar
The option -r (registry) tells regcomp where to write the registration data and the -br (bootstrap registry) option points regcomp to a registry to read common types from. The regcomp tool does not know the library that has the Java loader. The -l option gives the service name of the loader to use for the component that must be com.sun.star.loader.Java2. The option can be omitted for C++ components, because regcomp defaults to the [IDL:com.sun.star.loader.SharedLibrary] loader. The option -c gives the file url to the Java component.
File urls can be given absolute or relative. Absolute file urls must begin with 'file:///'. All other strings are interpreted as relative file urls. The '3rdpartYcomp/filterxy.dll', '../../3rdpartycomp/filterxyz.dll', and 'filterxyz.dll' are a few examples. Relative file urls are interpreted relative to all paths given in the PATH variable on Windows and LD_LIBRARY_PATH variable on Unix.
Java components require an absolute file URL for historical reasons.
Tip graphics marks a hint section in the text
The regcomp tool should be used only during the development and testing phase of components. For deploying final components, the pkgchk toolExtension Manager should be used instead. See [CHAPTER:ExtensionsComponents.Deployment.PackageInstall].
UNO Type Library Tools
There are several tools that currently access the type library directly. They are encountered when new UNOIDL types are introduced.
idlc, Compiles .idl files into .urd-registry-files.
cppumaker, Generates C++ header for a given UNO type list from a type registry used with the UNO C++ binding.
javamaker, Generates Java .class files for a given type list from a type registry.
rdbmaker, Creates a new registry by extracting given types (including dependent types) from another registry, and is used for generating minimal, but complete type libraries for components. It is useful when building minimal applications that use UNO components.
regcompare, Compares a type library to a reference type library and checks for compatibility.
regmerge, Merges multiple registries into a certain sub-key of a new or already existing registry.
Manual Component Installation
Manually Merging a Registry and Adding it to uno.ini or soffice.ini
Registry files used by [PRODUCTNAME] are configured within the uno(.ini|rc) file found in the program directory. After a default [PRODUCTNAME] installation, the files look like this:
uno.ini :
[Bootstrap]
UNO_TYPES=$ORIGIN/types.rdb
UNO_SERVICES=$ORIGIN/services.rdb
The two UNO variables are relevant for UNO components. The UNO_TYPES variable gives a space separated list of type library registries, and the UNO_SERVICES variable gives a space separated list of registries that contain component registration information. These registries are opened read-only. The same registry may appear in UNO_TYPES and UNO_SERVICES variables. The $ORIGIN points to the directory where the ini/rc file is located.
[PRODUCTNAME] uses the types.rdb as a type and the services.rdb as a component registration information repository. When a programmer or software vendor releases a UNO component, the following files must be provided at a minimum:
A file containing the code of the new component, for instance a shared library, a jar file, or maybe a python file in the future.
A registry file containing user defined UNOIDL types, if any.
(optional) A registry file containing registration information of a pre-registered component. The registry provider should register the component with a relative path to be beneficial in other [PRODUCTNAME] installations.
The latter two can be integrated into a single file.
Note graphics marks a special text section
In fact, a vendor may release more files, such as documentation, the .idl files of the user defined types, the source code, and configuration files. While every software vendor is encouraged to do this, there are currently no recommendations how to integrate these files into [PRODUCTNAME]. These type of files are ignored in the following paragraphs. These issues will be addressed in next releases of [PRODUCTNAME].
The recommended method to add a component to [PRODUCTNAME] manually is described in the following steps:
Copy new shared library components into the <OfficePath>/program directory and new Java components into the <OfficePath>/program/classes directory.
Copy the registry containing the type library into the <OfficePath>/program directory, if needed and available.
Copy the registry containing the component registration information into the <OfficePath>/program directory, if required. Otherwise, register the component with the regcomp command line tool coming with the [PRODUCTNAME] SDK into a new registry.
Modify the uno(.ini|rc) file, and add the type registry to the UNO_TYPES variable and the component registry to the UNO_SERVICES variable. The new uno(.ini|rc) might look like this:
[Bootstrap]UNO_TYPES=$ORIGIN/types.rdb $ORIGIN/filterxyz_types.rdbUNO_SERVICES=$ORIGIN/services.rdb $ORIGIN/filterxyz_services.rdb
After these changes are made, every office that is restarted can use the new component. The uno(.ini|rc) changes directly affect the whole office network installation. If adding a component only for a single user, pass the modified UNO_TYPES and UNO_SERVICES variables per command line. An example might be:
$ soffice “-env:UNO_TYPES=$ORIGIN/types.rdb $ORIGIN/filterxyz_types.rdb“
“-env:UNO_SERVICES=$ORIGIN/services.rdb
$ORIGIN/filter_xyz_services.rdb” ).
Bootstrapping a Service Manager
Bootstrapping a service manager means to create an instance of a service manager that is able to instantiate the UNO objects needed by a user. All UNO applications, that want to use the UnoUrlResolver for connections to the office, have to bootstrap a local service manager in order to create a UnoUrlResolver object. If developers create a new language binding, for instance for a scripting engine, they have to find a way to bootstrap a service manager in the target environment.
There are many methods to bootstrap a UNO C++ application, each requiring one or more registry files to be prepared. Once the registries are prepared, there are different options available to bootstrap your application. A flexible approach is to use UNO bootstrap parameters and the defaultBootstrap_InitialComponentContext() function.
#include <cppuhelper/bootstrap.hxx>
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace rtl;
using namespace cppu;int main( ){
// create the initial component context
Reference< XComponentContext > rComponentContext =
defaultBootstrap_InitialComponentContext();
// retrieve the service manager from the context
Reference< XMultiComponentFactory > rServiceManager =
rComponentContext()->getServiceManager();
// instantiate a sample service with the service manager.
Reference< XInterface > rInstance =
rServiceManger->createInstanceWithContext(
OUString::createFromAscii("com.sun.star.bridge.UnoUrlResolver" ),
rComponentContext );
// continue to connect to the office ....
}
No arguments, such as a registry name, are passed to this function. These are given using bootstrap parameters. Bootstrap parameters can be passed through a command line, an .ini file or using environment variables.
For bootstrapping the UNO component context, the following two variables are relevant:
UNO_TYPESGives a space separated list of type library registry files. Each registry must be given as an absolute or relative file url. Note that some special characters within the path require encoding, for example, a space must become a %20. The registries are opened in read-only.
UNO_SERVICESGives a space separated list of registry files with component registration information. The registries are opened in read-only. The same registry may appear in UNO_TYPES and UNO_SERVICES variables.
An absolute file URL must begin with the file:/// prefix (on windows, it must look like file:///c:/mytestregistry.rdb). To make a file URL relative, the file:/// prefix must be omitted. The relative url is interpreted relative to the current working directory.
Within the paths, use special placeholders.
Bootstrap variable
Meaning
$SYSUSERHOME
Path of the user's home directory (see osl_getHomeDir())
$SYSBINDIR
Path to the directory of the current executable.
$ORIGIN
Path to the directory of the ini/rc file.
$SYSUSERCONFIG
Path to the directory where the user's configuration data is stored (see osl_getConfigDir())
The advantage of this method is that the executable can be configured after it has been built. The [PRODUCTNAME] bootstraps the service manager with this mechanism.
Consider the following example:
A tool needs to be written that converts documents between different formats. This is achieved by connecting to [PRODUCTNAME] and doing the necessary conversions. The tool is named docconv. In the code, the defaultBootstrap_InitialComponentContext() function is used as described above to create the component context. Two registries are prepared: docconv_services.rdb with the registered components and types.rdb that contains the types coming with [PRODUCTNAME]. Both files are placed beside the executable. The easiest method to configure the application is to create a docconv(.ini|rc) ascii file in the same folder as your executable, that contains the following two lines:
UNO_TYPES=$ORIGIN/types.rdb
UNO_SERVICES=$ORIGIN/docconv_services.rdb
No matter where the application is started form, it will always use the mentioned registries. Note that this also works on different machines when the volume is mapped to different location mount points as $SYSBINDIR is evaluated at runtime.
The second possibility is to set UNO_TYPES and UNO_SERVICES as environment variables, but this method has drawbacks. All UNO applications started with this shell use the same registries.
The third possibility is to pass the variables as command line parameters, for instance
docconv -env:UNO_TYPES=$ORIGIN/types.rdb -env:
UNO_SERVICES=$ORIGIN/docconv_services.rdb
Note that on UNIX shells, you need to quote the $ with a backslash \.
The command line arguments do not need to be passed to the UNO runtime, because it is generally retrieved from some static variables. How this is done depends on the operating system, but it is hidden from the programmer. The docconv executable should ignore all command line parameters beginning with '-env:'. The easiest way to do this is to ignore argc and argv[] and to use the rtl_getCommandLineArg() functions defined in rtl/process.h header instead which automatically strips the additional parameters.
Combine the methods mentioned above. Command line parameters take precedence over .ini file variables and .ini file parameter take precedence over environment variables. That way, it is possible to overwrite the UNO_SERVICES variable on the command line for one invocation of the program only.
Special Service Manager Configurations
The [IDL:com.sun.star.container.XSet] interface allows the insertion or removal of [IDL:com.sun.star.lang.XSingleServiceFactory] or [IDL:com.sun.star.lang.XSingleComponentFactory] implementations into or from the service manager at runtime without making these changes persistent. When the office applications terminate, all the changes are lost. The inserted object must support the [IDL:com.sun.star.lang.XServiceInfo] interface. This interface returns the same information as the XServiceInfo interface of the component implementation which is created by the component factory.
With this feature, a running office can be connected, a new factory inserted into the service manager and the new service instantiated without registering it beforehand. This method of hard coding the registered services is not acceptable with [PRODUCTNAME], because it must be extended after compilation.
Java applications can use a native persistent service manager in their own process using JNI (see [CHAPTER:ProfUNO.LangBind.Java]), or in a remote process. But note, that all services will be instantiated in this remote process.
Dynamically Modifying the Service Manager
Bootstrapping in pure Java is simple, by calling the static runtime method createInitialComponentContext() from the Bootstrap class. The following small test program shows how to insert service factories into the service manager at runtime. The sample uses the Java component from the section [CHAPTER:Components.Java]. The complete code can be found with the JavaComp sample component.
The example shows that there is the possibility to control through command line parameter, whether the service is inserted in the local Java service manager or the remote office service manager. If it is inserted into the office service manager, access the service through [PRODUCTNAME] Basic. In both cases, the component runs in the local Java process.
If the service is inserted into the office service manager, instantiate the component through [PRODUCTNAME] Basic calling createUnoService("JavaTestComponentB"),as long as the Java process is not terminated. Note, to add the new types to the office process by one of the above explained mechanisms, use uno.ini.
public static void insertIntoServiceManager(
XMultiComponentFactory serviceManager, Object singleFactory)
throws com.sun.star.uno.Exception {
XSet set = (XSet ) UnoRuntime.queryInterface(XSet.class, serviceManager);
set.insert(singleFactory);
}
public static void removeFromServiceManager(
XMultiComponentFactory serviceManager, Object singleFactory)
throws com.sun.star.uno.Exception {
XSet set = (XSet) UnoRuntime.queryInterface( XSet.class, serviceManager);
set.remove(singleFactory);
}
public static void main(String[] args) throws java.lang.Exception {
if (args.length != 1) {
System.out.println("usage: RunComponent local|uno-url");
System.exit(1);
}
XComponentContext xLocalComponentContext =
Bootstrap.createInitialComponentContext(null);
// initial serviceManager
XMultiComponentFactory xLocalServiceManager = xLocalComponentContext.getServiceManager();
XMultiComponentFactory xUsedServiceManager = null;
XComponentContext xUsedComponentContext = null;
if (args[0].equals("local")) {
xUsedServiceManager = xLocalServiceManager;
xUsedComponentContext = xLocalComponentContext;
System.out.println("Using local servicemanager");
// now the local servicemanager is used !
}
else {
// otherwise interpret the string as uno-url
Object xUrlResolver = xLocalServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", xLocalComponentContext);
XUnoUrlResolver urlResolver = (XUnoUrlResolver) UnoRuntime.queryInterface(
XUnoUrlResolver.class, xUrlResolver);
Object initialObject = urlResolver.resolve(args[0]);
xUsedServiceManager = (XmultiComponentFactory) UnoRuntime.queryInterface(
XMultiComponentFactory.class, initialObject);
System.out.println("Using remote servicemanager");
// now the remote servicemanager is used.
}
// retrieve the factory for the component implementation
Object factory = TestServiceProvider.__getServiceFactory(
"componentsamples.TestComponentB", null, null);
// insert the factory into the servicemanager
// from now on, the service can be instantiated !
insertIntoServiceManager( xUsedServiceManager, factory );
// Now instantiate one of the services via the servicemanager !
Object objTest= xUsedServiceManager.createInstanceWithContext(
"JavaTestComponentB",xUsedComponentContext);
// query for the service interface
XSomethingB xs= (XSomethingB) UnoRuntime.queryInterface(
XSomethingB.class, objTest);
// and call the test method.
String s= xs.methodOne("Hello World");
System.out.println(s);
// wait until return is pressed
System.out.println( "Press return to terminate" );
while (System.in.read() != 10);
// remove it again from the servicemanager, otherwise we have
// a dangling reference ( in case we use the remote service manager )
removeFromServiceManager( xUsedServiceManager, factory );
// quit, even when a remote bridge is running
System.exit(0);
}
Creating a ServiceManager from a Given Registry File
To create a service manager from a given registry, use a single registry that contains the type library and component registration information. Hard code the name of the registry in the program and use the createRegistryServiceFactory() function located in the cppuhelper library.
#include <cppuhelper/servicefactory.hxx>
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace rtl;
using namespace cppu;int main( ){
// create the service manager on the registry test.rdb
Reference< XMultiServiceFactory > rServiceManager =
createRegistryServiceFactory( OUString::createFromAscii( “test.rdb” ) );
// instantiate a sample service with the service manager.
Reference< XInterface > rInstance =
rServiceManger->createInstance(
OUString::createFromAscii(“com.sun.star.bridge.UnoUrlResolver” ) );
// continue to connect to the office ....
}
Note graphics marks a special text section
This instantiates the old style service manager without the possibility of offering a component context. In future versions, (642) you will be able to use the new service manager here.
The UNO Executable
[TOPIC:com.sun.star.lang.XMain]In chapter [CHAPTER:ProfUNO.LangBind.Cpp], several methods to bootstrap a UNO application were introduced. In this section, the option UNO executable is discussed. With UNO executable, there is no need to write executables anymore, instead only components are developed. Code within executables is locked up, it can only run by starting the executable, and it can never be used in another context. Components offer the advantage that they can be used from anywhere. They can be executed from Java or from a remote process.
For these cases, the [IDL:com.sun.star.lang.XMain] interface was introduced. It has one method:
/* module com.sun.star.lang.XMain */
interface XMain: com::sun::star::uno::XInterface
{
long run( [in] sequence< string > aArguments );
};
Instead of writing an executable, write a component and implement this interface. The component gets the fully initialized service manager during instantiation. The run() method then should do what a main() function would have done. The UNO executable offers one possible infrastructure for using such components.
Basically, the uno tool can do two different things:
Instantiate a UNO component which supports the [IDL:com.sun.star.lang.XMain] interface and executes the run() method.
// module com::sun::star::langinterface XMain: com::sun::star::uno::XInterface{ long run( [in] sequence< string > aArguments ); };
Export a UNO component to another process by accepting on a resource, such as a tcp/ip socket or named pipe, and instantiating it on demand.
In both cases, the uno executable creates a UNO component context which is handed to the instantiated component. The registries that should be used are given by command line arguments. The goal of this tool is to minimize the need to write executables and focus on writing components. The advantage for component implementations is that they do not care how the component context is bootstrapped. In the future there may be more ways to bootstrap the component context. While executables will have to be adapted to use the new features, a component supporting XMain can be reused.
Standalone Use Case
Simply typing uno gives the following usage screen :
uno (-c ComponentImplementationName -l LocationUrl | -s ServiceName) [-ro ReadOnlyRegistry1] [-ro ReadOnlyRegistry2] ... [-rw ReadWriteRegistry] [-u uno:(socket[,host=HostName][,port=nnn]|pipe[,name=PipeName]);urp;Name [--singleaccept] [--singleinstance]] [-- Argument1 Argument2 ...]
Choosing the implementation to be instantiated
Using the option -s servicename gives the name of the service which shall be instantiated. The uno executable then tries to instantiate a service by this name, using the registries as listed below.
Alternatively, the -l and -c options can be used. The -l gives an url to the location of the shared library or .jar file, and -c the name of the desired service implementation inside the component. Remember that a component may contain more than one implementation.
Choosing the registries for the component context (optional)
With the option -ro, give a file url to a registry file containing component's registration information and/or type libraries. The -ro option can be given multiple times. The -rw option can only be given once and must be the name of a registry with read/write access. It will be used when the instantiated component tries to register components at runtime. This option is rarely needed.
Note that the uno tool ignores bootstrap variables, such as UNO_TYPES and UNO_SERVICES.
The UNO URL (optional)
Giving a UNO URL causes the uno tool to start in server mode, then it accepts on the connection part of the UNO URL. In case another process connects to the resource (tcp/ip socket or named pipe), it establishes a UNO interprocess bridge on top of the connection (see also [CHAPTER:ProfUNO.UNOConcepts.InterprocessConn]). Note that urp should always be used as protocol. An instance of the component is instantiated when the client requests a named object using the name, which was given in the last part of the UNO URL.
Option --singleaccept
Only meaningful when a UNO URL is given. It tells the uno executable to accept only one connection, thus blocking any further connection attempts.
Option --singleinstance
Only meaningful when a UNO URL is given. It tells the uno executable to always return the same (first) instance of the component, thus multiple processes communicate to the same instance of the implementation. If the option is not given, every getInstance() call at the [IDL:com.sun.star.bridge.XBridge] interface instantiates a new object.
Option -- (double dash)
Everything following –- is interpreted as an option for the component itself. The arguments are passed to the component through the initialize() call of [IDL:com.sun.star.lang.XInitialization] interface.
Note graphics marks a special text section
The uno executable currently does not support the bootstrap variable concept as introduced by [CHAPTER:ProfUNO.LangBind.Cpp]. The uno registries must be given explicitly given by command line.
The following example shows how to implement a Java component suitable for the uno executable.
import com.sun.star.uno.XComponentContext;
import com.sun.star.comp.loader.FactoryHelper;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.registry.XRegistryKey;
public class UnoExeMain implements com.sun.star.lang.XMain
{
final static String __serviceName = "MyMain";
XComponentContext _ctx;
public UnoExeMain( XComponentContext ctx )
{
// in case we would need the component context !
_ctx = ctx;
}
public int run( /*IN*/String[] aArguments )
{
System.out.println( "Hello world !" );
return 0;
}
public static XSingleServiceFactory __getServiceFactory(
String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey)
{
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals(UnoExeMain.class.getName()))
{
xSingleServiceFactory =
FactoryHelper.getServiceFactory(
UnoExeMain.class, UnoExeMain.__serviceName, multiFactory, regKey);
}
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey)
{
boolean b = FactoryHelper.writeRegistryServiceInfo(
UnoExeMain.class.getName(),
UnoExeMain.__serviceName, regKey);
return b;
}
}
The class itself inherits from [IDL:com.sun.star.lang.XMain]. It implements a constructor with the [IDL:com.sun.star.uno.XComponentContext] interface and stores the component context for future use. Within its run() method, it prints 'Hello World'. The last two mandatory functions are responsible for instantiating the component and writing component information into a registry. Refer to [CHAPTER:Components.Java] for further information.
The code needs to be compiled and put into a .jar file with an appropriate manifest file:
RegistrationClassName: UnoExeMain
These commands create the jar:
javac UnoExeMainjar -cvfm UnoExeMain.jar Manifest UnoExeMain.class
To be able to use it, register it with the following command line into a separate registry file (here test.rdb). The <OfficePath>/program directory needs to be the current directory, and the regcomp and uno tools must have been copied into this directory.
regcomp -register \-br <officepath>/program/services.rdb \-r test.rdb \-c file:///c:/devmanual/Develop/samples/unoexe/UnoExeMain.jar \-l com.sun.star.loader.Java2
The \ means command line continuation.
The component can now be run:
uno -s MyMain -ro types.rdb -ro services.rdb -ro test.rdb
This command should give the output "hello world !"
Server Use Case
This use case enables the export of any arbitrary UNO component as a remote server. As an example, the [IDL:com.sun.star.io.Pipe] service is used which is already implemented by a component coming with the office. It exports an [IDL:com.sun.star.io.XOutputStream] and a [IDL:com.sun.star.io.XInputStream] interface. The data is written through the output stream into the pipe and the same data from the input stream is read again. To export this component as a remote server, switch to the <OfficePath>/program directory and issue the following command line.
i:\o641l\program>uno -s com.sun.star.io.Pipe -ro types.rdb -ro services.rdb -u uno:socket,host=0,port=2002;urp;test
> accepting socket,host=0,port=2083...
Now a client program can connect to the server. A client may look like the following:
import com.sun.star.lang.XServiceInfo;
import com.sun.star.uno.XComponentContext;
import com.sun.star.bridge.XUnoUrlResolver;
import com.sun.star.io.XOutputStream;
import com.sun.star.io.XInputStream;
import com.sun.star.uno.UnoRuntime;
// Note: This example does not do anything meaningful, it shall just show,
// how to import an arbitrary UNO object from a remote process.
class UnoExeClient {
public static void main(String [] args) throws java.lang.Exception {
if (args.length != 1) {
System.out.println("Usage : java UnoExeClient uno-url");
System.out.println(" The imported object must support the com.sun.star.io.Pipe service");
return;
}
XComponentContext ctx =
com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
// get the UnoUrlResolver service
Object o = ctx.getServiceManager().createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver" , ctx);
XUnoUrlResolver resolver = (XUnoUrlResolver) UnoRuntime.queryInterface(
XUnoUrlResolver.class, o);
// connect to the remote server and retrieve the appropriate object
o = resolver.resolve(args[0]);
// Check if we got what we expected
// Note: This is not really necessary, you can also use the try and error approach
XServiceInfo serviceInfo = (XServiceInfo) UnoRuntime.queryInterface(XServiceInfo.class,o);
if (serviceInfo == null) {
throw new com.sun.star.uno.RuntimeException(
"error: The object imported with " + args[0] + " did not support XServiceInfo", null);
}
if (!serviceInfo.supportsService("com.sun.star.io.Pipe")) {
throw new com.sun.star.uno.RuntimeException(
"error: The object imported with "+args[0]+" does not support the pipe service", null);
}
XOutputStream output = (XOutputStream) UnoRuntime.queryInterface(XOutputStream.class,o);
XInputStream input = (XInputStream) UnoRuntime.queryInterface(XInputStream.class,o);
// construct an array.
byte[] array = new byte[]{1,2,3,4,5};
// send it to the remote object
output.writeBytes(array);
output.closeOutput();
// now read it again in two blocks
byte [][] read = new byte[1][0];
System.out.println("Available bytes : " + input.available());
input.readBytes( read,2 );
System.out.println("read " + read[0].length + ":" + read[0][0] + "," + read[0][1]);
System.out.println("Available bytes : " + input.available());
input.readBytes(read,3);
System.out.println("read " + read[0].length + ":" + read[0][0] +
"," + read[0][1] + "," + read[0][2]);
System.out.println("Terminating client");
System.exit(0);
}
}
After bootstrapping the component context, the UnoUrlResolver service is instantiated to access remote objects. After resolving the remote object, check whether it really supports the Pipe service. For instance, try to connect this client to a running [PRODUCTNAME] — this check will fail. A byte array with five elements is written to the remote server and read again with two readBytes() calls. Starting the client with the following command line connects to the server started above. You should get the following output:
I:\tmp>java UnoExeClient uno:socket,host=localhost,port=2083;urp;test
Available bytes : 5
read 2:1,2
Available bytes : 3
read 3:3,4,5
Terminating client
Using the uno Executable
The main benefit of using the uno tool as a replacement for writing executables is that the service manager initialization is separated from the task-solving code and the component can be reused. For example, to have multiple XMain implementations run in parallel in one process. There is more involved when writing a component compared to writing an executable. With the bootstrap variable mechanism there is a lot of freedom in bootstrapping the service manager (see chapter [CHAPTER:ProfUNO.LangBind.Cpp]).
The uno tool is a good starting point when exporting a certain component as a remote server. However, when using the UNO technology later, the tool does have some disadvantages, such as multiple objects can not be exported or the component can only be initialized with command line arguments. If the uno tool becomes insufficient, the listening part in an executable will have to be re-implemented.
Note graphics marks a special text section
To instantiate Java components in build version 641, you need a complete setup so that the uno executable can find the java.ini file.
Accessing Dialogs
This chapter describes how UNO Components can interact with dialogs that have been created with the Dialog Editor integrated in the [PRODUCTNAME] Basic IDE. Before [PRODUCTNAME] [OO2.0.4] dialogs designed with this Dialog Editor could only be reasonably used in the context of [PRODUCTNAME] Basic respectively in the scope of the Scripting Framework (see [CHAPTER:Scripting Framework]). The reason for this restriction was the fact that only scripts managed by the Scripting Franework could be assigned as action to control events. It was already possible to instantiate dialogs using the [IDL:com.sun.star.awt.XDialogProvider] API, but there was no other way to get call backs from the events as to directly add listeners using the corresponding AWT control interfaces. This is a very uinconvenient way to use dialogs created with the Dialog Editor.
From [PRODUCTNAME] [OO2.0.4] also component methods can be bound to control events. The following chapters describe both how the binding to component methods is done in Dialog Editor and how the component has to be designed to use this mechanism.
Assigning Component Methods to Control Events
How a dialog is generally designed in the Basic IDE Dialog editor is described in [CHAPTER:First Steps with [PRODUCTNAME] Basic.A Simple Dialog]. The assignment of macros to control events is also described there in the sub chapter Adding Event Handlers, but the Assign Action dialog showed in the following illustration can also be used to bind component methods to control events.
Overview graphic of an UNO component
Illustration 4.11: Assign Action dialog
Instead of pressing the Macro... button the Component... button has to be used. It opens a Assign Component dialog.
Overview graphic of an UNO component
Illustration 4.12: Assign Component dialog
Besides the standard buttons this dialog only contains an edit field to enter the name of the Component's method the event should be bound to. Unlike in the case of assigning macros it's not possible to browse to a component's methods because at design time no component instance exists. So the name has to be entered by hand.
The next illustration shows how the new assignment is shown in the Assign Action dialog.
Overview graphic of an UNO component
Illustration 4.13: Assign Action dialog with assigned component method
Tip graphics marks a hint section in the text
When designing dialogs that should be used for components, it could make sense to create a new library first (see [CHAPTER:Managing Basic and Dialog Libraries.Basic Macro Organizer Dialog]) and create the dialog there. Reason: The Standard library cannot be exported, but exporting the library containing the dialog as packageextension can be very useful in order to deploy it together with the corresponding component package extension which contains the component.
The implementation of methods that should be assigned to events is explained in the following chapter.
Using Dialogs in Components
In general components using dialogs are like any other component. But they need some additional code to instantiate and display the dialog(s) to be used and to accept the events created by the dialog controls.
Instantiate and display a dialog
To do this an extended version of the [IDL:com.sun.star.awt.DialogProvider] service - described in service [CHAPTER:Scripting Framework] - has to be used. The extended service version [IDL:com.sun. star.awt.DialogProvider2] supports [IDL:com.sun. star.awt.XDialogProvider2] providing an additional method [IDL:com::sun::star::awt::XDialog] createDialogWithHandler(...) that allows to pass an interface when creating the dialog. This interface will be used as event handler and called if events are bound to the component.
The following code is take from the DialogComponent SDK example that can be found in SDK/examples/DevelopersGuide/Components and shows how a dialog is created and displayed using the DialogProvider2 service:
// XTestDialogHandlerpublic String createDialog( String DialogURL, XModel xModel, XFrame xFrame ) {
m_xFrame = xFrame; try { XMultiComponentFactory xMCF = m_xCmpCtx.getServiceManager();
Object obj;
// If valid we must pass the XModel when creating a DialogProvider object
if( xModel != null ) {
Object[] args = new Object[1];
args[0] = xModel;
obj = xMCF.createInstanceWithArgumentsAndContext(
"com.sun.star.awt.DialogProvider2", args, m_xCmpCtx );
}
else {
obj = xMCF.createInstanceWithContext(
"com.sun.star.awt.DialogProvider2", m_xCmpCtx );
}
XDialogProvider2 xDialogProvider = (XDialogProvider2)
UnoRuntime.queryInterface( XDialogProvider2.class, obj );
XDialog xDialog = xDialogProvider.createDialogWithHandler( DialogURL, this );
if( xDialog != null )
xDialog.execute();
} catch (Exception e) { e.printStackTrace(); }
return "Created dialog \"" + DialogURL + "\"";}
The variable m_xCmpCtx is the [IDL:com.sun.star.uno.XComponentContext] interface passed to the component while initialisation. If the dialog that should be created is placed inside a document a [IDL:com.sun.star.frame.XModel] interface xModel representing this document has to be passed. It's used as argument to initialise the DialogProvider service enabling the access to the document's Dialog Libraries. If xModel is null the dialog has to be placed in the application library container. This also has to be reflected in the DialogURL passed to the method.
Example code for a Basic/Dialog library Library1 placed in a document:
Sub TestDialogComponent()
oComp = CreateUnoService( "com.sun.star.test.TestDialogHandler" )
oComp.createDialog( "vnd.sun.star.script:Library1.Dialog1?location=document", _
ThisComponent, StarDesktop.getActiveFrame() )
End Sub
Example code for a Basic/Dialog library Library1 placed in “My Macros”:
Sub TestDialogComponent()
oComp = CreateUnoService( "com.sun.star.test.TestDialogHandler" )
oComp.createDialog( "vnd.sun.star.script:Library1.Dialog1?location=application", _
null, StarDesktop.getActiveFrame() )
End Sub
The dialog contained in the DialogComponent.odt sample document in SDK/examples/DevelopersGuide/Components/DialogComponent looks like this.
Overview graphic of an UNO component
Illustration 4.14: Sample dialog
The button labels show which component method is called in each case. The next chapter explains how these methods can be implemented inside the component. Method “doit3” isn't implemented at all. It's called in the sample dialog to show the resulting error message:
Overview graphic of an UNO component
Illustration 4.15: Error message for not existing method
Accept events created by dialog controls
The event handling functionality can be implemented in two different ways. The test component described here uses both ways.
The first way is to implement a the generic handler interface [IDL:com.sun.star.awt.XDialogEventHandler] containing two methods:
interface XDialogEventHandler: com::sun::star::uno::XInterface{ bool callHandlerMethod ( [in] com::sun::star::awt::XDialog xDialog, [in] any Event, [in] string MethodName )
sequence<string> getSupportedMethodNames();}
If an event occurs that is bound to a component method and the component implements this interface the method callHandlerMethod will be called first with the method name used in the event binding passed as MethodName parameter. In this example this would be:
xHandler.callHandlerMethod( xDialog, aEvent, “handleEvent” );
xDialog points to the same dialog instance that has been returned by the createDialogWithHandler() method. Event represents the event object originally passed to the awt listener method. E.g. in case of the “When initiating” event used in this example the corresponding awt listener interface is [IDL:com.sun.star.awt.XActionListener] and an [IDL:com.sun.star.awt.ActionEvent] is passed to its actionPerformed method when the event occurs. This ActionEvent object will also be passed to callHandlerMethod. The Event object has to be passed as any, because other events use different listener interfaces with other event object types. callHandlerMethod returns a bool value. Returning true means that the event has been handled.
The method getSupportedMethodNames() should return the names of all methods handled by callHandlerMethod(). It's intended for later use, especially to expand the user interface to allow browsing a component's methods.
If the event has not been handled, because callHandlerMethod returns false or [IDL:com.sun.star.awt.XDialogEventHandler] isn't supported at all by the component, the DialogProvider uses the [IDL:com.sun.star.beans.Introspection] service to detect if one of the following methods is provided by one of the interfaces supported by the component:
void [MethodName] ( [in] com::sun::star::awt::XDialog xDialog, [in] any aEvent );
or
void [MethodName]( void );
The second method is only used if the first one is not available. In this example the component would have to support an interface containing a method handleEvent with one of these signatures. It also has to support [IDL:com.sun.star.lang.XTypeProvider] because otherwise the introspection mechanism does not work.
As already mentioned the sample component supports both ways to implement handler methods. [IDL:com.sun.star.awt.XDialogEventHandler] is implemented like this:
private String aHandlerMethod1 = "doit1";
private String aHandlerMethod2 = "doit2";
//XDialogEventHandler
public boolean callHandlerMethod( /*IN*/XDialog xDialog, /*IN*/Object EventObject,
/*IN*/String MethodName ) {
if ( MethodName.equals( aHandlerMethod1 ) ) {
showMessageBox( "DialogComponent", "callHandlerMethod() handled \"" + aHandlerMethod1 + "\"" );
return true;
}
else if ( MethodName.equals( aHandlerMethod2 ) ) {
showMessageBox( "DialogComponent", "callHandlerMethod() handled \"" + aHandlerMethod2 + "\"" );
return true;
}
return false;
}
public String[] getSupportedMethodNames() {
String[] retValue= new String[1];
retValue[0]= aHandlerMethod1;
retValue[1]= aHandlerMethod2;
return retValue;
}
The implementation is very simple to show only the logic. For the two handled method names the method displays a MessageBox and return true. Otherwise false is returned.
The other methods bound to the sample dialog control events are implemented using the other way. The interface [IDL:com.sun.star.test.XTestDialogHandler] looks like this:
module com { module sun { module star { module test { interface XTestDialogHandler { string createDialog( [in] string DialogURL, [in] ::com::sun::star::frame::XModel xModel, [in] ::com::sun::star::frame::XFrame xFrame ); void copyText( [in] ::com::sun::star::awt::XDialog xDialog, [in] any aEventObject ); void handleEvent(); void handleEventWithArguments( [in] ::com::sun::star::awt::XDialog xDialog,
[in] any aEventObject ); }; }; }; }; };
Besides the already described createDialog method three methods are defined to handle events. handleEvent and handleEventWithArguments are implemented very simple and only display a message box:
public void handleEvent() { showMessageBox( "DialogComponent", "handleEvent() called" );
} public void handleEventWithArguments( XDialog xDialog, Object aEventObject ) { showMessageBox( "DialogComponent", "handleEventWithArguments() called\n\n" +
"Event Object = " + aEventObject );
}
The method copy text shows, how the passed XDialog interface can be used to access controls on the dialog itself. The details are not described here. For more information see [CHAPTER:Creating Dialogs at Runtime].
public void copyText( XDialog xDialog, Object aEventObject ) {
XControlContainer xControlContainer = (XControlContainer)UnoRuntime.queryInterface(
XControlContainer.class, xDialog );
String aTextPropertyStr = "Text";
String aText = "";
XControl xTextField1Control = xControlContainer.getControl( "TextField1" );
XControlModel xControlModel1 = xTextField1Control.getModel();
XPropertySet xPropertySet1 = (XPropertySet)UnoRuntime.queryInterface(
XPropertySet.class, xControlModel1 );
try {
aText = (String)xPropertySet1.getPropertyValue( aTextPropertyStr );
}
catch (Exception e) {
e.printStackTrace();
}
XControl xTextField2Control = xControlContainer.getControl( "TextField2" );
XControlModel xControlModel2 = xTextField2Control.getModel();
XPropertySet xPropertySet2 = (XPropertySet)UnoRuntime.queryInterface(
XPropertySet.class, xControlModel2 );
try {
xPropertySet2.setPropertyValue( aTextPropertyStr, aText );
}
catch (Exception e) {
e.printStackTrace();
}
showMessageBox( "DialogComponent", "copyText() called" );
}
Simple components using dialogs can be realised very easily by supporting XDialogEventHandler as then no own interfaces have to be created. For complex components it could make more sense to define handler interfaces to avoid a huge switch/case blocks in XDialogEventHandler:: callHandlerMethod.