Jump to content

Input/Output to Screen with Python

From The Document Foundation Wiki


Python standard console output is not available when running Python macros from Tools ▸ Macros ▸ Run Macro… LibreOffice menu. Presenting the output of a module requires the Python interactive console. Features such as input(), print(), repr() and str() are available from the Python shell.

LibreOffice Basic proposes InputBox(), Msgbox() and Print() screen I/O functions. Python alternatives exist that rely either on LibreOffice API Abstract Windowing Toolkit, either on msgbox.py LibreOffice shared module, either on Python to Basic function calls. Proposed method signatures in the coming examples are intentionally close to that of Basic. The API Scripting Framework can be used to perform Basic, BeanShell, JavaScript and Python inter-languages function calls.

Note:
User interactions are devoted to LibreOffice dialog editor. The following examples exhibit little user interface benefits, therefore input/output to screen with Python is to be limited to development or test situations.

Screen InputBox() function

Cloning InputBox() Basic function can be achieved using LibreOffice API calls or Python to Basic inter-languages calls.

Using Application Programming Interface (API)

There is no InputBox() function provided for Python. The following example code is borrowed from Transfer from Basic to Python. The script builds the complete dialog plus its controls using the API abstract windowing toolkit. An instance of com.sun.star.awt.UnoControlDialogModel[1] service gets created, and various com.sun.star.awt.UnoControlxxxModel controls are added. com.sun.star.awt.Toolkit[2] service displays the dialog as com.sun.star.frame.Desktop[3] environment locates the current window.

def InputBox(message, title="", default="", x=None, y=None):
    """ Shows dialog with input box.
        @param message message to show on the dialog
        @param title window title
        @param default default value
        @param x optional dialog position in twips
        @param y optional dialog position in twips
        @return string if OK button pushed, otherwise zero length string
    """
    WIDTH = 600
    HORI_MARGIN = VERT_MARGIN = 8
    BUTTON_WIDTH = 100
    BUTTON_HEIGHT = 26
    HORI_SEP = VERT_SEP = 8
    LABEL_HEIGHT = BUTTON_HEIGHT * 2 + 5
    EDIT_HEIGHT = 24
    HEIGHT = VERT_MARGIN * 2 + LABEL_HEIGHT + VERT_SEP + EDIT_HEIGHT
    import uno
    from com.sun.star.awt.PosSize import POS, SIZE, POSSIZE
    from com.sun.star.awt.PushButtonType import OK, CANCEL
    from com.sun.star.util.MeasureUnit import TWIP
    ctx = uno.getComponentContext()
    def create(name):
        return ctx.getServiceManager().createInstanceWithContext(name, ctx)
    dialog = create("com.sun.star.awt.UnoControlDialog")
    dialog_model = create("com.sun.star.awt.UnoControlDialogModel")
    dialog.setModel(dialog_model)
    dialog.setVisible(False)
    dialog.setTitle(title)
    dialog.setPosSize(0, 0, WIDTH, HEIGHT, SIZE)
    def add(name, type, x_, y_, width_, height_, props):
        model = dialog_model.createInstance("com.sun.star.awt.UnoControl" + type + "Model")
        dialog_model.insertByName(name, model)
        control = dialog.getControl(name)
        control.setPosSize(x_, y_, width_, height_, POSSIZE)
        for key, value in props.items():
            setattr(model, key, value)
    label_width = WIDTH - BUTTON_WIDTH - HORI_SEP - HORI_MARGIN * 2
    add("label", "FixedText", HORI_MARGIN, VERT_MARGIN, label_width, LABEL_HEIGHT, 
        {"Label": str(message), "NoLabel": True})
    add("btn_ok", "Button", HORI_MARGIN + label_width + HORI_SEP, VERT_MARGIN, 
            BUTTON_WIDTH, BUTTON_HEIGHT, {"PushButtonType": OK, "DefaultButton": True})
    add("btn_cancel", "Button", HORI_MARGIN + label_width + HORI_SEP, VERT_MARGIN + BUTTON_HEIGHT + 5, 
            BUTTON_WIDTH, BUTTON_HEIGHT, {"PushButtonType": CANCEL})
    add("edit", "Edit", HORI_MARGIN, LABEL_HEIGHT + VERT_MARGIN + VERT_SEP, 
            WIDTH - HORI_MARGIN * 2, EDIT_HEIGHT, {"Text": str(default)})
    frame = create("com.sun.star.frame.Desktop").getCurrentFrame()
    window = frame.getContainerWindow() if frame else None
    dialog.createPeer(create("com.sun.star.awt.Toolkit"), window)
    if not x is None and not y is None:
        ps = dialog.convertSizeToPixel(uno.createUnoStruct("com.sun.star.awt.Size", x, y), TWIP)
        _x, _y = ps.Width, ps.Height
    elif window:
        ps = window.getPosSize()
        _x = ps.Width / 2 - WIDTH / 2
        _y = ps.Height / 2 - HEIGHT / 2
    dialog.setPosSize(_x, _y, 0, 0, POS)
    edit = dialog.getControl("edit")
    edit.setSelection(uno.createUnoStruct("com.sun.star.awt.Selection", 0, len(str(default))))
    edit.setFocus()
    ret = edit.getModel().Text if dialog.execute() else ""
    dialog.dispose()
    return ret

