Sidebar for Developers

From Apache OpenOffice Wiki
Jump to: navigation, search

This page will teach you how to write panels for the sidebar, either as extensions or in the core (core means C++ code in the OpenOffice source repository).

For a general description of concepts and names please see the main sidebar page.

Status

The sidebar development is finished and part of the 4.0 release. Here is how it looks:
Sidebar-2013-04-15.png

Overview

A general description of the sidebar is here.

There are two ways to add new sidebar panels and decks to OpenOffice:

  • In an extension in any programming language that is supported by UNO
  • By extending the OpenOffice C++ code.

The actual panel implementation is very similar in both cases. The main differences are in how to deploy the panel and, if you don't choose C++ to implement your extension, the programming language.

One crucial aspect of adding new panels is where and when to show the new panel:

Where
Panels can be shown in any of the existing decks or in a new deck. Inside a deck the new panel can be displayed above, below, or in between existing panels.
When
The set of available decks and the set of visible panels depends on the current configuration (more on what a configuration is can be found below). This can be best observed with the 'Properties' deck. It shows different panels depending on which object is selected in the edit view.

Panels and decks are described by XML snippets in XCU files. Panels need an additional implementing UNO service and optionally another UNO service that displays a dialog with details that don't fit in the panel. Decks are XML only.

There are only three relevant UNO interfaces regarding the implementation of panels:

css::ui::XUIElementFactory
implemented by the factory that creates new panels.
css::ui::XToolPanel
implemented by panels and returned by the panel factory.
The css::ui::XSidebarPanel
optionally (by recommended) implemented by panels

Some bugzilla meta issues regarding the sidebar:

  • Original implementation of the sidebar for OpenOffice 4.0: 121420
  • Open bugs for post 4.0: 122364
  • Enhancement ideas and requests: 122257

Context

The exact definition what is a context and how it differs from other contexts depends on the applications, and possibly extensions, that define contexts. Most often a context is defined by the selection in the edit view. When the type of selected object changes then the context changes also. That means that if you have text selected in Writer and select a different text portion then the context does not change because in both cases the selection contains text. But if you click on an embedded table or image, the context does change.

XML description of contexts

Sidebar.xcs defines a context as four comma separated values. The fourth is optional and often omitted. The following is an almost verbatim copy of the XCS documentation (note that values are case sensitive):

Application Name
Valid values are
   com.sun.star.text.TextDocument
   com.sun.star.text.GlobalDocument
   com.sun.star.text.WebDocument
   com.sun.star.xforms.XMLFormDocument
   com.sun.star.sdb.FormDesign
   com.sun.star.sdb.TextReportDesign
   com.sun.star.sheet.SpreadsheetDocument
   com.sun.star.presentation.PresentationDocument
   com.sun.star.drawing.DrawingDocument
Recognized shortcuts:
   Writer
   Calc
   Impress
   Draw
Shortcuts for multiple applications:
   DrawImpress
   WriterVariants
These shortcuts exist for even more convenience and handle the frequent case of Draw and Impress as well as different variants of the Writer where they have otherwise identical context descriptions.
Special values:
   any
   none
Context Name
Know context names are
   3DObject
   Annotation
   Auditing
   Cell
   Chart
   Draw
   DrawPage
   DrawText,
   EditCell
   Form
   Frame
   Graphic
   HandoutPage
   MasterPage
   Media
   Multiobj
   OLE
   OutlineText,
   Pivot
   SlidesorterPage
   Table
   Text
   TextObject
   default
Special values:
   any
Panel state
One of
   visible
   hidden
Defines whether a panel is initially visible and expanded or is initially collapsed, ie only its title bar is visible.
UNO command that overrides the DefaultMenuCommand
(Optional, only used for panels) Use the special value none to override the DefaultMenuCommand to the empty string and thereby disable the showing of a) the menu button in the panel titlebar and b) the showing of a dialog.

Contexts are listed in the Sidebar.xcu in the ContextList property. It can contain more than one context entry. These are separated by semicolons or whatever the seperator attribute defines.

Context change notification

Context changes are broadcast office wide via the com::sun::star::ui::ContextChangeEventMultiplexer singleton service and com::sun::star::ui::XContextChangeEventMultiplexer interface. In order to restrict the communication to one application window, there exists the concept of the event focus. When you register as context change event listener or broadcast a change notification then you pass a event focus object. Only when listener and broadcaster use the same event focus object then the notification will pass from broadcaster to listener. Typically the event focus object is the controller of the application.

To make life easier for developers there exists the svx::sidebar::ContextChangeEventMultiplexer frontend that

  • figures out the application name and event focus from either a given frame::XController or SfxViewShell object.
  • accepts only enum values from sfx2::sidebar::EnumContext::Context to prevent typos in context names to not correctly deliver context change events.

