Introduction

UNO stands for Universal Network Objects and is the base component technology for Apache OpenOffice.

GUNO stands for Groovy UNO and is an Apache Groovy language extension of the Java UNO API’s. The artifact of this extension is a Java jar file that when used in a Groovy script or class adds convenience methods to the regular Java UNO API’s.

The goal of the Groovy UNO Extension is to allow UNO programming that is less verbose than using the Java UNO API’s alone.

These methods are implemented using Groovy Extension Modules. An extension module allows you to add new methods to existing classes, including classes which are precompiled, like classes from the JDK or in this case Java UNO classes. These new methods, unlike those defined through a metaclass or using a category, are available globally.

Aside from a few general methods, initial efforts have been on enhancing the spreadsheet API’s and future work will be on enhancing the other applications.

This guide is not intended to be an OpenOffice UNO tutorial but is for developers familiar with the concepts to showcase the advantages of Apache Groovy + GUNO Extension for UNO programming.

This documentation’s AsciiDoc source is maintained in the docs folder of the GUNO-Extension Project on GitHub.

Extension Explanation

The term Extension is used in multiple contexts in this document and can cause some confusion. This GUNO-Extension project is compiled as a Jar file and is a Groovy Extension module that extends (adds methods to) other precompiled Java UNO library API jars when used with the Groovy programming language.

The OpenOffice-Groovy extension is an OpenOffice application extension packaged as an OXT file and installed into OpenOffice using the OpenOffice Extension Manager. OpenOffice extensions add functionality to OpenOffice.

Another OpenOffice application extension project is OpenOffice-Groovy-Macros that recreate many of the sample macros included with OpenOffice using the Groovy language.

Getting the Extension

Projects that build using dependencies from artifact repositories like Apache Maven, JCenter, etc. just need to include the latest version as a dependency in the build file or POM.

Gradle Projects

compile group: 'net.codebuilders', name: 'guno-extension', version: '4.1.6.13'

Maven Projects

<dependency>
    <groupId>net.codebuilders</groupId>
    <artifactId>guno-extension</artifactId>
    <version>4.1.6.13</version>
</dependency>

Groovy Scripts

Groovy scripts can declare a Grab dependency at the beginning before import statements.

@Grapes(
    @Grab(group='net.codebuilders', module='guno-extension', version='4.1.6.13')
)

OpenOffice Macros

OpenOffice doesn’t support Groovy macros by default. A companion project OpenOffice-Groovy is an OpenOffice Extension that adds Groovy as a macro language using the OpenOffice Scripting Framework.
Macros written in Groovy do not need to include a guno-extension dependency as the guno-extension jar file is packaged with the openoffice-groovy extension.

The OpenOffice-Groovy extension is not required to use Groovy for standalone client applications or OpenOffice Extensions. It is only required for AOO macros written in Groovy.

Groovy Syntax

There are many advantages to using the Groovy language over pure Java and most can be found in the Apache Groovy documentation.

Casting

While Java requires us to cast an Object to a type like when using the queryInterface method, Groovy does the casting automatically. We also don’t need line-ending semicolons.

Java
XIndexAccess xIndexSheets = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSheets);
Groovy
XIndexAccess xIndexSheets = UnoRuntime.queryInterface(XIndexAccess.class, xSheets)

Static and Dynamic Types

While Groovy works great as a dynamic language where we can declare variables with the def keyword, I prefer to declare types most of the time. This is only my preference and not a requirement.

Property Access

When Object properties have getter and setter methods to use, Groovy allows what appears to be property access while actually using the get and set methods.

Java
XSpreadsheets xSheets = myDoc.getSheets();
Groovy
XSpreadsheets xSheets = myDoc.sheets

String Interpolation

Groovy has a feature called String Interpolation where a variable is replaced with it’s string value upon evaluation of the string by enclosing the variable as shown.

Java
String str = "My String";
println("This is " + str);
Groovy
String str = "My String"
println "This is ${str}"

Both output This is My String

Macro Development Tip

If you start OpenOffice from the command line you can see stdout and stderr message output when using println() statements.

Using the Extension

