Sidebar for Developers

From Apache OpenOffice Wiki
Revision as of 09:04, 16 July 2013 by Andre (Talk | contribs)

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

General Information

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

Much of what has to be done to write a new sidebar panel is the same for extensions and core.

First you have to decide 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 visible panels in the property deck changes with the current configuration. Other decks usually show always the same panel, regardless of the context. But these are not hardwired rules. New panels can behave differently, when there is a good reason.

The implementation of a panel has only to implement two interfaces: css::ui::XUIElement and css::ui::XToolPanel. The css::ui::XSidebarPanel interface is optional.

Panels are created by factories via the css::ui::XUIElementFactory interface. If you add a panel to the core then you will probably use one of the existing panel factories in SVX, SW, SC, or SD. An extension will usually bring its own factory.

There is a description of each deck and each panel in the Sidebar.xcu file. Decks don't have any implementation per-deck. They are only described by their properties in the XCU file. Panels have both implementation and a XCU description.


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

Implemenation design

The UI of the sidebar consists of two major components:

  • The buttons in the vertical tab bar on the right switch manually between sidebar decks.
  • The content area contains a two-tier hierarchy of deck and content panels. There is one deck visible at any one time. It contains one or more content panels. A content panel can be displayed expanded or collapsed.

The visible deck and content panels depend on the current context. Each context change may result in replacing one set of visible panels with another or changing the whole deck. A context consists of two strings:

  • The id of the current application (like com.sun.star.text.TextDocument for Writer).
  • The name of the actual context (like Text for text editing, Table for editing table content, or default for the default context)

Context

What makes up a context depends on the application. For example Text is the default context for Writer but in Impress you have to select a text object to enter that context.

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.

Forced context changes

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.

Defining the set of decks and panels

Apart from the actual implementation the sidebar framework needs to know which decks and panels exists, which panels are to be displayed in each deck and for what configuration to display each deck and panel.

The configuration files Sidebar.xcs (schema) and Sidebar.xcu (data) can be found in the main/officecfg module. They describe a list of decks and a list of panels.

Decks

Each deck has

  • a unique id ("Id") which is referenced by panels that are to be displayed in the deck
  • a localized title ("Title") for display in the deck title bar
  • two URLs for the icon in the tab bar, one ("IconURL") is the default icon, the other ("HighContrastIconURL") is used when high contrast mode is active
  • an integer number ("OrderIndex") that describes the order in which deck icons are displayed in the tab bar; higher values result in locations farther down.
  • a string list of context descriptions ("ContextList") that specify for which contexts to display the deck; see more details about this in one of the next sections.

Panels

Each panel has

  • a unique id ("DeckId") which references a deck by its "Id" field; the panel will only be displayed in the referenced deck
  • a localized title ("Title") for display in the deck title bar
  • a flag ("TitleBarIsOptional") that indicates whether the title bar can be omitted when the panel is the only one in the deck
  • a command name ("DefaultMenuCommand") for opening a dialog with more detailed options (eg. ".uno:FormatArea"); when no command is given then no menu icon will be displayed in the panel title bar
  • an integer number ("OrderIndex") that describes the order in which panels are placed in their deck; higher values result in locations farther down.
  • a string list of context descriptions ("ContextList") that specify for which contexts to display the deck. See more details about this in the the next section.
  • a URL for specifying the implementation ("ImplementationURL") like private:resource/toolpanel/SvxPanelFactory/AreaPropertyPanel.

Context Specification

The "ContextList" properties of both decks and panels have basically a table form that is evaluated after each context change. If one of its rows matches the new context then deck or panel is displayed.

Rows are really separated by semicolons (or whatever you specify in the opening value tag). Formatting them into rows just makes reading their content easier. Each row contains three or four values which are separated by commas. Leading and trailing spaces are ignored.

Columns are:

Application name

This can be the full application name as returned by frame.ModuleManager but to increase readability shorter names can also be used. There is a special name, DrawImpress for the common case that decks and panels are handled exactly the same in Draw and Impress. Similarily WriterAndWeb covers the applications Writer and Writer/Web. Recognized values are:

com.sun.star.text.TextDocument
com.sun.star.text.WebDocument
com.sun.star.sheet.SpreadsheetDocument
com.sun.star.presentation.PresentationDocument
com.sun.star.drawing.DrawingDocument
            
Writer
WriterWeb
Calc
Impress
Draw
  
DrawImpress
WriterAndWeb
any
none

Most of these are self explanatory. DrawImpress and WriterAndWeb have been explained above. any matches any application while none matches no application. Use none to disable deck or panel temporarily during development.

Context name

Recognized values are 3DObject, Annotation, Auditing, Cell, Chart, Draw, DrawPage, DrawText, EditCell, Form, Frame, Graphic, HandoutPage, MasterPage, Media, Multiobj, OLE, OutlineText, Pivot, SlidesorterPage, Table, Text, TextObject. It is, however, possible to invent your own context names. The special value any matches any context name.

Initial state

Can be either visible or hidden. For decks this state decides whether the deck is initially enabled or disabled. You can change this state at runtime via the top button in the sidebar tab bar. For panels this state controls whether the panel is initially expanded or collapsed. Click on the panel title bar to toggle this state.

Menu command override

This optional value is only used for panels. It overrides the "DefaultMenuCommand" for the panel.

Here is an example for the "Area" property panel:

   <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 all applications. 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.

Legacy addons

Legacy addons are find in the application specific WindowState configuration files and instaniated via the existing ui::UIElementFactoryManager infrastructure is used. 5.3 Sidebar

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