Geertjan is a DZone Zone Leader and has posted 466 posts at DZone. You can read more from them at their website. View Full User Profile

How Fast Can OpenOffice.org Be Extended?

04.01.2008
| 12103 views |
  • submit to reddit

OpenOffice.org, the free and open source office productivity suite, can be extended, surprisingly easily. The typical scenarios in the context of samples and documentation tend to favor C++. Comprehensive documentation for Java extensions is growing, but for the complete newbie it can be hard to know where to start. Step by step, this article will show how to create an extension that provides a PopupMenuController for displaying available extensions in the OpenOffice.org extension catalog.

Here is what we will have by the time we complete the procedure that follows. In other words, when you select the "Wizards" popup, the extensions available in the OpenOffice.org extension catalog will be listed:

What the above implies is that we are going to override the Wizards menu item which, in the context above, is called a "popup" in OpenOffice.org terms. It is provided by the PopupMenuController service. For this particular scenario, we need, apart from our Java code, two XML files, together with the crucial "Controller.xcu" file. That file is used to register the PopupMenuController service. Here it is:

What does the above file tell us? There are three properties that we have defined. The first, "Command", specifies which existing PopupMenuController we are going to override. The PopupMenuController is called "AutoPilotMenu". That, however, is not its display name. Its display name is "Wizards". For mappings between commands and their display names, see Framework/Article OpenOffice.org 2.x Commands. The second property is "Module". That property specifies the application (i.e., Writer or Calc or one/more of the other OpenOffice.org applications) in which the PopupMenuController will appear. Here it is blank and so it will appear in all applications. The third property, "Controller", specifies the class that will implement our PopupMenuController.

In addition, notice the element hierarchy above. It starts Registered/PopupMenu. What else could be registered in this file? ToolbarControllers and StatusBarControllers. To see all the default controllers, go to your OpenOffice.org installation folder and then browse to share/registry/data/org/openoffice/Office/UI. There, amongst many other files, you will find Controller.xcu. The structure of that file is what we are trying to mimic above. Put the above file in the root package (i.e., the nameless default package) of your Java application.

Now you need to register the Controller.xcu file. You do that in an XML file called uno-extension-manifest.xml. It should be located in the same package as the Controller.xcu file. For this scenario, we need to register the Controller.xcu file, but also the name of the JAR that will be created. Here is the entire content of the uno-extension-manifest.xml file:

And now we need a small XML file named Description.xml, in the same location as the above two, with content as follows:

Next, let's look at some Java code. The general code-level process in this case starts with registration. Once you have provided the code that registers your extension with the services provided by OpenOffice.org, you can implement the PopupMenuController. There are also several methods required by the superclasses that we will leave unimplemented.

However, let's reveal at this point that there's a very handy plugin in NetBeans IDE that make everything a lot easier. The OpenOffice.org API Plugin has been around since 5.5 and before. It is now in the 6.1 Beta update center and I've used it without a problem. The plugin provides wizards and build scripts so that grunt work, from creation to deployment, is handled for you. For example, project templates are provided to get you going in several different directions:

Then, during the next steps, as shown below, you can define the service you want to work with. In this case, we want to work with com.sun.star.frame.PopupMenuController. So, we select that in the template:

And now, as a result of that selection, when the wizard is finished, all the code below is created for you to give you an entry point into the OpenOffice.org API:

package com.example;

import com.sun.star.uno.XComponentContext;
import com.sun.star.lib.uno.helper.Factory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.lib.uno.helper.ComponentBase;