The best way to explain the differences between the Java UNO API’s and using Groovy with and without the extension is with some example code. Many of the examples are spreadsheet examples are from SCalc.java that is included with the AOO SDK.

Import Statement

To use the UnoExtension you need to add to your imports section.

Add the UnoExtension to imports
import org.openoffice.guno.UnoExtension

Get an XComponentLoader

One of the first objects we need in AOO development after we bootstrap the office and acquire an XComponentContext is an XComponentLoader we can use to load a document. This requires a few steps as shown:

Groovy (begins with a XComponentContext xComponentContext reference)
XMultiComponentFactory mxRemoteServiceManager = null
    XComponentLoader aLoader = null
    mxRemoteServiceManager = xComponentContext.getServiceManager()
    aLoader = UnoRuntime.queryInterface(
        XComponentLoader.class, mxRemoteServiceManager.createInstanceWithContext(
        "com.sun.star.frame.Desktop", self))

The GUNO Extension adds a convenience method to XComponentContext
XComponentLoader getComponentLoader() to return an XComponentLoader object.

GUNO Extension
XComponentLoader aLoader = xComponentContext.componentLoader

UnoRuntime.queryInterface

A common task in OpenOffice development with Java is using the static UnoRuntime.queryInterface() method to get an Interface reference from another within the same Service object. The UNO concepts of Services and Intefaces are beyond the scope of this guide, but you can get more information in the AOO Development Guide.

The UnoRuntime.queryInterface(ReturnObject.class, FromObject) method can be replaced with the new FromObject.guno(ReturnObject.class) method.

Java (begins with a XSpreadsheetDocument myDoc reference)
XSpreadsheets xSheets = myDoc.getSheets();
XIndexAccess xIndexSheets = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSheets);
xSheet = (XSpreadsheet) UnoRuntime.queryInterface(XSpreadsheet.class, xIndexSheets.getByIndex(0));
GUNO Extension
XSpreadsheets xSheets = myDoc.sheets
XIndexAccess xIndexSheets = xSheets.guno(XIndexAccess.class)
xSheet = xIndexSheets.getByIndex(0).guno(XSpreadsheet.class)

Property Access

An UNO object must offer its properties through interfaces that allow you to work with properties. The most basic form of these interfaces is the interface com.sun.star.beans.XPropertySet .

In XPropertySet, two methods carry out the property access: Object getPropertyValue(String propertyName) and void setPropertyValue(String propertyName, Object propertyValue).

The GUNO Extension adds two special methods to XPropertySet: Object getAt(String propertyName) and void putAt(String propertyName, Object propertyValue).

Example: Set the CellStyle of a spreadsheet Cell xCell.

Java
XPropertySet xCellProps = (XPropertySet)UnoRuntime.queryInterface(XPropertySet.class, xCell);
xCellProps.setPropertyValue("CellStyle", "Result");
GUNO Extension
XPropertySet xCellProps = xCell.guno(XPropertySet.class)
xCellProps.putAt("CellStyle", "Result")

These special methods allow a shorthand version to getAt() and putAt() using Groovy Subscript Operator notation. This can Get or Set properties depending on which side of the assigment it’s on.

GUNO Extension using the Subscript operator for assignment.
xCellProps["CellStyle"] = "Result"

See below for an even faster method to set Cell Properties.

Spreadsheet By Index and Name

The GUNO Extension adds a method to XSpreadsheetDocument that returns the XSpreadsheet by the index position saving the steps of getting the XIndexAccess enumeration of sheets and then getting the sheet by index. Likewise there is a method that uses the sheet name to get the sheet. XSpreadsheet getSheetByIndex(Integer nIndex) and XSpreadsheet getSheetByName(String name).

The example leaves out the try/catch for brevity and assumes we have a reference to XSpreadsheetDocument myDoc

Java
XSpreadsheets xSheets = myDoc.getSheets();
XIndexAccess xIndexSheets = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSheets);
xSheet = (XSpreadsheet) UnoRuntime.queryInterface(XSpreadsheet.class, xIndexSheets.getByIndex(0));
GUNO Extension
XSpreadsheet xSheet = myDoc.getSheetByIndex(0)

