Working with MenuApp

Working with Class MenuApp.

from ooodev.calc import CalcDoc
from ooodev.utils.kind.menu_lookup_kind import MenuLookupKind

doc = CalcDoc.create_doc(loader=loader, visible=True)
menu = doc.menu[MenuLookupKind.TOOLS] # or .menu[".uno:ToolsMenu"]
itm = menu.items[".uno:AutoComplete"] # or .items[6]

The menu can be accessed via the doc.menu property. The doc.menu is an instance of the Class MenuApp and has access to all the menu items of the current menu bar.

The :py:class`~ooodev.utils.kind.menu_lookup_kind.MenuLookupKind` Enum is for convenience and can be replaced with the command name of the menu.

doc.menu[MenuLookupKind.TOOLS] is the same as doc.menu[".uno:ToolsMenu"].

The menu can be accessed even if the menu is not visible in the LibreOffice Window.

Getting Access to Menus

Accessing a menu is simple when working with a doc.

from ooodev.utils.kind.menu_lookup_kind import MenuLookupKind
from ooodev.calc import CalcDoc
from ooodev.loader import Lo
# ...

loader = Lo.load_office(connector=Lo.ConnectPipe())
doc = CalcDoc.create_doc(loader=loader, visible=True)
# doc.menu contains all the top level menus
tool_menu = doc.menu[MenuLookupKind.TOOLS]
# ...

The MenuLookupKind is for convenience and in this case returns .uno:ToolsMenu.

Index Access

The doc.menu[] index access can take a string or a zero-based index number. doc.menu[0] would give access to the first menu, most likely the File menu.

There is no recursive search in the MenuApp or MenuItem* classes. There is index access via menu position and menu command name.

Usually using the name is more practical as it will find the menu even if the user has reorder it in a different place.

The MenuLookupKind Enum is for convenience and can be replaced with the command name of the menu.

doc.menu[MenuLookupKind.TOOLS] is the same as doc.menu[".uno:ToolsMenu"].

Getting menu items in a menu is basically the same as finding a menu.

Menus has some limits as not all popup menus are actually sub menus. For instance the menu Insert -> Shapes -> Basic Shapes corresponds to the following:

>>> itm = (
>>>     doc.menu[".uno:InsertMenu"]
>>>     .items[".uno:ShapesMenu"]
>>>     .sub_menu.items[".uno:BasicShapes"]
>>> )
>>> repl(itm)
'<MenuItem(command=".uno:BasicShapes", kind=MenuItemKind.ITEM)>'

Although there is a popup menu for Insert -> Shapes -> Basic Shapes it is reported as a MenuItem and not a MenuItemSub. This by design because .uno:BasicShapes has a popup menu but the popup is not really a submenu. This is also reflected by the Menu Id of the .uno:BasicShapes popup items. The first item in the popup has a menu id of 1 and the second item has an id of 2 and so on.

Alternatively the Basic Shapes menu can be access in the following way:

basic_shapes = doc.menu[".uno:InsertMenu"][".uno:ShapesMenu"][".uno:BasicShapes"]

However, like the previous method the sub menu is still not available.

>>> basic_shapes.items[".uno:BasicShapes.circle"]
KeyError: "Menu item '.uno:BasicShapes.circle' not found"

In the menubar.xml file you can also see that .uno:BasicShapes has no popup menu in the configuration.

<menu menu:id=".uno:ShapesMenu">
    <menupopup>
        <menu menu:id=".uno:ShapesLineMenu">
            <menupopup>
                <menuitem menu:id=".uno:Line" />
                <menuitem menu:id=".uno:Freeline_Unfilled" />
                <menuitem menu:id=".uno:Freeline" />
                <menuitem menu:id=".uno:Bezier_Unfilled" />
                <menuitem menu:id=".uno:BezierFill" />
                <menuitem menu:id=".uno:Polygon_Unfilled" />
                <menuitem menu:id=".uno:Polygon_Diagonal_Unfilled" />
                <menuitem menu:id=".uno:Polygon_Diagonal" />
            </menupopup>
        </menu>
        <menuitem menu:id=".uno:BasicShapes" />
        <menuitem menu:id=".uno:ArrowShapes" />
        <menuitem menu:id=".uno:SymbolShapes" />
        <menuitem menu:id=".uno:StarShapes" />
        <menuitem menu:id=".uno:CalloutShapes" />
        <menuitem menu:id=".uno:FlowChartShapes" />
    </menupopup>
</menu>

The MenuItem, MenuItemSub and MenuItemSep have a item_kind property that also can be used to check for the appropriate type before taking action.

from ooodev.gui.menu.item import MenuItemKind
# ...

if itm.item_kind >= MenuItemKind.ITEM:
    # `MenuItem, do work
    MenuItem.execute() # run the menu command

Add Menu

from ooodev.calc import CalcDoc
from ooodev.utils.kind.menu_lookup_kind import MenuLookupKind


doc = CalcDoc.create_doc(loader=loader, visible=True)
menu = doc.menu[MenuLookupKind.TOOLS] # or .menu[".uno:ToolsMenu"]
itm = menu.items[".uno:AutoComplete"] # or .items[6]

menu_name = ".custom:my.custom_menu"
new_menu = {
    "Label": "My Menu",
    "CommandURL": menu_name,
    "Submenu": [
        {
            "Label": "Execute macro...",
            "CommandURL": "RunMacro",
            "ShortCut": "Shift+Ctrl+Alt+E",
        },
        {
            "Label": "Python Hello World",
            "CommandURL": {
                "library": "HelloWorld",
                "name": "HelloWorldPython",
                "language": "Python",
                "location": "share",
            },
        },
    ],
}

Adding a menu is done with the insert() method.

Only add the menu if it does not exist. If the menu did exist then this could cause some issues at getting a menu my name or index may return the incorrect instance if the menu was added twice with the same name. The save=True option means the changes will be persisted.

If you only wanted the menu to be available for the current instance then save=False could be used and the menu would not be persisted.

if not menu_name in menu:
    # only add the menu if it does not already exist
    menu.insert(new_menu, after=itm.command, save=True)

Remove Menu

The remove() method is used to remove a submenu from a menu. The save=True option means the changes will be persisted.

menu_name = ".custom:my.custom_menu" # or can just be "my.custom_menu"
if menu_name in menu:
    menu.remove(menu_name, save=True)

Execute Menu Item

Menu commands are mostly dispatch calls or a URL to run a macro. MenuItem and MenuItemSub have an execute method that will call call the dispatch or run the macro.

from ooodev.gui.menu.item import MenuItemKind
# ...
menu = doc.menu[MenuLookupKind.TOOLS]
itm = menu.items[".uno:AutoComplete"]
if itm.item_kind >= MenuItemKind.ITEM:
    MenuItem.execute() # run the menu command