public final class SearchExtensions extends ComponentBase
implements com.sun.star.lang.XServiceInfo,
com.sun.star.frame.XDispatchProvider,
com.sun.star.frame.XStatusListener,
com.sun.star.lang.XInitialization,
com.sun.star.frame.XPopupMenuController
{
private final XComponentContext m_xContext;
private static final String m_implementationName = SearchExtensions.class.getName();
private static final String[] m_serviceNames = {
"com.sun.star.frame.PopupMenuController" };


public SearchExtensions( XComponentContext context )
{
m_xContext = context;
};

public static XSingleComponentFactory __getComponentFactory( String sImplementationName ) {
XSingleComponentFactory xFactory = null;

if ( sImplementationName.equals( m_implementationName ) )
xFactory = Factory.createComponentFactory(SearchExtensions.class, m_serviceNames);
return xFactory;
}

public static boolean __writeRegistryServiceInfo( XRegistryKey xRegistryKey ) {
return Factory.writeRegistryServiceInfo(m_implementationName,
m_serviceNames,
xRegistryKey);
}

// com.sun.star.lang.XServiceInfo:
public String getImplementationName() {
return m_implementationName;
}

public boolean supportsService( String sService ) {
int len = m_serviceNames.length;

for( int i=0; i < len; i++) {
if (sService.equals(m_serviceNames[i]))
return true;
}
return false;
}

public String[] getSupportedServiceNames() {
return m_serviceNames;
}

// com.sun.star.frame.XDispatchProvider:
public com.sun.star.frame.XDispatch queryDispatch(com.sun.star.util.URL URL, String TargetFrameName, int SearchFlags)
{
// TODO: Exchange the default return implementation for "queryDispatch" !!!
// NOTE: Default initialized polymorphic structs can cause problems
// because of missing default initialization of primitive types of
// some C++ compilers or different Any initialization in Java and C++
// polymorphic structs.
return null;
}

public com.sun.star.frame.XDispatch[] queryDispatches(com.sun.star.frame.DispatchDescriptor[] Requests)
{
// TODO: Exchange the default return implementation for "queryDispatches" !!!
// NOTE: Default initialized polymorphic structs can cause problems
// because of missing default initialization of primitive types of
// some C++ compilers or different Any initialization in Java and C++
// polymorphic structs.
return null;
}

// com.sun.star.lang.XEventListener:
public void disposing(com.sun.star.lang.EventObject Source)
{
// TODO: Insert your implementation for "disposing" here.
}

// com.sun.star.frame.XStatusListener:
public void statusChanged(com.sun.star.frame.FeatureStateEvent State)
{
// TODO: Insert your implementation for "statusChanged" here.
}

// com.sun.star.lang.XInitialization:
public void initialize(Object[] aArguments) throws com.sun.star.uno.Exception
{
// TODO: Insert your implementation for "initialize" here.
}

// com.sun.star.frame.XPopupMenuController:
public void setPopupMenu(com.sun.star.awt.XPopupMenu PopupMenu)
{
// TODO: Insert your implementation for "setPopupMenu" here.
}

public void updatePopupMenu()
{
// TODO: Insert your implementation for "updatePopupMenu" here.
}

}

In addition to the above, another class is also created. This class centralizes registration for all the services provided by your extension. Hence it is called "CentralRegistrationClass.java", as that's what it does. I won't reproduce it here because all of it is boilerplate code and not very interesting.

And now it is our turn to do some coding. Let's begin by declaring com.sun.star.frame.XFrame and com.sun.star.awt.XPopupMenu. The latter will provide the popup menu that we will create later. So here are my two additional declarations, below those that have already been declared:

XFrame m_xFrame;
XPopupMenu m_xPopupMenu;

Next, notice that there are a few very handy methods that we are required to do something with. Two of them are setPopupMenu and updatePopupMenu. For both of them, let's create a utility method that parses one of the feeds that lists OpenOffice.org extensions:

void fillPopupMenu(XPopupMenu xPopupMenu) {

if (xPopupMenu != null) {

// prepare to read our extensions feed:
BufferedReader in = null;
try {
java.net.URL url = new java.net.URL("http://extensions.services.openoffice.org/taxonomy/term/2/0/feed");
in = new BufferedReader(new InputStreamReader(url.openStream()));
InputSource source = new InputSource(in);

// parse it somehow, in this case using
// org.openide.xml.XMLUtil from the NetBeans APIs:
Document doc = XMLUtil.parse(source, false, false, null, null);
NodeList localList = doc.getElementsByTagName("*");
int length = localList.getLength();
for (int i = 0; i < length; i++) {
org.w3c.dom.Node mainNode = localList.item(i);
String nodeName = mainNode.getNodeName();

// if an element in the XML is "item":
if (nodeName.equals("item")) {
NodeList kids = mainNode.getChildNodes();
for (int k = 0; k < kids.getLength(); k++) {
org.w3c.dom.Node kidNode = kids.item(k);
String kidNodeName = kids.item(k).getNodeName();
String kidValue = kidNode.getTextContent();

//...find its child element called "title":
if (kidNodeName.equals("title")) {

// add the result to the popup menu:
xPopupMenu.insertItem((short)(0), "--> " + kidValue, (short) 0, (short) 0);

}
}
}
}
} catch (SAXException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}

Now that we have that utility method, we can implement the aforementioned required methods:

@Override
public void setPopupMenu(XPopupMenu xPopupMenu) {
// We must have a valid frame reference to fill our popup menu
if (m_xFrame != null) {
m_xPopupMenu = xPopupMenu;
}
// Add a listener to our popup menu
xPopupMenu.addMenuListener((XMenuListener) UnoRuntime.queryInterface(XMenuListener.class, this));

fillPopupMenu(xPopupMenu);
}

@Override
public void updatePopupMenu() {
fillPopupMenu(m_xPopupMenu);
}

Finally, set all the other required methods to null, or make them empty, or let them return true. Those can be filled in later. For example, the select method can be overridden to perform the action that is invoked when an item in the popup menu is selected.

Now, assuming you used NetBeans IDE to create your OpenOffice extension project, you are already good to go. That is because NetBeans IDE provides all the necessary Ant scripts required for the building and packing of your OpenOffice.org extension. Your extension should look something like this:

And, in fact, when you choose "Deploy Extension in Target OpenOffice.org", OpenOffice.org will start up and install your extension, right from inside NetBeans IDE.

Best of all, it is clear that we have only had to deal with the business logic of our OpenOffice.org extension. All the boilerplate code and the building/packaging of our extension have been done under the hood for us. That's pretty cool and a significant step forward in the viability of OpenOffice.org extensions.

Resources

  • Open Office.org
  • OpenOffice.org SDK
  • Framework/Tutorial/Popup Menu Controller
  • OO Snippets: Adding a Combo-box to a Toolbar
  • Framework/Article OpenOffice.org 2.x Commands
  • AttachmentSize
    toolbarXml.png61.28 KB
    view-current-extensions.png81.64 KB
    xml-manifest.png37.6 KB
    descriptionXml.png33.57 KB
    service-selector.png46.55 KB
    ooo-wizard.png54.76 KB
    final-proj-window.png19.57 KB
    Published at DZone with permission of its author, Geertjan Wielenga.

    Comments

    othman El moulat replied on Sun, 2011/07/17 - 9:10am

    thank you for this important article!

    i tried doing same with XToolbarController but it didn't worked for me. i implemented the  createItemWindow(com.sun.star.awt.XWindow Parent) to display a combobox in toolbar but it does'nt show.

     my class is below . can you help what i am missing?

    thanks

     -------------

    my Controller.xcu :

    <?xml version="1.0" encoding="UTF-8"?>
    <oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="Controller" oor:package="org.openoffice.Office.UI">
      <node oor:name="Registered">
        <node oor:name="ToolBar">
          <node oor:name="c10" oor:op="replace">
             <prop oor:name="Command">
                <value>.uno:CommandLineToolbar</value>
          </prop>
          <prop oor:name="Module">
                 <value></value>
          </prop>
          <prop oor:name="Controller">
                  <value>com.example.toolbar.Toolbar</value>
          </prop>
          </node>
        </node>
      </node>
    </oor:component-data>

     my toolbar controller class :

     
    
    public final class Toolbar extends ComponentBase
       implements com.sun.star.util.XUpdatable,
                  com.sun.star.frame.XToolbarController,
                  com.sun.star.frame.XStatusListener,
                  com.sun.star.lang.XServiceInfo,
                  com.sun.star.lang.XInitialization,
                  com.sun.star.frame.XSubToolbarController
    {
        private final XComponentContext m_xContext;
        private static final String m_implementationName = Toolbar.class.getName();
        private static final String[] m_serviceNames = {
            "com.sun.star.frame.ToolbarController" };
     private String msInternalName = "i do not now";
        private XComponent mxDocument;
        private XTextComponent fixedText;
        private XComboBox cBox_xComboBox;
    private XMultiComponentFactory mxMCF;
        public XMultiServiceFactory mxMSF;
        public Toolbar( XComponentContext context )
        {
            m_xContext = context;
          
        };
    
        public static XSingleComponentFactory __getComponentFactory( String sImplementationName ) {
            XSingleComponentFactory xFactory = null;
    
            if ( sImplementationName.equals( m_implementationName ) )
                xFactory = Factory.createComponentFactory(Toolbar.class, m_serviceNames);
            return xFactory;
        }
    
        public static boolean __writeRegistryServiceInfo( XRegistryKey xRegistryKey ) {
            return Factory.writeRegistryServiceInfo(m_implementationName,
                                                    m_serviceNames,
                                                    xRegistryKey);
        }
    
        // com.sun.star.util.XUpdatable:
        public void update()
        {
            // TODO: Insert your implementation for "update" here.
        }
    
        // com.sun.star.frame.XToolbarController:
        public void execute(short KeyModifier)
        {
            // TODO: Insert your implementation for "execute" here.
        }
    
        public void click()
        {
            // TODO: Insert your implementation for "click" here.
        }
    
        public void doubleClick()
        {
            // TODO: Insert your implementation for "doubleClick" here.
        }
    
        public com.sun.star.awt.XWindow createPopupWindow()
        {
            // TODO: Exchange the default return implementation for "createPopupWindow" !!!
            // NOTE: Default initialized polymorphic structs can cause problems
            // because of missing default initialization of primitive types of
            // some C++ compilers or different Any initialization in Java and C++
            // polymorphic structs.
            return null;
        }
    
        public com.sun.star.awt.XWindow createItemWindow(com.sun.star.awt.XWindow Parent)
        {
             System.out.println("ToolbarCombobox createItemWindow start");
           Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
    
    // xMSF is set by initialize(Object[])
          try {
    // get XWindowPeer
            XWindowPeer xWinPeer = (XWindowPeer) UnoRuntime.queryInterface(XWindowPeer.class, Parent);
            Object o = mxMSF.createInstance("com.sun.star.awt.Toolkit");
            XToolkit xToolkit = (XToolkit) UnoRuntime.queryInterface(XToolkit.class, o);
    // create WindowDescriptor
            WindowDescriptor wd = new WindowDescriptor();
            wd.Type = WindowClass.SIMPLE;
            wd.Parent = xWinPeer;
            wd.Bounds = new Rectangle(0, 0, 230, 23);
            wd.ParentIndex = (short) -1;
            wd.WindowAttributes = WindowAttribute.SHOW |VclWindowPeerAttribute .DROPDOWN;
            wd.WindowServiceName = "combobox";
    // create ComboBox
            XWindowPeer cBox_xWinPeer = xToolkit.createWindow(wd);
            cBox_xComboBox = (XComboBox) UnoRuntime.queryInterface(XComboBox.class, cBox_xWinPeer);
    // Get Interface for manipulating the text in the combobox
            fixedText = (XTextComponent)UnoRuntime.queryInterface(XTextComponent.class,cBox_xComboBox);
            fixedText.setText("Enter Command Here");
            XWindow cBox_xWindow = (XWindow) UnoRuntime.queryInterface(XWindow.class, cBox_xWinPeer);
     // add some elements
            cBox_xComboBox.addItems(new String[] { "test", "foo", "bar", "test2", "foo2", "bar2" }, (short) 0);
    // cBox_xComboBox.addItems(new String[] {""}, (short) 4);
            cBox_xComboBox.setDropDownLineCount((short) 6);
            //cBox_xWindow.addKeyListener(this);
    
            System.out.println("ToolbarCombobox createItemWindow end");
    
            return cBox_xWindow;
    
          } catch (com.sun.star.uno.Exception e) {
            System.out.println("ToolbarCombobox createItemWindow end not o.k.");
            return null; }
        }
    
        // com.sun.star.lang.XEventListener:
        public void disposing(com.sun.star.lang.EventObject Source)
        {
            // TODO: Insert your implementation for "disposing" here.
        }
    
        // com.sun.star.frame.XStatusListener:
        public void statusChanged(com.sun.star.frame.FeatureStateEvent State)
        {
            // TODO: Insert your implementation for "statusChanged" here.
        }
    
        // com.sun.star.lang.XServiceInfo:
        public String getImplementationName() {
             return m_implementationName;
        }
    
        public boolean supportsService( String sService ) {
            int len = m_serviceNames.length;
    
            for( int i=0; i < len; i++) {
                if (sService.equals(m_serviceNames[i]))
                    return true;
            }
            return false;
        }
    
        public String[] getSupportedServiceNames() {
            return m_serviceNames;
        }
    
        // com.sun.star.lang.XInitialization:
        public void initialize(Object[] aArguments) throws com.sun.star.uno.Exception
        {
            if ( aArguments.length > 0 )
            {
                mxMCF = m_xContext.getServiceManager();
               
                mxMSF = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, mxMCF);
            }
    
        }
    
        // com.sun.star.frame.XSubToolbarController:
        public boolean opensSubToolbar()
        {
            // TODO: Exchange the default return implementation for "opensSubToolbar" !!!
            // NOTE: Default initialized polymorphic structs can cause problems
            // because of missing default initialization of primitive types of
            // some C++ compilers or different Any initialization in Java and C++
            // polymorphic structs.
            return false;
        }
    
        public String getSubToolbarName()
        {
            // TODO: Exchange the default return implementation for "getSubToolbarName" !!!
            // NOTE: Default initialized polymorphic structs can cause problems
            // because of missing default initialization of primitive types of
            // some C++ compilers or different Any initialization in Java and C++
            // polymorphic structs.
            return new String();
        }
    
        public void functionSelected(String aCommand)
        {
            // TODO: Insert your implementation for "functionSelected" here.
        }
    
        public void updateImage()
        {
            // TODO: Insert your implementation for "updateImage" here.
        }
    
    }

     

     

     

    Comment viewing options

    Select your preferred way to display the comments and click "Save settings" to activate your changes.