From this point on, the examples are Groovy without and then with the GUNO Extension.

Index Access

The GUNO Extension adds a special getAt(int index) method to XIndexAccess that allows the Groovy Subscript operator to be used.

This first example will ignore that we already have a method to get a sheet by index from a spreadsheet document to highlight the the Subscript operator with XIndexAccess xIndexAccess[0] instead of xIndexAccess.getByIndex(0).

Example: Set the active sheet.

With Extension (begin with an XSpreadsheetDocument xSpreadsheetDocument reference)
XSpreadsheets xSheets = xSpreadsheetDocument.sheets
XIndexAccess xIndexAccess = xSheets.guno(XIndexAccess.class)
xSheet = xIndexAccess[0].guno(XSpreadsheet.class)
XController xController = xModel.currentController
XSpreadsheetView xSpreadsheetView = xController.guno(XSpreadsheetView.class)
xSpreadsheetView.activeSheet = xSheet
GUNO Extension (shorter version)
xSheet = xSpreadsheetDocument.getSheetByIndex(0)
XController xController = xModel.currentController
XSpreadsheetView xSpreadsheetView = xController.guno(XSpreadsheetView.class)
xSpreadsheetView.activeSheet = xSheet

Cell Contents

The GUNO Extension adds getters and setters for cell Formulas (text) and Values (numeric) to XCellRange. This allows you to get or set the contents of a cell by it’s position in a XCellRange, XSheetCellRange, or XSpreadsheet depending on which Interface you use.
The methods are:
String getFormulaOfCell(int column, int row)
void setFormulaOfCell(int column, int row, String value)
Double getValueOfCell(int column, int row)
void setValueOfCell(int column, int row, float value)

Without Extension (begins with an XSpreadsheet xSpreadsheet reference)
XCellRange xCellRange = UnoRuntime.queryInterface(XCellRange.class, xSpreadsheet)
xCell = xCellRange.getCellByPosition(2,2)
XText xCellText = UnoRuntime.queryInterface(XText.class, xCell)
xCellText.setString("Quotation")
With Extension
xSpreadsheet.setFormulaOfCell(2,2, "Quotation")

Cell Address

A CellAddress object allows access to the column and row address of a cell. Normally you need to get a XCellAddressable object using an XCell reference to get a CellAddress object.
The GUNO Extension adds a CellAddress getAddress() method to XCell to get the address directly.

Example: Get the address of a cell and print it.

Without Extension (begins with an XCell xCell reference)
XCellAddressable xCellAddressable = UnoRuntime.queryInterface(XCellAddressable.class, xCell)
CellAddress cellAddress = xCellAddressable.getCellAddress()
println("Cell Address: column ${cellAddress.Column}, row ${cellAddress.Row}")
With Extension
CellAddress cellAddress = xCell.address
println("Cell Address: column ${cellAddress.Column}, row ${cellAddress.Row}")

Cell Style

The extension adds getter and setter methods for CellStyle to XCell:
Object getCellStyle() and void setCellStyle(Object value) allowing what looks like property access to the CellStyle property.

Example: Set the cell style to "Result":

Without Extension
XPropertySet xCellProps = UnoRuntime.queryInterface(XPropertySet.class, xCell)
xCellProps.setPropertyValue("CellStyle", "Result")
With extension
xCell.cellStyle = "Result"

Example: Get the style as a String:

With Extension
String style = xCell.cellStyle

CellVertJustify Enum

The extension adds getter and setter methods to XCell allowing what looks like property access to vertJustify and use the CellVertJustify enum types.
Integer getVertJustify() and void setVertJustify(Object value).

Without Extension
xCellProps.setPropertyValue("VertJustify", com.sun.star.table.CellVertJustify.TOP)
With Extension
xCell.vertJustify = com.sun.star.table.CellVertJustify.TOP

Cell Ranges

The GUNO Extension adds a method to XSpreadsheet to get the the cell ranges that match certain types.:
XSheetCellRanges getCellRanges(Object type) where type is one or a combination of CellFlag constants added together.