On the receiving side of context change events there is the sfx2::sidebar::SidebarPanelBase base class for panel implementations. Let your panel implement the sfx2::sidebar::IContextChangeReceiver interface, use SidebarPanelBase (which probably needs a better name) as glue code between the sidebar framework and your panel and context change events will be delivered to you via the IContextChangeReceiver::HandleContextChange() method. Not all panels need to be informed about context changes. For many it is enough that they are activated for some contexts and deactivated for others.

By default the property deck is displayed. The user can switch between decks by clicking on buttons in the tab bar. This will result in the forced notification of a context change.

Example

The context list for the "Area" property panel looks like this (or at one time looked like this):

   <prop oor:name="ContextList">
       <value oor:separator=";">
           Calc,        Draw,       visible ;
           DrawImpress, 3DObject,   visible ;
           DrawImpress, Draw,       visible ;
           DrawImpress, TextObject, hidden  ;
           Writer,      Draw,       visible ;
       </value>
   </prop>

The panel will be displayed for the Draw context for almost all applications. It is not displayed for some Writer variants. For Draw and Impress it will also be shown for contexts 3DObject and TextObject. The panel will be initially expanded except for the TextObject context for Draw and Impress. For this context it will be initially collapsed.

List of known contexts

The following table shows the association between panels and dialogs for all supported contexts. It also lists whether panels are initially displayed expanded or collapsed and the uno command to use to open a more detailed dialog. With further enhancements and extensions of the sidebar this list may not reflect the current state. See Sidebar.xcu for up-to-date information.

Applications are abreviated:

  • SC - Calc
  • SD - Draw and Impress
  • SW - Writer
Panel Application Context Initially visible Detail dialog command
Alignment SC Auditing yes .uno:Hyphenate
Alignment SC Cell yes .uno:Hyphenate
Alignment SC EditCell yes .uno:Hyphenate
Alignment SC Pivot yes .uno:Hyphenate
Area SC Draw yes .uno:FormatArea
Area SD 3DObject yes .uno:FormatArea
Area SD Draw yes .uno:FormatArea
Area SD TextObject no .uno:FormatArea
Area SW Draw yes .uno:FormatArea
Cell Appearance SC Auditing yes .uno:FormatCellDialog
Cell Appearance SC Cell yes .uno:FormatCellDialog
Cell Appearance SC Pivot yes .uno:FormatCellDialog
Graphic SC Graphic yes -none-
Graphic SD Graphic yes -none-
Graphic SW Graphic yes -none-
Line SC Draw yes .uno:FormatLine
Line SC Graphic yes .uno:FormatLine
Line SD 3DObject yes .uno:FormatLine
Line SD Draw yes .uno:FormatLine
Line SD Graphic yes .uno:FormatLine
Line SD TextObject no .uno:FormatLine
Line SW Draw yes .uno:FormatLine
Number Format SC Auditing no .uno:FormatCellDialog
Number Format SC Cell no .uno:FormatCellDialog
Number Format SC Pivot no .uno:FormatCellDialog
Wrap SW Graphic yes .uno:ObjectWrapDialog
Wrap SW OLE yes .uno:ObjectWrapDialog
Wrap SW Frame yes .uno:ObjectWrapDialog
Layouts SD DrawPage yes -none-
Layouts SD HandoutPage yes -none-
Layouts SD NotesPage yes -none-
Layouts SD SlidesorterPage yes -none-
Page SW Table no .uno:PageDialog
Page SW Text no .uno:PageDialog
Paragraph SC DrawText yes .uno:ParagraphDialog
Paragraph SD 3DObject yes .uno:ParagraphDialog
Paragraph SD Draw no .uno:ParagraphDialog
Paragraph SD DrawText yes .uno:ParagraphDialog
Paragraph SD Graphic no .uno:ParagraphDialog
Paragraph SD Table yes .uno:ParagraphDialog
Paragraph SD TextObject yes .uno:ParagraphDialog
Paragraph SW Annotation yes .uno:ParagraphDialog
Paragraph SW DrawText yes .uno:ParagraphDialog
Paragraph SW Table yes .uno:ParagraphDialog
Paragraph SW Text yes .uno:ParagraphDialog
Position and Size SC Draw no .uno:TransformDialog
Position and Size SC Form yes .uno:TransformDialog
Position and Size SC Graphic no .uno:TransformDialog
Position and Size SC Media yes .uno:TransformDialog
Position and Size SC MultiObject yes .uno:TransformDialog
Position and Size SC OLE yes .uno:TransformDialog
Position and Size SD 3DObject yes .uno:TransformDialog
Position and Size SD Draw no .uno:TransformDialog
Position and Size SD Form yes .uno:TransformDialog
Position and Size SD Graphic no .uno:TransformDialog
Position and Size SD Media yes .uno:TransformDialog
Position and Size SD MultiObject yes .uno:TransformDialog
Position and Size SD OLE yes .uno:TransformDialog
Position and Size SD TextObject no .uno:TransformDialog
Position and Size SW Draw no .uno:TransformDialog
Position and Size SW Form yes .uno:TransformDialog
Position and Size SW Graphic yes .uno:GraphicDialog
Position and Size SW Media yes .uno:TransformDialog
Position and Size SW OLE yes .uno:FrameDialog
Table Design SD Table yes -none-
Text SC Auditing yes .uno:CellTextDlg
Text SC Cell yes .uno:CellTextDlg
Text SC DrawText yes .uno:FontDialog
Text SC EditCell yes .uno:FontDialog
Text SC Pivot yes .uno:CellTextDlg
Text SD 3DObject yes .uno:FontDialog
Text SD Draw no .uno:FontDialog
Text SD DrawText yes .uno:FontDialog
Text SD Graphic no .uno:FontDialog
Text SD OutlineTExt yes .uno:FontDialog
Text SD Table yes .uno:FontDialog
Text SD Textobj yes .uno:FontDialog
Text SW Annotation yes .uno:FontDialog
Text SW DrawText yes .uno:FontDialog
Text SW Table yes .uno:FontDialog
Text SW Text yes .uno:FontDialog