Example:

reply = InputBox("Please input some value", "Input", "Default value")

Using X-Scripting

That mechanism is described in Input/Output to Screen help page. screen_io.InputBox() function signature is identical to that of LibreOffice Basic InputBox() function.

ScriptForge Python module calls Basic routines in order to provide InputBox, MsgBox and Print functions that exhibit identical method signatures to that of LibreOffice Basic. See …/program/scriptforge.py Python module in LibreOffice installation directory for source code insights.

MsgBox() function

MsgBox() function alternatives exist using either LibreOffice API, either msgbox.py module or Python to Basic calls. Available features differ depending on the solution. They are summarized herewith:

features using the API using msgbox.py using x-Scripting
Auto-rendering yes no yes
Bundled module yes yes no
icons yes no yes
Dialog types pre-defined no pre-defined
Dialog buttons pre-defined user-defined pre-defined
Default button yes 1st button yes
Closeable dialog yes no yes
Sizable dialog no yes no
Return button value button text button value


Using the API

The message dialog is performed by calling the createMessageBox method of the com.sun.star.awt.Toolkit service. A very similar example is present in Python Guide - Useful functions page. The Developer's Guide illustrates that same approach using Java.


Tip:
Other message box examples using BeanShell, Java, JavaScript or Python can be obtained from Display a message box[4] thread.


# -*- coding: utf-8 -*-

import uno
from com.sun.star.awt.MessageBoxType import \
    MESSAGEBOX, INFOBOX, ERRORBOX, WARNINGBOX, QUERYBOX
from com.sun.star.awt.MessageBoxButtons import \
    BUTTONS_OK, BUTTONS_OK_CANCEL, BUTTONS_YES_NO, \
    BUTTONS_YES_NO_CANCEL, BUTTONS_RETRY_CANCEL, \
    BUTTONS_ABORT_IGNORE_RETRY
from com.sun.star.awt.MessageBoxButtons import \
    DEFAULT_BUTTON_OK, DEFAULT_BUTTON_CANCEL, DEFAULT_BUTTON_RETRY, \
    DEFAULT_BUTTON_YES, DEFAULT_BUTTON_NO, DEFAULT_BUTTON_IGNORE
from com.sun.star.awt.MessageBoxResults import \
    CANCEL, OK, YES, NO, RETRY, IGNORE

def MsgBox(prompt: str, title: str, boxtype=MESSAGEBOX, 
           buttons=BUTTONS_OK, windowPeer=None) -> int:
    """return value of MessageBox selected button"""
    ''' adapted from 'apso_utils' by Jean-Marc Zambon 
    https://extensions.libreoffice.org/extensions/apso-alternative-script-organizer-for-python'''
    tk = _createUnoService("com.sun.star.awt.Toolkit")
    if not windowPeer:
        desktop = _createUnoService("com.sun.star.frame.Desktop")
        frame = desktop.ActiveFrame
        if frame.ActiveFrame:  # top window is a subdocument
            frame = frame.ActiveFrame
        windowPeer = frame.ComponentWindow
    box = tk.createMessageBox(windowPeer, boxtype, buttons, title, prompt)
    return box.execute()