Without Extension
XCellRangesQuery xCellQuery = UnoRuntime.queryInterface(XCellRangesQuery.class, xSpreadsheet)
XSheetCellRanges xFormulaCells = xCellQuery.queryContentCells((short)CellFlags.FORMULA)
With Extension
XSheetCellRanges xFormulaCells = xSpreadsheet.getCellRanges(CellFlags.FORMULA)

Cell Iteration

Normally we start with a Cell Range and get an XEnumerationAccess, and from that an XEnumeration and use it iterate through Cells.

Without Extension
XEnumerationAccess xFormulas = xFormulaCells.getCells()
XEnumeration xFormulaEnum = xFormulas.createEnumeration()
while (xFormulaEnum.hasMoreElements()) {
    Object formulaCell = xFormulaEnum.nextElement()
    xCell = UnoRuntime.queryInterface(XCell.class, formulaCell)
    XCellAddressable xCellAddress = UnoRuntime.queryInterface(XCellAddressable.class, xCell)
    println("Formula cell in column " +
        xCellAddress.getCellAddress().Column + ", row " + xCellAddress.getCellAddress().Row
        + " contains " + xCell.getFormula())
}

The GUNO Extension adds a List<XCell> getCellList() method to both XSheetCellRanges and XSheetCellRangeContainer to get a List of cells to iterate over.

Using the List we can iterate through each cell in a Groovy Closure.

With Extension
XCell[] cellList = xFormulaCells.cellList
cellList.each() { cell ->
    println("Formula cell in column ${cell.address.Column}, " +
    "row ${cell.address.Row} contains ${cell.formula}")
}

RangeContainer

Range Containers hold Cell Ranges. XSheetRangeContainer provides methods to access cell ranges in a collection via index and to add and remove cell ranges.

Example: Create a new cell range container, add all cells that are filled, and iterate through them.
XCellRangesQuery queryContentCells() takes a short but CellFlags are a long (1023 is the total of all CellFlag constants)

Without Extension (begins with an XSpreadsheet xSpreadsheet reference)
XCellRangesQuery xCellQuery = UnoRuntime.queryInterface(XCellRangesQuery.class, xSpreadsheet)
XSheetCellRanges xCellRanges = xCellQuery.queryContentCells((short) 1023)
com.sun.star.lang.XMultiServiceFactory xDocFactory = UnoRuntime.queryInterface(com.sun.star.lang.XMultiServiceFactory.class, xSpreadsheetDocument)
com.sun.star.sheet.XSheetCellRangeContainer xRangeCont = UnoRuntime.queryInterface(com.sun.star.sheet.XSheetCellRangeContainer.class,
    xDocFactory.createInstance("com.sun.star.sheet.SheetCellRanges"))
xRangeCont.addRangeAddresses(xCellRanges.rangeAddresses, false)
println("All filled cells: ")
com.sun.star.container.XEnumerationAccess xCellsEA = xRangeCont.getCells()
com.sun.star.container.XEnumeration xEnum = xCellsEA.createEnumeration()
while (xEnum.hasMoreElements()) {
    Object aCellObj = xEnum.nextElement()
    xCell = UnoRuntime.queryInterface(XCell.class, aCellObj);
    com.sun.star.sheet.XCellAddressable xAddr = UnoRuntime.queryInterface(com.sun.star.sheet.XCellAddressable.class, aCellObj)
    com.sun.star.table.CellAddress cellAddress = xAddr.getCellAddress()
    println("Formula cell in column ${cellAddress.Column}, row ${cellAddress.Row} contains ${xCell.formula}")
}

The GUNO Extension adds a XSheetCellRangeContainer getRangeContainer() method to XSpreadsheetDocument that returns an XSheetRangeContainer.

With Extension and using a Closure to iterate over
XSheetCellRangeContainer xRangeCont = xSpreadsheetDocument.rangeContainer
XSheetCellRanges xCellRanges = xSpreadsheet.getCellRanges(1023)
xRangeCont.addRangeAddresses(xCellRanges.rangeAddresses, false)
XCell[] cellList = xRangeCont.cellList
println("All filled cells: ")
cellList.each() { cell ->
    println("Formula cell in column ${cell.address.Column}, row ${cell.address.Row} contains ${cell.formula}")
}

