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.
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.
XIndexAccess xIndexSheets = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSheets);
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.
XSpreadsheets xSheets = myDoc.getSheets();
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.
String str = "My String";
println("This is " + str);
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 |
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.
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:
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.
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.
XSpreadsheets xSheets = myDoc.getSheets();
XIndexAccess xIndexSheets = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSheets);
xSheet = (XSpreadsheet) UnoRuntime.queryInterface(XSpreadsheet.class, xIndexSheets.getByIndex(0));
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
.
XPropertySet xCellProps = (XPropertySet)UnoRuntime.queryInterface(XPropertySet.class, xCell);
xCellProps.setPropertyValue("CellStyle", "Result");
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.
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
XSpreadsheets xSheets = myDoc.getSheets();
XIndexAccess xIndexSheets = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSheets);
xSheet = (XSpreadsheet) UnoRuntime.queryInterface(XSpreadsheet.class, xIndexSheets.getByIndex(0));
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.
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
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)
XCellRange xCellRange = UnoRuntime.queryInterface(XCellRange.class, xSpreadsheet)
xCell = xCellRange.getCellByPosition(2,2)
XText xCellText = UnoRuntime.queryInterface(XText.class, xCell)
xCellText.setString("Quotation")
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.
XCellAddressable xCellAddressable = UnoRuntime.queryInterface(XCellAddressable.class, xCell)
CellAddress cellAddress = xCellAddressable.getCellAddress()
println("Cell Address: column ${cellAddress.Column}, row ${cellAddress.Row}")
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":
XPropertySet xCellProps = UnoRuntime.queryInterface(XPropertySet.class, xCell)
xCellProps.setPropertyValue("CellStyle", "Result")
xCell.cellStyle = "Result"
Example: Get the style as a String:
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)
.
xCellProps.setPropertyValue("VertJustify", com.sun.star.table.CellVertJustify.TOP)
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.
XCellRangesQuery xCellQuery = UnoRuntime.queryInterface(XCellRangesQuery.class, xSpreadsheet)
XSheetCellRanges xFormulaCells = xCellQuery.queryContentCells((short)CellFlags.FORMULA)
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.
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.
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)
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.
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.
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.
XMessageBox infoBox = xContext.getMessageBox(MessageBoxType.INFOBOX,
MessageBoxButtons.BUTTONS_OK, "This in an informative mesage...")
short infoBoxResult = infoBox.execute()

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()

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