def _createUnoService(service, ctx=None, args = None):
    ''' from 'apso_utils' by Jean-Marc Zambon 
    https://extensions.libreoffice.org/extensions/apso-alternative-script-organizer-for-python'''
    if not ctx:
        ctx = uno.getComponentContext()
    smgr = ctx.getServiceManager()
    if ctx and args:
        return smgr.createInstanceWithArgumentsAndContext(service, args, ctx)
    elif args:
        return smgr.createInstanceWithArguments(service, args)
    elif ctx:
        return smgr.createInstanceWithContext(service, ctx)
    else:
        return smgr.createInstance(service)

Examples:

def desk_msg():
    MsgBox('no active document present', 'Desktop only')

def doc_msg():
    """ Adapted from 'Dialogue message étendu' by B.Marcelly
    https://forum.openoffice.org/fr/forum/viewtopic.php?f=15&t=47603 """
    doc = XSCRIPTCONTEXT.getDocument()
    parentwin = doc.CurrentController.Frame.ContainerWindow
    res = YES
    while res != NO:
        s = "Do you want to continue?"
        t = "A message from Python"
        res = MsgBox(s, t, QUERYBOX,
            BUTTONS_YES_NO_CANCEL + DEFAULT_BUTTON_NO, parentwin)
        s = "Response : " +str(res)
        MsgBox(s, t, INFOBOX, windowPeer=parentwin)

def long_msg():
    """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut semper mi sit amet turpis sollicitudin blandit. Aliquam ligula ex, maximus at sapien et, tristique lacinia justo. Sed venenatis tincidunt massa sed imperdiet. Suspendisse vitae odio viverra, euismod tellus ac, vestibulum sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ultrices ac enim at sodales. Pellentesque sollicitudin convallis lacinia. Ut hendrerit vel augue vel malesuada. Aenean ut est a neque sodales porttitor sit amet at magna. Duis tristique dolor quis nunc elementum dapibus. Curabitur dapibus aliquam condimentum. Nam porta, quam vel porta posuere, eros lectus aliquet eros, sed eleifend magna sem sed metus. Quisque fermentum maximus porta. Vestibulum feugiat neque malesuada purus condimentum convallis. Integer leo justo, congue in rutrum vel, dictum et tortor. Fusce eget iaculis sapien. Donec imperdiet ut lacus ac semper. Nulla facilisi. Ut efficitur, nulla at pellentesque luctus, ligula est aliquet metus, scelerisque condimentum erat nunc eu enim. Pellentesque mattis augue vel dictum accumsan. Ut convallis vestibulum ipsum, sit amet blandit nisi rutrum sit amet. Vestibulum non lacus eu nunc luctus dignissim. Maecenas tincidunt enim vitae hendrerit iaculis. Nullam lacinia massa quis enim venenatis vulputate nec id nibh. Aliquam consequat dignissim interdum. Proin consectetur sem sit amet dolor lobortis ullamcorper ac vitae odio.

    Integer aliquam, arcu condimentum venenatis lobortis, felis nisl ullamcorper elit, eu luctus ligula mi sit amet ante. Donec eu ante facilisis, tempor metus vitae, dignissim libero. Praesent euismod varius libero ac feugiat. Maecenas tempor lorem eget venenatis vehicula."""
    reply = MsgBox(long_msg.__doc__, 'Lorem ipsum …', WARNINGBOX, 
                   BUTTONS_ABORT_IGNORE_RETRY + DEFAULT_BUTTON_OK)
    MsgBox( reply, 'response', ERRORBOX)

g_exportedScripts = desk_msg, doc_msg, long_msg

Using msgbox.py

This module comes bundled with LibreOffice. Use it to build custom message dialogs that require user-defined buttons. Return value is that of the selected button. Dialog closing is inhibited. Its MsgBox() class method signatures are:

  • MsgBox(ctx: “Component context”)
  • addButton(caption: str)
  • renderFromBoxSize(size: num = 150)
  • renderFromButtonsSize(size: int = 50)
  • show(message: str, decoration: int , title: str): str

Examples:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import uno, unohelper  # PyUno itself
from msgbox import MsgBox  # LibreOffice shared module 

