Difference between revisions of "Sidebar for Developers"

From Apache OpenOffice Wiki
Jump to: navigation, search
m (Trying out syntax highlighting)
m (Added syntax highlighting for all code snippets.)
Line 72: Line 72:
 
         </prop>
 
         </prop>
 
         <prop oor:name="IconURL" oor:type="xs:string">
 
         <prop oor:name="IconURL" oor:type="xs:string">
             <value>vnd.sun.star.extension://org.apache.openoffice.sidebar.AnalogClock/icons/tools-large.png</value>       </prop>
+
             <value>vnd.sun.star.extension://org.apache.openoffice.sidebar.AnalogClock/icons/tools-large.png</value>
 +
        </prop>
 
         <prop oor:name="ContextList">
 
         <prop oor:name="ContextList">
 
             <value oor:separator=";">
 
             <value oor:separator=";">
Line 91: Line 92:
  
 
The second entry in the Sidebar.xcu file describes the new panel:
 
The second entry in the Sidebar.xcu file describes the new panel:
 +
<syntaxhighlight lang="XML">
 
     <node oor:name="AnalogClockPanel" oor:op="replace">
 
     <node oor:name="AnalogClockPanel" oor:op="replace">
 
         <prop oor:name="Title" oor:type="xs:string">
 
         <prop oor:name="Title" oor:type="xs:string">
Line 119: Line 121:
 
         </prop>
 
         </prop>
 
     </node>
 
     </node>
 +
</syntaxhighlight>
 
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.
 
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.
  
Line 138: Line 141:
  
 
The panel factory is registered with this XCU snippet:
 
The panel factory is registered with this XCU snippet:
 +
<syntaxhighlight lang="XML">
 
     <node oor:name="WorkbenchPanelFactory" oor:op="replace">
 
     <node oor:name="WorkbenchPanelFactory" oor:op="replace">
 
         <prop oor:name="Type">
 
         <prop oor:name="Type">
Line 152: Line 156:
 
         </prop>
 
         </prop>
 
     </node>
 
     </node>
 +
</syntaxhighlight>
 
The "Type" property has to have the "toolpanel" value.
 
The "Type" property has to have the "toolpanel" value.
  
Line 164: Line 169:
  
 
To define the command that opens the options dialog we have these lines:
 
To define the command that opens the options dialog we have these lines:
 +
<syntaxhighlight lang="XML">
 
     <node oor:name="org.apache.openoffice.sidebar.ProtocolHandler" oor:op="replace">
 
     <node oor:name="org.apache.openoffice.sidebar.ProtocolHandler" oor:op="replace">
 
         <prop oor:name="Protocols" oor:type="oor:string-list">
 
         <prop oor:name="Protocols" oor:type="oor:string-list">
Line 169: Line 175:
 
       </prop>
 
       </prop>
 
     </node>
 
     </node>
 +
</syntaxhighlight>
 
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.
 
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.
  
Line 177: Line 184:
  
 
The manifest of the extension (the java archive has its own manifest, more on this later) looks like this:
 
The manifest of the extension (the java archive has its own manifest, more on this later) looks like this:
 +
<syntaxhighlight lang="XML">
 
     <?xml version="1.0" encoding="UTF-8"?>
 
     <?xml version="1.0" encoding="UTF-8"?>
 
         <manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
 
         <manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
Line 192: Line 200:
 
             manifest:full-path="registry/ProtocolHandler.xcu" />  
 
             manifest:full-path="registry/ProtocolHandler.xcu" />  
 
     </manifest:manifest>
 
     </manifest:manifest>
 +
</syntaxhighlight>
 
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".
 
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".
  
Line 198: Line 207:
  
 
This is the description of the extension:
 
This is the description of the extension:
 +
<syntaxhighlight lang="XML">
 
   <identifier value="org.apache.openoffice.sidebar.AnalogClock" />
 
   <identifier value="org.apache.openoffice.sidebar.AnalogClock" />
 
   <version value="1.0" />      <dependencies>
 
   <version value="1.0" />      <dependencies>
Line 208: Line 218:
 
     <name lang="en">Analog Clock for Sidebar</name>
 
     <name lang="en">Analog Clock for Sidebar</name>
 
   </display-name>
 
   </display-name>
 +
</syntaxhighlight>
 
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 "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.
  
Line 262: Line 273:
  
 
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:
 
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:
 +
<syntaxhighlight lang="java">
 
     final PathIterator aPathIterator = aShape.getPathIterator(null);
 
     final PathIterator aPathIterator = aShape.getPathIterator(null);
 
     final double[] aCoordinates = new double[6];
 
     final double[] aCoordinates = new double[6];
Line 289: Line 301:
 
         apathIterator.next();
 
         apathIterator.next();
 
     }
 
     }
 +
</syntaxhighligh>
 
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.
 
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.
  
Line 310: Line 323:
  
 
The first thing the dialog does is to activate the system look and feel:
 
The first thing the dialog does is to activate the system look and feel:
 +
<syntaxhighlight lang="java">
 
     try  
 
     try  
 
     {
 
     {
Line 318: Line 332:
 
         e.printStackTrace();
 
         e.printStackTrace();
 
     }
 
     }
 +
</syntaxhighlight>
 
This makes the dialog look less 'Javaish' and more like any other dialog on the platform the extension runs on.
 
This makes the dialog look less 'Javaish' and more like any other dialog on the platform the extension runs on.
  
Line 332: Line 347:
  
 
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:
 
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:
 +
<syntaxhighlight lang="XML">
 
     <?eclipse.ant.import?>
 
     <?eclipse.ant.import?>
 
     <project basedir="." default="otx">
 
     <project basedir="." default="otx">
Line 355: Line 371:
 
         </target>
 
         </target>
 
     </project>
 
     </project>
 +
</syntaxhighlight>
 
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.
 
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.
  

Revision as of 07:39, 11 June 2013

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.


General Information

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.


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 snippert that will be merged ino 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 exaplained 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 hardcoded and not a convention. The values are: application name, context name, whether or not the panel is expaned 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 craeate 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 everytying 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();
    }
</syntaxhighligh>
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.  Whent 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:
<syntaxhighlight lang="java">
    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.

Personal tools