Adding a new UNO command

What are UNO commands, and why would you need them? If you want to add some feature to the LibreOffice UI, you may need to add or modify a UNO command, so it would be help much to learn more about these commands, and in general, the dispatch framework. So, stay tuned!

UNO Dispatch Framework and UNO Commands

The UNO dispatch framework is an internal mechanism of LibreOffice to dispatch the commands from the UI to the actual code that handles it, and also send data back to the user interface. For example, a dispatch command updates the list of recent files in the menu after you save a file.

Let’s considering a single action like loading or saving a file. This action is available from several places in the UI, from the menu bar to the toolbar, and possibly other places.

The UNO dispatch command is a text in the form of .uno:About, and its name implies what it does. This specific command opens the “About LibreOffice” dialog. As another example, .uno:Quit, make LibreOffice application shut down. These commands do not take arguments, but there are other commands that take some argument, or return useful data.

Dispatching UNO Commands

As described in the dispatch Framework section of the LibreOffice getting started documentation, “the command is sent to the document frame and this passes on the command until an object is found that can handle the command.”

There is a big list of many commands currently available in the LibreOffice:

This list is generated and updated regularly using the bin/list-dispatch-commands.py script inside LibreOffice core source, that scans the source code. This description is from bin/list-dispatch-commands.py‘s documentation:

3 types of source files are scanned to identify and describe a list of relevant UNO commands:

  • hxx files: containing the symbolic and numeric id’s, and the respective modes and groups
  • .xcu files: containing several English labels as they appear in menus or tool-tips
  • .sdi files: containing a list of potential arguments for the commands, and their types

Invoking UNO Commands

There are several ways to test a UNO command. One is to use internal Basic language. For example, using the command .uno:SheetRightToLeft with this BASIC code snippet, it is possible to reverse the the direction of an open Calc document.

Sub Main
  document   = ThisComponent.CurrentController.Frame
  dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
  dim args(0) as new com.sun.star.beans.PropertyValue
  args(0).Name = "RightToLeft"
  args(0).Value = "true"
  dispatcher.executeDispatch(document, ".uno:SheetRightToLeft", "", 0, args())
End Sub

You can edit and run this script from the menu item “Tools > Macros > Edit Macros…”.

Creating a New UNO Command

Let’s consider a real world example. Recently, Rafael Lima raised the good point that we need a “duplicate sheet” option in the Calc. The idea was discussed in the UX/design meeting, and the decision was that this option would be good, and we need to introduce a .uno:DuplicateSheet command. In this way, one can invoke the command from the tab bar, or elsewhere.

The starting point for adding such a command was to take a look at the currently implemented commands. The .uno:Move is a command invoked from the tab bar, and has many similar properties.

More information about this command, and also other Calc commands is available here:

Looking carefully, we can find this information:

Dispatch command .uno:move
Description Move or Copy Sheet…
Group Edit
Arguments DocName (string)
Index (integer)
Copy (bool)
UseCurrentDocument (bool)
Internal name (value) FID_TAB_MOVE (26358)
Mode AM
Source files XCU SDI HXX

We use this legend to understand the mode column in the Wiki:

  • U : Autoupdate, M : MenuConfig, T : ToolboxConfig, A : AccelConfig

Every command has its own internal name and value. As you can see, the internal name is FID_TAB_MOVE, and its value is 26358. One should not use the values directly, and there is an ongoing EasyHack effort to remove small parts of the code that use them directly.

Adding Code for a UNO Command

Looking for the FID_TAB_MOVE with grep command:

git grep FID_TAB_MOVE

We understand that it is in these files:

sc/inc/sc.hrc
sc/sdi/docsh.sdi
sc/sdi/scalc.sdi
sc/source/ui/view/tabcont.cxx
sc/source/ui/view/tabvwshf.cxx

In fact, one can go on and start from copying parts of code for the move command, and then try to be specific on this command. As you may see, the ScViewFunc::MoveTable() is invoked in the switch case inside tabvwshf.cxx.

Looking for the .uno:Move:

git grep -Fw .uno:Move |grep -v .py

will lead to these files:

officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu
sc/uiconfig/scalc/menubar/menubar.xml
sc/uiconfig/scalc/popupmenu/sheettab.xml

Now it is obvious that we should define FID_TAB_DUPLICATE and .uno:DuplicateSheets. The 2 XML files are related to menu bar and tab bar. We will change them in the next section.

To add the new command, many files should be edited, but by following the definition of .uno:Move, finding the files to change would not be much difficult.