def large_msg():
    """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut semper mi sit amet turpis sollicitudin blandit. Aliquam ligula ex, maximus at sapien et, tristique lacinia justo. Sed venenatis tincidunt massa sed imperdiet. Suspendisse vitae odio viverra, euismod tellus ac, vestibulum sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ultrices ac enim at sodales. Pellentesque sollicitudin convallis lacinia. Ut hendrerit vel augue vel malesuada. Aenean ut est a neque sodales porttitor sit amet at magna. Duis tristique dolor quis nunc elementum dapibus. Curabitur dapibus aliquam condimentum. Nam porta, quam vel porta posuere, eros lectus aliquet eros, sed eleifend magna sem sed metus. Quisque fermentum maximus porta. Vestibulum feugiat neque malesuada purus condimentum convallis. Integer leo justo, congue in rutrum vel, dictum et tortor. Fusce eget iaculis sapien. Donec imperdiet ut lacus ac semper. Nulla facilisi. Ut efficitur, nulla at pellentesque luctus, ligula est aliquet metus, scelerisque condimentum erat nunc eu enim. Pellentesque mattis augue vel dictum accumsan. Ut convallis vestibulum ipsum, sit amet blandit nisi rutrum sit amet. Vestibulum non lacus eu nunc luctus dignissim. Maecenas tincidunt enim vitae hendrerit iaculis. Nullam lacinia massa quis enim venenatis vulputate nec id nibh. Aliquam consequat dignissim interdum. Proin consectetur sem sit amet dolor lobortis ullamcorper ac vitae odio.

    Integer aliquam, arcu condimentum venenatis lobortis, felis nisl ullamcorper elit, eu luctus ligula mi sit amet ante. Donec eu ante facilisis, tempor metus vitae, dignissim libero. Praesent euismod varius libero ac feugiat. Maecenas tempor lorem eget venenatis vehicula."""
    myBox = MsgBox(uno.getComponentContext())
    myBox.addButton("Yes")
    myBox.addButton("No")
    myBox.addButton("May be")
    myBox.renderFromBoxSize(450)
    #myBox.numberOfLines = 8
    _msg( myBox.show(large_msg.__doc__ + chr(10)+chr(10)+"Do you agree ?",0,"That message is too long. Please increase the box size!"))

def short_msg():
    myBox = MsgBox(XSCRIPTCONTEXT.getComponentContext())
    myBox.addButton("oK")
    myBox.renderFromButtonSize()
    myBox.numberOflines = 2
    _msg( myBox.show("A small message",0,"Dialog title"))

def _msg(prompt='', title=''):
    mb = MsgBox(uno.getComponentContext())
    mb.addButton('Howdy')
    mb.show(prompt,0,title)

g_exportedScripts = large_msg, short_msg

Using X-Scripting

That mechanism is described in Input/Output to Screen help page. screen_io.MsgBox() function signature is identical to that of LibreOffice Basic MsgBox() function or statement.

ScriptForge Python module calls Basic routines in order to provide InputBox, MsgBox and Print functions that exhibit identical method signatures to that of LibreOffice Basic. See …/program/scriptforge.py Python module in LibreOffice installation directory for source code insights.

Print() display function

Cloning Print() function can be achieved with Python to Basic inter-languages calls or using msgbox.py Python module.

Using msgbox.py

This module comes bundled with LibreOffice. Use it to build custom message dialogs that require user-defined buttons. Return value is that of the selected button. Dialog closing is inhibited. Its MsgBox() class method signatures are:

  • MsgBox(ctx: “Component context”)
  • addButton(caption: str)
  • renderFromBoxSize(size: num = 150)
  • renderFromButtonsSize(size: int = 50)
  • show(message: str, decoration: int , title: str): str

Examples:

import uno
from msgbox import MsgBox 

def prt():
    Print( 'prolog', 'test ', 'epilog', sep="\n")

def Print(*args, sep=''):
    """ Print arguments using given separator """
    mb = MsgBox(uno.getComponentContext())
    mb.addButton('Ok')
    mb.addButton('Cancel')
    prompt = ''
    for arg in args:
        prompt = prompt+arg+sep
    return mb.show(prompt,0,'Warning')

g_exportedScripts = prt,

Using X-Scripting

That mechanism is described in Input/Output to Screen help page. screen_io.Print() function signature is intentionally close to that of LibreOffice Basic Print statement.

ScriptForge Python module calls Basic routines in order to provide InputBox, MsgBox and Print functions that exhibit identical method signatures to that of LibreOffice Basic. See …/program/scriptforge.py Python module in LibreOffice installation directory for source code insights.

ODT file to run scripts

Note:
Download ODT file with macros included

Notes

Lorem Ipsum - All the facts

Pypsum is an interface to lipsum.com written in Python.