There are some simple documents for Writer, Calc and Impress illustrate how switch to any of the contexts:

Implementing a Sidebar Panel

As outlined above you need three things for a new sidebar panel:

  • Panel factory
  • XCU description of the panel
  • Implementation of the panel

And optionally:

  • XCU description of a new deck
  • Implementation of a detail dialog


Panel Factory

If your are adding a panel to the C++ core of OpenOffice then you can from one of the existing factories:

If you are adding a panel via extension then you have to provide a UNO service that implements the [http://www.openoffice.org/api/docs/common/ref/com/sun/star/ui/XUIElementFactory.html com/sun/star/ui/XUIElementFactory] interface.

If you write a new factory then add it to Factories.xcu

The XUIElementFactory::createUIElement(URL,arguments) method is called for URLs defined in Sidebar.xcu. They usually look like this

   private:resource/toolpanel/SvxPanelFactory/TextPropertyPanel

where SvxPanelFactory is the name of the factory and TextPropertyPanel is the name of the panel. The second argument contains a sequence of properties:

Frame
The css::frame::XFrame object of the calling application (window).
ParentWindow
The panel is expected to create a child window for this css::awt::XWindow object.
Sidebar
The css::ui::XSidebar object with its single method requestLayout should be called when eg. a context change causes the panel to change its size.
Theme
A property set with some colors, sizes, font descriptions.
ApplicationName
Part of the initial context;
ContextName
Part of the initial context;
Canvas
Still experimental. A css::rendering::XSpriteCanvas that can be used to paint the panel content. Only provided if the panel description has the "WantsCanvas" property set.
SfxBindings
This is a hack to support legacy implementations of panels. Deprecated.


Panel XCU Description

Add a new entry to the Sidebar/Content/PanelList in Sidebar.xcu. The available properties are:

Title
Name of the panel, displayed in its title bar. Localizable.
TitleBarIsOptional
When true and the panel is the only panel in the deck then the panel title bar is not displayed. Use when the title is somewhat redundant with the deck title. Defaults to false.
Id
Internal id used to identify the panel. Has to be unique.
DeckId
Internal id of the deck in which the panel will be displayed.
TitleBarIconURL
URL to an icon that is displayed in the title bar of the panel. When missing then no icon is displayed.
HighContrastTitleBarIconURL
High contrast version of the title bar icon. When missing then the regular icon is used.
HelpURL
URL of help content.
DefaultMenuCommand
UNO service name of detail dialog to show when the user clicks on the detail dialog button in the title bar of the panel. Can be overridden by values in the ContextList. When not command is given then the button in the title bar is not displayed.
ContextList
List of context descriptions. The entries are explained above. Individual entries are separated by semicolons (or whatever is specified by the separator attributes). Formatting entries into rows makes reading their content easier but leading and trailing spaces are ignored.
ImplementationURL
URL that is recognized by the panel factory.
OrderIndex
A numerical value that defines the top-down order of panels in a deck. Higher values lead to positions further down. Defaults to 10000 which should result in a position below the standard panels.
ShowForReadOnlyDocument
When true then the panel will be displayed for read-only documents. Defaults to false.
WantsCanvas
When true then an XCanvas object will be passed to the panel factory when the panel is created. Experimental. Defaults to false.


Panel Implementation

The panel factory returns objects that implement the css::ui::XUIElement interface. Its getRealInterface method returns a reference to the actual panel object. You can let the panel implement XUIElement but it may be better to use a wrapper to do that.

The panel is expected to implement two interfaces. Implementation of both is optional but recommended:

css::ui::XToolPanel
Its getWindow() method is used to acquire a reference to the panel window. This window is used to layout the panel.
css::ui::XSidebarPanel
Its getHeightForWidth is used by the deck layouter to determine the size of the panel. The returned LayoutSize struct has three values:
Minimum
Minimum height, zero or positive. The value itself is included in the valid height range.;Maximum: Maximum height or -1 to specify that there is no fixed upper bound. Valid values are -1 or a value larger than or equal to Minimum.
Preferred
The preferred height. Has to be inside the valid range.

When panel wants its height to be changed, eg when a context change leads to some UI controls to be shown or hidden, then it has to call XSidebar::requestLayout. The XSidebar object is usually passed by the panel factory to the constructor of the panel.

Apart from the above, the panel is free to display its content as it likes.


Deck XCU Description

If you don't want to use any of the existing decks then you can create your own. Decks have no implementation and therefore there are no factories to implement.

It is enough to add an entry to the Sidebar/Content/DeckList in Sidebar.xcu. The available properties are similar to those of panels:

Title
Name of the deck, displayed in its title bar. Localizable.
Id
The internal id used by panels to specify the deck they belong to. Has to be unique.;IconURL:URL of an icon that is displayed in the icon bar. When missing then an empty button is displayed.
HighContrastIconURL
High contrast version of the icon. When missing then the regular icon is used.
TitleBarIconURL
URL of an icon that is displayed in the title bar of the deck. When missing then no icon is displayed.
HighContrastTitleBarIconURL
High contrast version of the title bar icon. When missing then the regular icon is used.
HelpURL
URL of help content.
ContextList
Same as the ContextList of panels.
OrderIndex
A numerical value that defines the top-down order of buttons in the icon bar. Higher values lead to positions further down. Defaults to 10000 which should result in a position below the standard decks.


Detail Dialog

Detail dialogs show information that does not fit into the panel or is not used frequently enough to justify its inclusion into the panel. The dialogs where originally conceived as menus, hence the property name DefaultMenuCommand in the panel description.

You don't have to provide a dialog but if you do you can display the same dialog for all contexts or different dialog depending on the current context. Use the DefaultMenuCommand property in the panel description to define the default dialog. Override it by providing a fourth element in one, some, or each of the context entries in the panel's ContextList property.

The commands that are specified either by DefaultMenuCommand or the fourth argument of context list entries are names of UNO commands. These typically look like this .uno:SomeCommandName. The text property panel for example defines the default command as .uno:FontDialog which opens the 'Character' dialog. Some Calc contexts override it with .uni:CellTextDlg which opens the 'Format Cells' dialog.

Panel via extension

Here are detailed information about what to do to write an extension that provides new decks and panels. Writing the actual extension is described elsewhere and not described in detail here.

Here is a checklist of what an extension has to bring:

  • A Sidebar.xcu snippet that will be merged in the global Sidebar.xcu file on extension registration . It describes the decks and panels that are provided by the extension.
  • A Factories.xcu snippet that will be merged into the global Factories.xcu file on extension registration. It describes the factory that creates the new panels.
  • Implementation of the new panels.
  • Implementation of the new panel factory.
  • Optional: if your extension brings its own UNO commands then you have to supply your own protocol handler. This requires a ProtocolHandler.xcu snippet and an implementation of the XDispatchProvider interface.
  • Implementation of the __writeRegistryServiceInfo() and __getServiceFactory() methods for registering and creation of the panel factory and protocol handler.
  • A manifest.xml file that lists the files that the extension provides and that have to be deployed when the extension is installed.
  • A description.xml file that provides general properties about the extension such as name, version, supported platforms, minimal OpenOffice version, and about you, the author/publisher.

That may sound like more than it really is. The demo extension that is described below has a total of 15 source files (10 Java files, 3 XCU files, 2 XML files).

Example: Analog Clock Extension

Let's see how this looks in a real (but simple) demo extension. The extension provides a new "Tools" deck and one panel. The panel is shown in the new deck and displays the current time as analog clock. It does that by using a canvas, that is provided by the sidebar framework.
Analog-clock-screenshot.png

You can find the source code as Eclipse project here and the extension here.

Files

This section only shows the important or interesting parts of source code files. The full source and the extension can be found elsewhere.

Sidebar.xcu

The Sidebar.xcu file has two entries. The one for the new "Tools" deck looks like this:

    <node oor:name="ToolsDeck" oor:op="replace">
        <prop oor:name="Title" oor:type="xs:string">
            <value xml:lang="en-US">Tools</value>
        </prop>
        <prop oor:name="Id" oor:type="xs:string">
            <value>ToolsDeck</value>
        </prop>
        <prop oor:name="IconURL" oor:type="xs:string">
            <value>vnd.sun.star.extension://org.apache.openoffice.sidebar.AnalogClock/icons/tools-large.png</value>
        </prop>
        <prop oor:name="ContextList">
            <value oor:separator=";">
                any, any, visible ;
           </value>
        </prop>
    </node>

The name of the outer node, "ToolsDeck" in this case, can be any string. The only constraint is, that it has to be unique among the deck nodes.

The "Id" property is referenced by the panel below. It could also be referenced by panels from other extensions. This tools deck could be the deck for a whole family of panels that come from more than one extensions.

The "IconURL" property defines the icon to use in the tab bar for the button that switches to the "Tools" deck. The value has three parts. The firs part is the protocol for files that are provided by an extension: "vnd.sun.star.extension://". The second part is the name of the extension as defined in the description.xml file. The third part is the relative path of where the icon can be found. That means that the OXT extension file has an entry "icons/tools-large.png". When the extension is registered, this file is copied to somewhere inside the user part of the OpenOffice file system.

The "ContextList" property will be explained below. The panel description has the exact same property.

The second entry in the Sidebar.xcu file describes the new panel:

    <node oor:name="AnalogClockPanel" oor:op="replace">
        <prop oor:name="Title" oor:type="xs:string">
            <value xml:lang="en-US">Analog Clock</value>
        </prop>
        <prop oor:name="Id" oor:type="xs:string">
            <value>AnalogClockPanel</value>
        </prop>
        <prop oor:name="DeckId" oor:type="xs:string">
            <value>ToolsDeck</value>
        </prop>
        <prop oor:name="ContextList">
            <value oor:separator=";">
                any, any, visible ;
            </value>
        </prop>
        <prop oor:name="ImplementationURL" oor:type="xs:string">
            <value>private:resource/toolpanel/AnalogClockPanelFactory/AnalogClockPanel</value>
        </prop>
        <prop oor:name="OrderIndex" oor:type="xs:int">
            <value>100</value>
        </prop>
        <prop oor:name="WantsCanvas" oor:type="xs:boolean">
            <value>true</value>
        </prop>
        <prop oor:name="DefaultMenuCommand">
            <value>org.apache.openoffice.sidebar:ShowAnalogClockOptionsDialog</value>
        </prop>
    </node>

Again, the node name "AnalogClockPanel" is an arbitrary string that only has to be unique, this time among the node names for all panels, not just the ones provided by the extension.

The "Id" property is a unique name that is used internally in the sidebar framework. It is not referenced from any XCU file.

The "DeckId" property references the "Id" property of the deck that was defined above. Therefore it has the same value: "ToolsDeck".

The value of the "ContextList" property defines when to show the panel. Its value consists of one or more lines separated by semicolons. The choice of separator string and the notation of using separate lines are conventions that increase readability. Each line can have three or four, comma separated values. This separator is hard-coded and not a convention. The values are: application name, context name, whether or not the panel is expanded by default. More on this and the optional fourth value can be found [[1]].

The "ImplementationURL" property tells the sidebar framework where to find the service that implements the panel. It has three parts: The prefix "private:resource/toolpanel" is required by the XUIElementFactory and the framework that manages these factories. The second part "AnalogClockPanelFactory" is the name of the ui element factory as defined by the "Name" property in the Factories.xcu file. The third part "AnalogClockPanel" is interpreted by the factory itself and can be freely chosen.

The "OrderIndex" property defines an index that is used to sort panels that are displayed in a deck. Smaller values lead to positions higher up, larger values lead to positions further down. The standard sidebar panels start with a value of 100 and increase in steps of 100. This allows other panels to go before or in between.

The value of the "WantsCanvas" property defaults to "false" and all standard panels use this default. In our case, however, we set it to "true" because we want to paint into our panel with a canvas. This property is interpreted by the sidebar framework and causes a canvas object being setup and passed to the panel factory when the panel is created. This feature is experimental but only uses standard techniques.

With the "DefaultMenuCommand" property we define the command that is dispatched when the user clicks on the "More Options" button in the panel title bar. We can not define a command in the usual ".uno:<command-name>" syntax and have to resort to commands provided by a new protocol handler. Therefore the syntax of the command looks a bit unfamiliar: "org.apache.openoffice.sidebar:ShowAnalogClockOptionsDialog".

Factories.xcu

The panel factory is registered with this XCU snippet:

    <node oor:name="WorkbenchPanelFactory" oor:op="replace">
        <prop oor:name="Type">
            <value>toolpanel</value>
        </prop>
        <prop oor:name="Name">
            <value>AnalogClockPanelFactory</value>
        </prop>
            <prop oor:name="Module">
        <value/>
        </prop>
        <prop oor:name="FactoryImplementation">
            <value>org.apache.openoffice.sidebar.AnalogClockPanelFactory</value>
        </prop>
    </node>

The "Type" property has to have the "toolpanel" value.

We can chose the "Name" property as we like. It just has to be identical to the middle part in the "ImplementationURL" value for the new panel.

The "Module" property remains empty.

The "FactoryImplementation" property defines the service name of the implementation of the panel factory. It can be chosen freely but has to be identical to what the panel factory says its service name is. This service name does not have to be an 'official' service name with a service description in offapi or, in our case, a service description provided by the extension. But an explicit service description is a good thing for a real-life extension.


ProtocolHandler.xcu

To define the command that opens the options dialog we have these lines:

    <node oor:name="org.apache.openoffice.sidebar.ProtocolHandler" oor:op="replace">
        <prop oor:name="Protocols" oor:type="oor:string-list">
      	    <value>org.apache.openoffice.sidebar*</value>
      	</prop>
    </node>

In contrast to node names used in Sidebar.xcu this node name is used to find the protocol handler. It is the service name of our new protocol handler class.

The "Protocols" property defines the command prefix that all our commands have to have.


manifest.xml

The manifest of the extension (the java archive has its own manifest, more on this later) looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
        <manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
        <manifest:file-entry
            manifest:media-type="application/vnd.sun.star.uno-component;type=Java"
            manifest:full-path="AnalogClock.jar"/>
        <manifest:file-entry
            manifest:media-type="application/vnd.sun.star.configuration-data"
            manifest:full-path="registry/data/org/openoffice/Office/UI/Factories.xcu"/>
        <manifest:file-entry
            manifest:media-type="application/vnd.sun.star.configuration-data"
            manifest:full-path="registry/data/org/openoffice/Office/UI/Sidebar.xcu"/>
        <manifest:file-entry
            manifest:media-type="application/vnd.sun.star.configuration-data"
            manifest:full-path="registry/ProtocolHandler.xcu" /> 
    </manifest:manifest>

It tells the extension manager that the three XCU files that we have seen above have to be merged into their global counterparts. Additionally our extension brings one JAR file that contains the actual implementation: "AnalogClock.jar".


description.xml

This is the description of the extension:

  <identifier value="org.apache.openoffice.sidebar.AnalogClock" />
  <version value="1.0" />      <dependencies>
    <OpenOffice.org-minimal-version value="4.0" d:name="Apache OpenOffice"/>
  </dependencies>
  <publisher>
      <name xlink:href="http://www.openoffice.org" lang="en">Apache Software Foundation</name>
  </publisher>
  <display-name>
    <name lang="en">Analog Clock for Sidebar</name>
  </display-name>

The "identifier" property is referenced by the "IconURL" property of our panel description in Sidebar.xcu. It is an arbitrary name that is chosen by the extension author.

The "version" property tells you that this is the first version of the extension.

The "OpenOffice.org-minimal-version" property in the "dependencies" section makes sure that the extension can not be installed in older versions of OpenOffice. Version 4.0 is the first one with support for the sidebar.

Both the "publisher" property and the "display-name" property are used in the extension manager dialog to represent the extension.


Classes

The largest part of the implementation is about generic extension functionality. Here is an overview of the classes in the extension:

Component

The Component class implements two methods that tell the extension manager what services are supported by the extension and how to create instances of them. The two services supported by this extension are the panel factory which is named, well, PanelFactory, and the protocol handler, ProtocolHandler.

PanelFactory

The PanelFactory class implements two interfaces.

XServiceInfo: The XServiceInfo interface with its methods getImplementationname(), supportsService() and getSupportedServiceName() is a generic interface implemented by every class that implements a UNO service. Note that we use a service name without explicit service description. This may not be good form but it helps to keep the amount of generic extension code down.

XUIElementFactory: This interface has only one method. The "createUIElement()" method is called indirectly by the sidebar framework to create a new panel object. This happens when the sidebar is initialized or whenever a panel becomes visible but not when it is expanded. The first of the two arguments is the resource URL that specifies which panel to create. This is more useful for factories that can create more than one type of panel. In our factory we only check that the given resource URL is really the one that it supports. The major part of the factory code is processing the second argument, a list of PropertyValues. These values are meant to be passed on to the new panel. Not every factory and not every panel need all properties. Our factory only uses two, the "ParentWindow" property and the "Canvas" property. The later one is the only property that is provided on demand. This demand is flagged by setting the "WantsCanvas" property to "true" in the panel description in Sidebar.xcu. It is the responsibility of the new panel to create a child window in the given parent window. Position and size of that window are controller by the sidebar framework. The panel only has to react to size changes.

constructor: The constructor of the panel factory is called with an XComponentContext object because of the FactoryHelper that we use in Component. This context is later passed to the panel and used there to get access to a service factory.


PanelBase

The implementation of the actual panel is split into two classes. The base class PanelBase contains everything that other panels might also need. The other, AnalogClockPanel, is a derived class and provides the actual content of the panel.

Most of the work in PanelBase takes place in its Initialize() method that is called from the AnalogClockPanel constructor. It creates a child window in the parent window that was given as "ParentWindow" property to the the panel factory.

The PanelBase class implements four interfaces.

XToolPanel with its Window attribute (and the getWindow() method) gives access to the panel child window. It is used by the layouter in the sidebar framework to position and size the panel. Its second method createAccessible() is the entry point for supporting the UNO accessibility API. It is really implemented in this demo, it just returns the generic accessibility implementation of VCL windows. But if the panel where a regular dialog this might already be enough.

XWindowListener is implemented in order to be informed when the size of the panel window changes or when the panel is shown or hidden. In our extension all these methods result in a call to the abstract Layout() method which is implemented in AnalogClockPanel.

XSidebarPanel has one method, getHeightForWidth(). In PanelBase there is a default implementation that tells the sidebar panel layouter to use the current window size and not to change it.

XComponent is supported to give the owner of the panel, the sidebar framework, the opportunity to dispose the panel. In Java this is especially useful because of the missing destructors.


AnalogClockPanel

This is the center of the whole extension. It implements a simple analog clock that is displayed via an XCanvas object. It extends the PanelBase base class and does not implement any additional interfaces.

In its constructor it sets up some frequently used variables. For the canvas these are instances of ViewState and RenderState. Additionally a timer is started that calls back two times per second. Calling back more than once per second prevents temporal aliasing where irregularities in the time can lead to one call being made very shortly after the beginning of a second and the following call is made very shortly before the end of the second. As both calls will report the same time, the clock would not be updated until the third call and therefore show the same time for almost two seconds. Two seconds don't sound like much but this would be a visible stuttering.

The Layout() method that is called every time the window is resized or shown just stores the new size. The little layouting that is done for the clock face is done on every repaint.

The clock is painted in the PaintCurrentTime() method. A circle represents the clock face. Three hands for hours, minutes and seconds show the current time. All very simple. The clock face and the hands are created as Java Shape objects, one Ellipse2D and three Line2D objects. These are painted in DrawShape() onto the XCanvas. Java allows us to iterate over the individual parts of the shape paths, each part being a moveto, lineto, curveto (quadratic or cubic) or closing. These are converted in appropriate canvas draw commands. Here is the snippet that process the cubic bezier parts of each curve:

    final PathIterator aPathIterator = aShape.getPathIterator(null);
    final double[] aCoordinates = new double[6];
    double nX = 0;
    double nY = 0;
    while ( ! aPathIterator.isDone())
    {
        switch (aPathIterator.currentSegment(aCoordinates))
        {
            ...
            case PathIterator.SEG_CUBICTO:
                 // It looks like the canvas bezier curve definition is broken.
                 // We have to swap the second control point and the end point of the curve.
                 mxCanvas.drawBezier(
                     new RealBezierSegment2D(
                          nX,nY,
                          aCoordinates[0], aCoordinates[1],
                          aCoordinates[4], aCoordinates[5]),
                     new RealPoint2D(aCoordinates[2], aCoordinates[3]),
                     maViewState,
                     maRenderState);
                 nX = aCoordinates[4];
                 nY = aCoordinates[5];
                 break;
             ...
        }
        apathIterator.next();
    }

Note that in the constructor of RealBezierSegment2D the second bezier control point and the and point of the curve have changed places. This is a workaround for a bug in the canvas implementation.

The dispose() method stops the time, to avoid callbacks to a dead.

The two methods GetColor() and SetColor() allow the options dialog to change the colors of clock face and hands. More on that in the description of class AnalogClockOptionsDialog.


UIElement

This class implements the XUIElement interface and is just a wrapper around the panel implementation. It could be integrated into the PanelBase class but the separation hopefully makes the separate responsibilities more clear. The XUIElement interface mostly provides access to some objects like XFrame, resource URL (that is the URL given to the panel factory to create the panel) and the element type (for sidebar panels always css.ui.UIElementType.TOOLPANEL). The only interesting method is getRealInterface() which probably should better be named getRealObject() or getRealImplementation(). It allows separation of generic UIElement code and specialized panel implementation.


AnalogClockOptionsDialog

This dialog lets the user change the colors of clock face and all three hands at run time. It demonstrates how to provide a "MoreOptions" dialog in an extension.

Because it is a new dialog there is no UNO command for it. Therefore we have to create a new command as well. But the extension framework does not allow extensions to create commands in the ".uno:<command>" syntax so we have to provide a new protocol with the ProtocolHandler. How this is done has already been explained.

The dispatch() method of the ProtocolHandler calls the static AnalogClockOptionsDialog.Show() method. When the dialog is already visible it does nothing. Otherwise it creates a new dialog and shows it.

The first thing the dialog does is to activate the system look and feel:

    try 
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

This makes the dialog look less 'Javaish' and more like any other dialog on the platform the extension runs on.

The dialog consists of four radio buttons, a 'Close' button, and a color chooser. The radio buttons connect the color chooser with either the clock face or one of the three hands. Selecting a color in the color chooser directly changes the respective color in the running clock panel. The close button closes the dialog, as does the closer in the dialog title bar.

Building the extension

The easiest part of the extension is the hardest part to describe. How the extension is built---compiled, creation of the jar file, creation of the oxt file---depends on the development environment you use.

Eclipse

The AnalogClock extension has been developed with Eclipse. It was created as standard Java project with separate directories for source and compiled files. Open the properties dialog for the new project and under Java Build Path->Libraries add references to external JARs java_uno.jar, juh.jar, jurt.jar and unoil.jar. All of them can be found in the program/classes directory of an OpenOffice installation. Alternatively search for them in an OpenOffice SDK. Under the src directory add directories icons, registry, META-INF for the non-Java files that are part of the extension.

Export the project as Ant buildfile. Just click through the export dialog. This creates a file build.xml toplevel in the project. Add a second buildfile beside it, eg build2.xml:

    <?eclipse.ant.import?>
    <project basedir="." default="otx">
        <target name="jar">
            <jar destfile="dist/AnalogClock.jar">
                <fileset dir="bin" includes="**/*.class">
                </fileset>
                <manifest>
                    <attribute
                        name="RegistrationClassName"
                        value="org.apache.openoffice.sidebar.Component"/>
                </manifest>
            </jar>
        </target>
        <target name="otx" depends="jar">
            <zip destfile="dist/AnalogClock.oxt">
                <zipfileset dir="src" includes="META-INF/manifest.xml"/>
                <zipfileset dir="dist" includes="AnalogClock.jar"/>
                <zipfileset dir="src" includes="description.xml"/>
                <zipfileset dir="src" includes="registry/**"/>
                <zipfileset dir="src" includes="icons/tools-large.png"/>
            </zip>
        </target>
    </project>

It has two targets. The "jar" target creates the jar file that creates all the compiled .class files under bin/. It also create a manifest file that registers the Component class as "RegistrationClassName". This is how the extension manager knows which class implements the __writeRegistryServiceInfo() and __getServiceFactory() functions.

The "otx" target depends on the "jar" target. It creates the AnalogClock.oxt file in another top level directory dist/ and adds the various files that make up the extension: the manifest.xml, the jar archive, the description.xml, all xcu files under registry/ and the icon in icons/ that is used in the tab bar of the sidebar for the new deck.

Export the Ant buildfile a second time, again just click through the dialog. This will automatically add a reference of the second ant file to the first.

To save a few clicks you can make the Ant view always visible (menu Window->Show View, select Ant). In the view click on the "Add Buildfiles". In the dialog that pops up, expand the entry for your project file and select build.xml. Click OK and the Ant view will show an entry for the otx target. Double click it and the jar and oxt files are built. Remember that on Eclipse you don't have to compile Java code. That is done automatically in the background when you save a Java file.

The extension is added to OpenOffice like any other extension. Tools->Extension Manager opens the extension manager dialog. Click the add button, regardless of wether the extension is already registered or not. Navigate to the dist/ directory under the project path of the extension project. Click on Open and finish the installation of the extension. Restart OpenOffice. You should see a new button for the new deck. Click on that button and the analog clock should appear.

Errors during the installation of the extension are hard to find because the extension manager seldomly presents meaningful error messages and debugging at this stage is not possible. Therefore the full source code still contains a simple Log class that is used to write some diagnostic messages and stack traces to file. This may help to pinpoint errors at this stage. If you want to use it, just set its static msLogFilename variable to a valid file name.

Resources

Personal tools