The new command is defined in the officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu, much similar to the part for .uno:Move:

      <node oor:name=".uno:DuplicateSheet" oor:op="replace">
        <prop oor:name="Label" oor:type="xs:string">
          <value xml:lang="en-US">Duplicate Sheet</value>
        </prop>
        <prop oor:name="Properties" oor:type="xs:int">
          <value>1</value>
        </prop>
      </node>

Then, in inc/sc.hrc, we add this line right after FID_TAB_EVENTS and before TAB_POPUP_END. Of course, we should make sure that we do not create a collision when we are adding +11.

#define FID_TAB_DUPLICATE       (TAB_POPUP_START+11)

And in sc/sdi/docsh.sdi, we add this line which is essentially similar with other definitions:

   FID_TAB_DUPLICATE        [ ExecMethod = ExecuteTable; StateMethod = GetStateTable; ]

And in sc/sdi/scalc.sdi, we add this definition which is mostly similar to other definitions, but without any parameters, as the command does not take any parameters:

SfxVoidItem DuplicateSheet FID_TAB_DUPLICATE
()
[
    AutoUpdate = FALSE,
    FastCall = FALSE,
    ReadOnlyDoc = TRUE,
    Toggle = FALSE,
    Container = FALSE,
    RecordAbsolute = FALSE,
    RecordPerSet;

    AccelConfig = TRUE,
    MenuConfig = TRUE,
    ToolBoxConfig = FALSE,
    GroupId = SfxGroupId::Edit;
]

Also in sc/source/ui/view/tabcont.cxx, we add this part which is similar to previous definitions:

    rBind.Invalidate( FID_TAB_DUPLICATE );

The main part of code is in sc/source/ui/view/tabvwshf.cxx, which is partly similar to the code that is done by the .uno:Move command:

        case FID_TAB_DUPLICATE:
            {
                // Get info about current document and selected tab
                SCTAB nTab = rViewData.GetTabNo();
                OUString aDocName = GetViewData().GetDocShell()->GetTitle();
                sal_uInt16 nDoc = 0;
                bool bCpy = true;

                SfxObjectShell* pSh = SfxObjectShell::GetFirst();
                ScDocShell* pScSh = nullptr;
                sal_uInt16 i = 0;

                // Determine the index of the current document
                while ( pSh )
                {
                    pScSh = dynamic_cast<ScDocShell*>( pSh );

                    if( pScSh )
                    {
                        pScSh->GetTitle();

                        if (aDocName == pScSh->GetTitle())
                        {
                            nDoc = i;
                            break;
                        }
                        // Only count ScDocShell
                        i++;
                    }
                    pSh = SfxObjectShell::GetNext( *pSh );
                }

                MoveTable( nDoc, nTab + 1, bCpy );
            }
            break;

Then, we add this part in the same .cxx file, which is similar to the parts from other commands:

            case FID_TAB_DUPLICATE:
                if (   !rDoc.IsDocEditable()
                    || rDoc.GetChangeTrack() != nullptr
                    || nTabCount > MAXTAB)
                    rSet.DisableItem( nWhich );
                break;

This would complete the creation of the new command. The next step would be using this new command.

Editing Menus, Tabs and Other UI Elements

To be able to use the defined UNO command, you should also change the UI elements. This specific command is invoked from the menu bar and also tab bar, so just like the .uno:Move, .uno:Duplicate sheet should be added to the sc/uiconfig/scalc/menubar/menubar.xml (menu bar):

  <menu:menuitem menu:id=".uno:DuplicateSheet"/>

and also sc/uiconfig/scalc/popupmenu/sheettab.xml (tab bar):

  <menu:menuitem menu:id=".uno:DuplicateSheet"/>

Final Result

The code is actually written by the LibreOffice community member Rafael Lima, and it is now merged in LibreOffice:

https://git.libreoffice.org/core/commit/d519cbe89268ff24e2b8b05ba5124dca98f22b79

You can compile the LibreOffice from the latest git sources to see this change in action!

Duplicate command in Calc Duplicate command in Calc

Additional Learning Materials

Fore a lengthy description of how to edit UI elements, this 2 part talk from Katarina Behrens can be informative:

Part 1: How to Make LibreOffice UI Elements Move (PDF)

Please accept YouTube cookies to play this video. By accepting you will be accessing content from YouTube, a service provided by an external third party.

YouTube privacy policy

If you accept this notice, your choice will be saved and the page will refresh.

 

Part 2: UI Hacking explained, episode 2: Revenge of sfx2 dispatch API (PDF)

Please accept YouTube cookies to play this video. By accepting you will be accessing content from YouTube, a service provided by an external third party.

YouTube privacy policy

If you accept this notice, your choice will be saved and the page will refresh.