LibreOffice Javascript

There are other languages in which you can write LibreOffice macros. One of them is Javascript. If you’ve installed the package “libreoffice-script-provider-js”, you can write Javascript macros. In Linux’ you can use apt-get or rpm to install it.

Javascript macros can be edited from LibreOffice, and is ready to run without compilation.

.In the Developer Guide, you will find how to write macros in Java. Those example can be easily translated to Javascript. You can even implement interfaces and extend classes. For example, you can override an “actionPerformed” method of a button, as shown in the previous postLibreOffice – The Kakuro Cell Macro.

LibreOffice provides you with a friendlier way to create dialogs:

1. Tools=>Macros=>Organize Dialogs.

2. Edit an existing dialog or a new one.

3. If you added a control, such as a button, double-click it. a window is opened. In the General Tab, you can get or change information such as the name (important if you want to use it in the macro), color, type (“Ok” button, for example), etc. In the Event tab, you can assign macros to events.

The dialog can be created and executed by a macro. Following is an example in LibreOffice Basic:

Sub kakuro_cell
DialogLibraries.LoadLibrary( “Standard” )

oDialog1 = CreateUnoDialog( DialogLibraries.Standard.kakuro )

End Sub

This will show the following dialog:

Clicking “Submit” will run another macro with the event details, and the “Button type” selected in the general tab was “OK”. We can also start our macro by pressing the “tab” key until our button gains focus and then pressing the Enter key, thus the event starting the macro is “Item status change”.

Now, what does event handling look like in a Javascript macro:

 event=ARGUMENTS[0];      // Because this macro is a callback, it has the event for first argument.
 evtSource=event.Source;       // The control that fired the event.
 xControl=UnoRuntime.queryInterface(XControl, evtSource);
 xControlModel = xControl.getModel();
 xPropertySet = UnoRuntime.queryInterface(XPropertySet, xControlModel);
 peer=xControl.getPeer();       // The dialog window.
 ctxControlContainer=UnoRuntime.queryInterface(XControlContainer, ctx);  // Yes, the window is a control container that contains the button and input fields.
 lowerPartControl = ctxControlContainer.getControl("lowerPart");
 upperPartControl = ctxControlContainer.getControl("upperPart");

How to Translate from Java to Javascript?

The interpreter used by the script provider is named Rhino, an interpreter developed by Mozilla.  Rhino is written entirely in Java, and allows developers to embed Java objects within their code.


To embed a Java object, you should first import it using the command “importClass(Packages.<class-name>);”.

For example:



In Javascript, the first command executed is not found inside a function, so instead of an argument of class XScriptContext, a variable named XSCRIPT CONTEXT is supplied upon invocation of a Javascript macro.

Java Objects

Java objects are accessed the same way in both Java and Javascript, but without the casting in the latter because Javascript is loosely type. The “new” command in Javascript has the same syntax as in Java, except for instantiating a Java array.

Instantiating a Java Array

A Javascript array containing Java objects is not a Java array. To instantiate a Java array, you should use class “java.lang.reflect.Array”. This class and other classes from the “reflect” package should not be imported.

To instantiate an array use the function “newInstance(<class>, <integer array>);”. For example:
points = java.lang.reflect.Array.newInstance(Point, [1, 3]);
Will create a bi-dimensional array with one row and 3 columns of Point objects.

Implementing an Interface or Extending a Class

Use the syntax:

<var> = new <class or interface>(<args>){
      <func-name>: function(<args>){

For example:

xButton.addActionListener(new XActionListener() {
     disposing: function(evtObj) {
        // TODO Auto-generated method stub
     actionPerformed: function(evt){
        // Javascript code


LibreOffice – The Kakuro Cell Macro

This macro is to be run from the Calc program.

How does it work?

When the user starts the macro, a dialog is opened:

The user insets values for the upper and lower triangles, and clicks the submit button.

Then the macro draws two triangles in the cell selected. Each triangle is empty or contains a numeric value.

The program uses 3 main components:

  • The spreadsheet document.
  • dialog.
  • The draw-page part of the document, since graphics are not part of the cell contents. So, you would use a draw page to draw shapes.

The Code

This macro will create the dialog, add controls, and execute the dialog. A callback function will be attached to the submit button, and will draw the triangles.

Imports are not included; A link to the relevant data type will replace the import.

 // Define the triangle type in the cell
 enum TriangleType {

 public class KakuroCell {
   public XNameContainer m_xDlgModelNameContainer = null; // Allows to access controls by name.
   public XControlContainer m_xDlgContainer = null;       // The dialog contains controls, such as buttons, input fields, etc.
   public XMultiServiceFactory m_xMSFDialogModel = null;
   public XControl m_xDialogControl = null;
   public XTopWindow m_xTopWindow;
   public XComponentContext m_xContext;
   public XModel m_doc;
   public XSpreadsheetDocument m_spreadSheetDoc;
   public XMultiServiceFactory doc_multiServiceFactory;

      private void createDialog(XMultiComponentFactory _xMCF) {
      try {
          Object oDialogModel = _xMCF.createInstanceWithContext("", m_xContext);

          // The XMultiServiceFactory of the dialog model is needed to instantiate the controls...
          m_xMSFDialogModel = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, oDialogModel);

          // The named container is used to insert the created controls into...
          m_xDlgModelNameContainer = (XNameContainer) UnoRuntime.queryInterface(XNameContainer.class, oDialogModel);
          String[] sPropertyNames = new String[] {"Height", "Moveable", "Name","PositionX","PositionY", "Step", "TabIndex","Title","Width"};

          Object[] oObjectValues = new Object[] { new Integer(129), Boolean.TRUE, "KakuroDialog", new Integer(95),new Integer(100), new Integer(1), new Short((short) 0), "Kakuro Cell", new Integer(149)};

          setPropertyValues(sPropertyNames, oObjectValues);

          // create the dialog...
          Object oUnoDialog = _xMCF.createInstanceWithContext("", m_xContext);
          m_xDialogControl = (XControl) UnoRuntime.queryInterface(XControl.class, oUnoDialog);

          // The scope of the control container is public...
          m_xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, oUnoDialog);

          m_xTopWindow = (XTopWindow) UnoRuntime.queryInterface(XTopWindow.class, m_xDlgContainer); 

          // link the dialog and its model...
          XControlModel xControlModel = (XControlModel) UnoRuntime.queryInterface(XControlModel.class, oDialogModel);

      } catch ( exception) {

      // Define the dialog at the model - keep in mind to pass the property names in alphabetical order!
      public XMultiComponentFactory m_xMCF;
      public XWindowPeer m_xWindowPeer;     // This will ensure a dialog is opened.

      public void setPropertyValues(String[] PropertyNames, Object[] PropertyValues){
          XMultiPropertySet xMultiPropertySet = (XMultiPropertySet) UnoRuntime.queryInterface(XMultiPropertySet.class, m_xDlgModelNameContainer);
          xMultiPropertySet.setPropertyValues(PropertyNames, PropertyValues);
      } catch ( ex) {

      public void insertSubmitButton(String name, String label, short tabIndex, int posX, int posY, int width, int height){
          try {
              Object oSubmitButton = m_xMSFDialogModel.createInstance("");
                String[] sPropertyNames= new String[]{ "BackgroundColor", "DefaultButton", "Height", "Label", "Name",   "PositionX", "PositionY", "PushButtonType", "TabIndex", "Toggle",  "Width"};
                Object [] oObjectValues=new Object[]{ new Integer(0x006e7f50), Boolean.FALSE, new Integer(height), label,  name,   new Integer(posX), new Integer(posY), new Short((short), new Short(tabIndex), Boolean.TRUE, new Integer(width)};
                XMultiPropertySet xSBModelMPSet = (XMultiPropertySet) UnoRuntime.queryInterface(XMultiPropertySet.class, oSubmitButton);
                xSBModelMPSet.setPropertyValues(sPropertyNames, oObjectValues);
                m_xDlgModelNameContainer.insertByName(name, xSBModelMPSet);
                XControl xButtonControl = m_xDlgContainer.getControl(name);
                XButton  xButton = (XButton) UnoRuntime.queryInterface(XButton.class, xButtonControl);
                xButton.addActionListener(new XActionListener() {

                    public void disposing(EventObject evtObj) {
                        // TODO Auto-generated method stub


                    public void actionPerformed( evt) {
                        // TODO Auto-generated method stub
                        try {
                          Object src=evt.Source;
                          XControl xControl=UnoRuntime.queryInterface(XXDialogControl.class, src);
                          Object controlModel=xControl.getModel();
                          XPropertySet ctlPropertySet=UnoRuntime.queryInterface(XPropertySet.class, controlModel);
                          String name=(String)ctlPropertySet.getPropertyValue("Name");
                            if (name.equals("Submit")){
                                ctlPropertySet.setPropertyValue("Enabled", Boolean.FALSE);
                                XControl upperCtl=m_xDlgContainer.getControl("UpperValue");
                                XControlModel upperModel = upperCtl.getModel();
                                XPropertySet upperPropertySet = UnoRuntime.queryInterface(XPropertySet.class, upperModel);
                                XControl lowerCtl=m_xDlgContainer.getControl("LowerValue");
                                XControlModel lowerModel = lowerCtl.getModel();
                                XPropertySet lowerPropertySet = UnoRuntime.queryInterface(XPropertySet.class, lowerModel);
                                String upperText = (String)upperPropertySet.getPropertyValue("Text");
                                String lowerText = (String)lowerPropertySet.getPropertyValue("Text");

                                int upperNumericValue = 0;
                                int lowerNumericValue = 0;
                                try {
                                } catch (NumberFormatException nfe) {
                                    // Leave the numeric value zero

                                try {
                                } catch (NumberFormatException nfe) {
                                    // Leave the numeric value zero

                                draw_cell (upperNumericValue, lowerNumericValue);
                                XDialog xDialog = (XDialog) UnoRuntime.queryInterface(XDialog.class, m_xDialogControl);
                        } catch (Exception e){
          } catch (Exception e){

       // draw_cell - Get the cell's place and size, and draw the 2 triangles over it.
       public void draw_cell (int upperNumericValue, int lowerNumericValue) throws Exception{
          Object xSelection = m_doc.getCurrentSelection();
          XCellRange xCellRange = UnoRuntime.queryInterface(XCellRange.class, xSelection);
          XCell cell=null;
          try {
            cell=xCellRange.getCellByPosition(0, 0);
          } catch (IndexOutOfBoundsException e) {
            // TODO Auto-generated catch block
            throw new Exception("No cells selected.");
          XCellAddressable xCellAddressable=UnoRuntime.queryInterface(XCellAddressable.class, cell);
          CellAddress cellAddress = xCellAddressable.getCellAddress();
          short sheetIx = cellAddress.Sheet;
          XSpreadsheets sheets = m_spreadSheetDoc.getSheets();
          XIndexAccess xIndexAccess = UnoRuntime.queryInterface(XIndexAccess.class, sheets);
          Object currentSheet = xIndexAccess.getByIndex(sheetIx);

          // Get the draw page properties from the spreadsheet
          XDrawPageSupplier xDrawPageSupplier=UnoRuntime.queryInterface(XDrawPageSupplier.class, currentSheet);
          XDrawPage drawPage = xDrawPageSupplier.getDrawPage();
          doc_multiServiceFactory = UnoRuntime.queryInterface(XMultiServiceFactory.class, m_doc);

          XPropertySet cellPropertySet=UnoRuntime.queryInterface(XPropertySet.class, cell);
          // Get the start point and the size of the cell.
          Point pt=(Point)cellPropertySet.getPropertyValue("Position");
          int ptx = pt.X;
          int pty = pt.Y;
          Size sz=(Size)cellPropertySet.getPropertyValue("Size");
          int width = sz.Width;
          int height = sz.Height;
          drawTriangle (drawPage, cell, ptx, pty, width, height, upperNumericValue, TriangleType.UPPER);
          drawTriangle (drawPage, cell, ptx, pty, width, height, lowerNumericValue, TriangleType.LOWER);

      // Draw the triangle according to its coordinates and type, add its numeric value in it.
      private void drawTriangle(XDrawPage drawPage, XCell cell, int ptx, int pty, int width,
            int height, int numericValue, TriangleType triangleType) {
        // TODO Auto-generated method stub
        try {
            Object shapeObj = doc_multiServiceFactory.createInstance("");  // This is the type supported for polygons.
            XShape shape=UnoRuntime.queryInterface(XShape.class, shapeObj);
            Point shapePos=new Point(ptx, pty);
            XPropertySet descriptor=UnoRuntime.queryInterface(XPropertySet.class, shape);
            Point[][] points=new Point[1][3];
            points[0][0]=new Point(ptx, pty);
            points[0][1]=triangleType==TriangleType.LOWER ? 
                    /* Yes */ new Point(ptx, pty + height) :
                    /* No  */ new Point(ptx + width, pty);
            points[0][2]=new Point(ptx+width, pty+height);
            PolygonFlags flags[][] = new PolygonFlags[1][3];
            flags[0][0] = flags[0][1] = flags[0][2] = PolygonFlags.NORMAL;      // All points are normal because the shape contains no curves.
            XText shapetext=UnoRuntime.queryInterface(XText.class, shapeObj);
            PolyPolygonBezierCoords coords=new PolyPolygonBezierCoords();
            coords.Flags = flags;
            coords.Coordinates = points;
            descriptor.setPropertyValue("PolyPolygonBezier", coords);
            String shapeString = numericValue!=0 ? Integer.toString(numericValue) : "";
            XPropertySet textPropertySet = UnoRuntime.queryInterface(XPropertySet.class, shapetext);
            textPropertySet.setPropertyValue("CharHeight", 12.0);
            textPropertySet.setPropertyValue("CharColor", new Integer(0xffffff));
                    triangleType == TriangleType.LOWER ? 
                            /* Yes */ TextHorizontalAdjust.LEFT :
                            /* No  */ TextHorizontalAdjust.RIGHT);
                    triangleType == TriangleType.LOWER ?
                            /* Yes */ TextVerticalAdjust.BOTTOM :
                            /* No  */ TextVerticalAdjust.TOP);
            textPropertySet.setPropertyValue("CharHeightComplex", 12.0);
            descriptor.setPropertyValue("Visible", true);
            descriptor.setPropertyValue("FillColor", new Integer(0));
            descriptor.setPropertyValue("Anchor", cell);
        } catch (Exception e){


    // More dialog controls:
    public void insertNumericField(String name, String label, short tabIndex, int posX, int posY, int width, int height){
          try {
              Object oNumericField = m_xMSFDialogModel.createInstance("");
                String[] sPropertyNames= new String[]{ "Height", "Name",   "PositionX", "PositionY", "TabIndex", "Width"};
                Object [] oObjectValues=new Object[]{ new Integer(height), name,   new Integer(posX), new Integer(posY), new Short(tabIndex), new Integer(width)};
                XMultiPropertySet xNFModelMPSet = (XMultiPropertySet) UnoRuntime.queryInterface(XMultiPropertySet.class, oNumericField);
                xNFModelMPSet.setPropertyValues(sPropertyNames, oObjectValues);
                m_xDlgModelNameContainer.insertByName(name, xNFModelMPSet);
          } catch (Exception e){

      public void insertTextlabel(String name, String label, short tabIndex, int posX, int posY, int width, int height){
          try {
              Object oTextLabel = m_xMSFDialogModel.createInstance("");
                String[] sPropertyNames= new String[]{ "Height", "Label", "Name",   "PositionX", "PositionY", "TabIndex", "Width", "WritingMode"};
                Object [] oObjectValues=new Object[]{ new Integer(height), label, name,   new Integer(posX), new Integer(posY), new Short(tabIndex), new Integer(width), new Short(};
                XMultiPropertySet xFTModelMPSet = (XMultiPropertySet) UnoRuntime.queryInterface(XMultiPropertySet.class, oTextLabel);
                xFTModelMPSet.setPropertyValues(sPropertyNames, oObjectValues);
                m_xDlgModelNameContainer.insertByName(name, xFTModelMPSet);
          } catch (Exception e){

      public void insertGroupBox (String id, short tabIndex, int posX, int posY, int width, int height){
          try {
            Object oGroupBox=null;
                oGroupBox = m_xMSFDialogModel.createInstance("");
            String sName="FrameControl1";
            String[] sPropertyNames= new String[]{ "Height", "Name",   "PositionX", "PositionY", "Width", "WritingMode"};
            Object [] oObjectValues=new Object[]{ new Integer(height), sName,   new Integer(posX), new Integer(posY), new Integer(width), new Short(};

            XMultiPropertySet xGBModelMPSet = (XMultiPropertySet) UnoRuntime.queryInterface(XMultiPropertySet.class, oGroupBox);
            xGBModelMPSet.setPropertyValues(sPropertyNames, oObjectValues);
            m_xDlgModelNameContainer.insertByName(sName, xGBModelMPSet);
            XPropertySet xGBPSet = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, oGroupBox);
            xGBPSet.setPropertyValue("Label", "Kakuro Values");
          } catch (Exception e){

      public short executeDialog() throws Exception{
          XWindow xWindow = (XWindow) UnoRuntime.queryInterface(XWindow.class, m_xDlgContainer);
          // set the dialog invisible until it is executed
          Object oToolkit = m_xMCF.createInstanceWithContext("", m_xContext);
          XToolkit xToolkit = (XToolkit) UnoRuntime.queryInterface(XToolkit.class, oToolkit);
          XWindowPeer xWindowParentPeer = xToolkit.getDesktopWindow();
          m_xDialogControl.createPeer(xToolkit, xWindowParentPeer);
          m_xWindowPeer = m_xDialogControl.getPeer();
          XDialog xDialog = (XDialog) UnoRuntime.queryInterface(XDialog.class, m_xDialogControl);
          XComponent xDialogComponent = (XComponent) UnoRuntime.queryInterface(XComponent.class, m_xDialogControl);     
          // the return value contains information about how the dialog has been closed...
          short nReturnValue = xDialog.execute();
          // free the resources...
          return nReturnValue;

      // The entry point: this function will be called, when the user runs the macro.
      public  void start(XScriptContext xScriptContext) throws BootstrapException, Exception{
          m_spreadSheetDoc = UnoRuntime.queryInterface(XSpreadsheetDocument.class, m_doc);
          if (m_spreadSheetDoc == null)
              throw new Exception("Invalid Document Type. Please call from a Calc document.");
          m_xMCF = xScriptContext.getComponentContext().getServiceManager();
          insertGroupBox("abc", (short)0, 16, 20, 112, 80);
          insertTextlabel("Label1", "Upper:", (short)-1, 22, 41, 22, 9);
          insertNumericField("UpperValue", "", (short)1, 55, 40, 56, 12);
          insertTextlabel("Label2", "Lower:", (short)-1, 22, 57, 26, 7);
          insertNumericField("LowerValue", "666", (short)2, 55, 56, 56, 12);
          insertSubmitButton("Submit", "Submit", (short)3, 48, 82, 46, 12);



The Parcel Descriptor

 <?xml version="1.0" encoding="UTF-8"?>
<parcel language="Java" xmlns:parcel="scripting.dtd">
  <script language="Java">
    <locale lang="en">
      <displayname value="Kakuro Cell"/>
        Dialog Excercise
    <functionname value="KakuroCell.start"/>
    <logicalname value="KakuroCell.start"/>
        <prop name="classpath" value="kakuro.jar"/>

LibreOffice – Scripting Your Editor

I think using a spreadsheet document for Sudoku and Kakuro puzzles is great because those documents contain cells where you can place your content: numeric value and text. As you probably know, Kakuro puzzles have squares split by diagonal lines into triangles.

A kakuro puzzle copied to a spreadsheet document

The cells split into triangles are not provided by the office suite and should be crated by the user. The best way to create them is by a macro. The macro can be written in any language that accesses UNO(Universal Network Objects) components. For example: Javascript, Java, BeanShell, Python and Basic.
To learn how to write a HelloWorld script in each language, click here.

Files And Directories

A macro needs an entry point. If your macro is written in Java, it mus have a function with a parameter of type ‘XScriptContext’. The path to function name should be found in a file named ‘parcel-descriptor.xml’. The path includes the package name, the class name, and the function name in the format ‘<pacckage-name>.<class-name>.<function-name>, for example:


The parcel-descriptor should also contain the location of the jar file (a zipped directory containing Java classes).

The parcel descriptor is located in ‘<path>/Scripts/java/<Script Dir>/’.

Path may be one of

  • a user path, such as’ ${HOME}/.config/libreoffice/3/user/’ – for a specific user.
  • a LibreOffice path, such as ‘/usr/lib/libreoffice/share/’         – for all LibreOffice users


The function ‘printHW’ is in the class ‘HelloWorld’ in package hello. The package is stored in “${HOME}/.config/libreoffice/3/user/Scripts/java/HelloWorld1/HelloWorld1.jar”

The file “${HOME}/.config/libreoffice/3/user/Scripts/java/HelloWorld1/parcel-descriptor.xml” will look like:

<parcel language=”Java”>

   <script language=”Java”>

      <locale lang=”en”>

         <displayname value=”HelloWorld1″/>

         <description>Prints “Hello World”.</description>


     <functionname value=”hello.HelloWorld.printHW“/>

     <logicalname value=”HelloWorld.printHW“/>


        <prop name=”classpath” value=”HelloWorld1.jar“/>




Library Files for the Class Path

Your macro will access classes found in JAR files. Some of the jar files can be found in the Java directory (In my Ubuntu 12.04, it is ‘/usr/share/java’) and some in the ‘libreoffice/program/classes’ (‘/usr/lib/libreoffice/program/classes’).

The files are:

  • ridl.jar – in ‘/usr/share/java’
  • unoil.jar – in (‘/usr/lib/libreoffice/program/classes’
  • unoloader.jar – in ‘/usr/share/java’
  • jurt.jar – in ‘/usr/share/java’
  • juh.jar – in ‘/usr/share/java’

Data Types

There are 4 kinds of data types in UNO:

  • Simple and primitive data types, with equivalents in Java, described here.
  • Structures – objects with public attributes, described here
  • Interfaces containing the functions to be used by the programmer, described here.
  • Services which are everything the module (Draw, Writer, etc) provides to the user, a Spreadsheet cell, for example.

The service implements interfaces and other services. To access the service function, get the relevant interface using  ‘UnoRuntime.queryInterface(InterfaceClass, object)’.

To instantiate (or create a Java object from) a service, use the function ‘createInstance’ of the MultiServiceFactory or MultiComponentFactory.

The next post will describe an example macro in java, The Kakuro Cell macro.