Last active
September 13, 2025 02:05
-
-
Save ikks/91c8340ed98b9f07a3c0c6cff02860d5 to your computer and use it in GitHub Desktop.
Pythonic LibreOffice Dialog with NumericField
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
#!env py | |
# | |
# Igor Támara | |
# This file can be freely distributed under MPL1.0 and derivatives | |
import uno | |
import unohelper | |
from com.sun.star.connection import NoConnectException | |
from com.sun.star.uno import XComponentContext | |
from typing import TYPE_CHECKING | |
from typing import Any | |
from typing import List | |
from typing import Tuple | |
if TYPE_CHECKING: | |
from com.sun.star.awt import UnoControlDialog | |
from com.sun.star.awt import UnoControlDialogModel | |
from com.sun.star.awt import UnoControlModel | |
from com.sun.star.awt import UnoControlNumericFieldModel | |
from com.sun.star.lang import ExtToolkit | |
from com.sun.star.lang import ServiceManager | |
from com.sun.star.awt.tab import UnoControlTabPageModel | |
from com.sun.star.awt.tab import UnoControlTabPageContainerModel | |
def create_widget( | |
container, | |
typename: str, | |
identifier: str, | |
rect: Tuple[int, int, int, int], | |
component=None, | |
add_now: bool = True, | |
additional_properties: List[Tuple[str, Any]] = None, | |
): | |
""" | |
Creates a control Model from typename, with the identifier, positioned with | |
widthxheight, when add_now is True, inserts the new control inside component, | |
if component is None, inserts in the container. States property | |
values from additional_properties to the new created control. | |
The new created control is returned. | |
https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt.html | |
""" | |
if component is None: | |
component = container | |
if typename == "TabPageContainer": | |
cmpt_type = f"com.sun.star.awt.tab.UnoControl{typename}Model" | |
else: | |
cmpt_type = f"com.sun.star.awt.UnoControl{typename}Model" | |
cmpt: UnoControlModel = container.createInstance(cmpt_type) | |
cmpt.setPropertyValues( | |
[ | |
"Name", | |
"PositionX", | |
"PositionY", | |
"Width", | |
"Height", | |
], | |
[ | |
identifier, | |
rect[0], | |
rect[1], | |
rect[2], | |
rect[3], | |
], | |
) | |
if add_now: | |
component.insertByName(identifier, cmpt) | |
if additional_properties: | |
cmpt.setPropertyValues(*zip(*additional_properties)) | |
return cmpt | |
class DialogWithTab(unohelper.Base): | |
def __init__(self, ctx=uno.getComponentContext()): | |
self.context: XComponentContext = ctx | |
self.insert_in = [] | |
self.sm: ServiceManager = self.context.ServiceManager | |
self.toolkit: ExtToolkit = self.sm.createInstanceWithContext( | |
"com.sun.star.awt.ExtToolkit", self.context | |
) | |
self.dc: UnoControlDialog = self.sm.createInstanceWithContext( | |
"com.sun.star.awt.UnoControlDialog", self.context | |
) | |
self.dm: UnoControlDialogModel = create_widget( | |
self.sm, | |
"Dialog", | |
"dlg_options", | |
(47, 10, 165, 116), | |
add_now=False, | |
additional_properties=[ | |
("Closeable", True), | |
("Moveable", True), | |
("Title", "My Configuration"), | |
], | |
) | |
self.dc.setModel(self.dm) | |
dm: UnoControlDialogModel = self.dc.getModel() | |
self.dc.createPeer(self.toolkit, None) | |
book: UnoControlTabPageContainerModel = create_widget( | |
dm, "TabPageContainer", "book", (18, 15, 130, 60) | |
) | |
page_ad: UnoControlTabPageModel = book.createTabPage(1) | |
page_ad.Title = "🔧 Parameters" | |
book.insertByIndex(0, page_ad) | |
page_ui: UnoControlTabPageModel = book.createTabPage(2) | |
page_ui.Title = "👀 UI" | |
book.insertByIndex(1, page_ui) | |
self.page_ad = page_ad | |
ctrl: UnoControlNumericFieldModel = create_widget( | |
page_ad, | |
"NumericField", | |
"int_height", | |
(66, 12, 54, 13), | |
add_now=False, | |
additional_properties=[ | |
("StrictFormat", True), | |
("DecimalAccuracy", 0), | |
("Value", 384), | |
("ValueMin", 384), | |
("ValueMax", 1024), | |
("ValueStep", 64), | |
("Spin", True), | |
], | |
) | |
self.first = ctrl | |
self.insert_in.append((self.page_ad, self.first)) | |
ctrl: UnoControlNumericFieldModel = create_widget( | |
dm, | |
"NumericField", | |
"int_outer", | |
(88, 82, 54, 13), | |
add_now=False, | |
additional_properties=[ | |
("StrictFormat", True), | |
("DecimalAccuracy", 0), | |
("Value", 384), | |
("ValueMin", 384), | |
("ValueMax", 1024), | |
("ValueStep", 64), | |
("Spin", True), | |
], | |
) | |
self.second = ctrl | |
self.insert_in.append((self.dm, self.second)) | |
def showDialog(self): | |
self.dc.setVisible(True) | |
for pair in self.insert_in: | |
pair[0].insertByName(pair[1].Name, pair[1]) | |
return self.dc.execute() | |
def run_hello_tab_sample(*args): | |
try: | |
# Invoked from External process | |
ctx: XComponentContext = remote_ctx | |
except NameError: | |
# Invoked from LibreOffice UI | |
ctx: XComponentContext = uno.getComponentContext() | |
app = DialogWithTab(ctx=ctx) | |
app.showDialog() | |
# Execute macro from LibreOffice UI (Tools - Macro) | |
g_exportedScripts = (run_hello_tab_sample,) | |
if __name__ == "__main__": | |
""" Connect to LibreOffice proccess. | |
1) Start the office in shell with command: | |
libreoffice "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext" --norestore | |
2) Run script | |
""" | |
local_ctx: XComponentContext = uno.getComponentContext() | |
resolver = local_ctx.ServiceManager.createInstance( | |
"com.sun.star.bridge.UnoUrlResolver" | |
) | |
try: | |
remote_ctx: XComponentContext = resolver.resolve( | |
"uno:socket," | |
"host=127.0.0.1," | |
"port=2002," | |
"tcpNoDelay=1;" | |
"urp;" | |
"StarOffice.ComponentContext" | |
) | |
except NoConnectException: | |
print("""You are not running a libreoffice instance, to do so from a command line that has `libreoffice` in the path run: | |
libreoffice "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext" --norestore \n\nAnd try again | |
""") | |
run_hello_tab_sample() |
I found a solution, making the dialog visible and afterwards adding the components that had problems calls the necessary code to show them correctly. I will add this as version 3, in order to make it easy to compare the changes. There are additional cosmetic to save some keystrokes.
# -*- coding: utf-8 -*-
#!env py
#
# Igor Támara
# This file can be freely distributed under MPL1.0 and derivatives
import uno
import unohelper
from com.sun.star.connection import NoConnectException
from com.sun.star.uno import XComponentContext
from typing import TYPE_CHECKING
from typing import Any
from typing import List
from typing import Tuple
if TYPE_CHECKING:
from com.sun.star.awt import UnoControlDialog
from com.sun.star.awt import UnoControlDialogModel
from com.sun.star.awt import UnoControlModel
from com.sun.star.awt import UnoControlNumericFieldModel
from com.sun.star.lang import ExtToolkit
from com.sun.star.lang import ServiceManager
from com.sun.star.awt.tab import UnoControlTabPageModel
from com.sun.star.awt.tab import UnoControlTabPageContainerModel
def create_widget(
container,
typename: str,
identifier: str,
rect: Tuple[int, int, int, int],
component=None,
add_now: bool = True,
additional_properties: List[Tuple[str, Any]] = None,
):
"""
Creates a control Model from typename, with the identifier, positioned with
widthxheight, when add_now is True, inserts the new control inside component,
if component is None, inserts in the container. States property
values from additional_properties to the new created control.
The new created control is returned.
https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt.html
"""
if component is None:
component = container
if typename == "TabPageContainer":
cmpt_type = f"com.sun.star.awt.tab.UnoControl{typename}Model"
else:
cmpt_type = f"com.sun.star.awt.UnoControl{typename}Model"
cmpt: UnoControlModel = container.createInstance(cmpt_type)
cmpt.setPropertyValues(
[
"Name",
"PositionX",
"PositionY",
"Width",
"Height",
],
[
identifier,
rect[0],
rect[1],
rect[2],
rect[3],
],
)
if add_now:
component.insertByName(identifier, cmpt)
if additional_properties:
cmpt.setPropertyValues(*zip(*additional_properties))
return cmpt
class DialogWithTab(unohelper.Base):
def __init__(self, ctx=uno.getComponentContext()):
self.context: XComponentContext = ctx
self.insert_in = []
self.sm: ServiceManager = self.context.ServiceManager
self.toolkit: ExtToolkit = self.sm.createInstanceWithContext(
"com.sun.star.awt.ExtToolkit", self.context
)
self.dc: UnoControlDialog = self.sm.createInstanceWithContext(
"com.sun.star.awt.UnoControlDialog", self.context
)
self.dm: UnoControlDialogModel = create_widget(
self.sm,
"Dialog",
"dlg_options",
(47, 10, 165, 116),
add_now=False,
additional_properties=[
("Closeable", True),
("Moveable", True),
("Title", "My Configuration"),
],
)
self.dc.setModel(self.dm)
dm: UnoControlDialogModel = self.dc.getModel()
self.dc.createPeer(self.toolkit, None)
book: UnoControlTabPageContainerModel = create_widget(
dm, "TabPageContainer", "book", (18, 15, 130, 60)
)
page_ad: UnoControlTabPageModel = book.createTabPage(1)
page_ad.Title = "🔧 Parameters"
book.insertByIndex(0, page_ad)
page_ui: UnoControlTabPageModel = book.createTabPage(2)
page_ui.Title = "👀 UI"
book.insertByIndex(1, page_ui)
self.page_ad = page_ad
ctrl: UnoControlNumericFieldModel = create_widget(
page_ad,
"NumericField",
"int_height",
(66, 12, 54, 13),
add_now=False,
additional_properties=[
("StrictFormat", True),
("DecimalAccuracy", 0),
("Value", 384),
("ValueMin", 384),
("ValueMax", 1024),
("ValueStep", 64),
("Spin", True),
],
)
self.first = ctrl
self.insert_in.append((self.page_ad, self.first))
ctrl: UnoControlNumericFieldModel = create_widget(
dm,
"NumericField",
"int_outer",
(88, 82, 54, 13),
add_now=False,
additional_properties=[
("StrictFormat", True),
("DecimalAccuracy", 0),
("Value", 384),
("ValueMin", 384),
("ValueMax", 1024),
("ValueStep", 64),
("Spin", True),
],
)
self.second = ctrl
self.insert_in.append((self.dm, self.second))
def showDialog(self):
self.dc.setVisible(True)
for pair in self.insert_in:
pair[0].insertByName(pair[1].Name, pair[1])
return self.dc.execute()
def run_hello_tab_sample(*args):
try:
# Invoked from External process
ctx: XComponentContext = remote_ctx
except NameError:
# Invoked from LibreOffice UI
ctx: XComponentContext = uno.getComponentContext()
app = DialogWithTab(ctx=ctx)
app.showDialog()
# Execute macro from LibreOffice UI (Tools - Macro)
g_exportedScripts = (run_hello_tab_sample,)
if __name__ == "__main__":
""" Connect to LibreOffice proccess.
1) Start the office in shell with command:
libreoffice "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext" --norestore
2) Run script
"""
local_ctx: XComponentContext = uno.getComponentContext()
resolver = local_ctx.ServiceManager.createInstance(
"com.sun.star.bridge.UnoUrlResolver"
)
try:
remote_ctx: XComponentContext = resolver.resolve(
"uno:socket,"
"host=127.0.0.1,"
"port=2002,"
"tcpNoDelay=1;"
"urp;"
"StarOffice.ComponentContext"
)
except NoConnectException:
print("""You are not running a libreoffice instance, to do so from a command line that has `libreoffice` in the path run:
libreoffice "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext" --norestore \n\nAnd try again
""")
run_hello_tab_sample()
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Adding
TabPage
andTabPageContainer
withcreatePeer