Graphical User Interfaces

Message Box

UNO provides a XMessageBox to display UI messages to the user. There a a number of steps to get from an XComponentContext to displaying a message.

There are standard MessageBoxType enums used depending on the icon and MessageBoxButton constants for button combinations displayed. the INFOBOX type is different in that it will ignore the button parameter and use BUTTONS_OK and display a single OK button.

Without Extension (begins with an XcomponentContext xContext reference)
XMultiComponentFactory xMCF = xContext.getServiceManager()
XDesktop xDesktop = xMCF.createInstanceWithContext("com.sun.star.frame.Desktop", xContext)
XFrame xFrame = xDesktop.getCurrentFrame()
Object oToolkit = xMCF.createInstanceWithContext("com.sun.star.awt.Toolkit", xContext)
XMessageBoxFactory xMessageBoxFactory = UnoRuntime.queryInterface(XMessageBoxFactory.class, oToolkit)
XWindow xWindow = xFrame.getContainerWindow()
XWindowPeer xWindowPeer = UnoRuntime.queryInterface(XWindowPeer.class, xWindow)

XMessageBox xMessageBox = xMessageBoxFactory.createMessageBox(xWindowPeer,
    MessageBoxType.INFOBOX, MessageBoxButtons.BUTTONS_OK,
    "Window Title", "This in an informative mesage...")

short infoBoxResult = xMessageBox.execute()

The GUNO extension adds two methods to XComponentContext that return a XMessageBox:
XMessageBox getMessageBox(MessageBoxType type, Integer buttons, String message) that uses a default window title of "soffice" and
XMessageBox getMessageBox(MessageBoxType type, Integer buttons, String message, String title) that includes a title parameter.

With Extension (Info Box example using default title)
XMessageBox infoBox = xContext.getMessageBox(MessageBoxType.INFOBOX,
    MessageBoxButtons.BUTTONS_OK, "This in an informative mesage...")

short infoBoxResult = infoBox.execute()
infobox
Figure 1. Info Box
With Extension (Warning Box example with title and default okay button and a cancel button)
String warnMsg = "This is a warning mesage...\nYou should be careful."
Integer warnButtons = MessageBoxButtons.BUTTONS_OK_CANCEL +  MessageBoxButtons.DEFAULT_BUTTON_OK
XMessageBox warningBox = xContext.getMessageBox(MessageBoxType.WARNINGBOX,
    warnButtons, warnMsg, "Warning Title")

short warnBoxResult = warningBox.execute()
warningbox
Figure 2. Warning Box

Groovy SwingBuilder

If you’re a Java developer you’ve probaly heard of Swing with was Java’s 2nd generation UI Toolkit. Many developers have ran into threading issues leading to blocked and unresponsive UI’s if Swing wasn’t used correctly.

Groovy includes many examples of the Builder pattern which are examples of a Domain Specific Language (DSL) focused on hierarchal structures. Builders are great for declaring content like HTML, XML, and in this case Swing. An added bonus is it takes care of handling the threading for you.

Macro Development Tip

The GUNO Extension is not involved in using SwingBuilder with UNO, only Groovy which is included with the OpenOffice-Groovy extension that adds Groovy as a macro language to OpenOffice or using Groovy in a AOO Client application.

This simple example comes straight from the Groovy Swing page. I modified the size dimension of the frame and added the return Integer at the end. Otherwise it runs as a AOO marco.

Simple SwingBuilder Example
import groovy.swing.SwingBuilder
import java.awt.BorderLayout as BL

count = 0
new SwingBuilder().edt {
  frame(title: 'Frame', size: [150, 80], show: true) {
    borderLayout()
    textlabel = label(text: 'Click the button!', constraints: BL.NORTH)
    button(text:'Click Me',
         actionPerformed: {count++; textlabel.text = "Clicked ${count} time(s)."; println "clicked"}, constraints:BL.SOUTH)
  }
}

// Groovy OpenOffice scripts should always return 0
return 0
swingbuilder 1
Figure 3. After launch
swingbuilder 2
Figure 4. After first button click