Using Python with OpenOffice.org - Part 2/3

From an OpenOffice.org forum post:

  • XSCRIPTCONTEXT.getDocument() is the equivalent of Basic ThisComponent
  • XSCRIPTCONTEXT.getDesktop() is the equivalent of Basic StarDesktop
  • XSCRIPTCONTEXT.getComponentContext() provides a com.sun.star.uno.XComponentContext which I understand as “root interface of a running office instance”, which provides a ServiceManager, which can create instances of services.

The model returned by getDocument() then has a controller (hopefully! Remember to check return values). Controllers have a Frame – the actual GUI component that corresponds to it. They also have a getViewCursor() method that doesn’t appear to be documented in XModel. My understanding is that cursors represent positions in the document and are the starting point for text manipulation.

There is only one view cursor, but you can have multiple text cursors. A good place to start with all this is the documentation for XModel.

The view cursor’s String represents the selected text, if any, so we can see if the user wants to use what they selected:

def queryUseSelection(selectedText):
    if selectedText:
        answer = messageBox("There is text selected in the document, do you want to use it as the recipient address instead of looking for paragraphs with the Addressee style?\n\n" + selectedText, "Text is selected", "querybox", BUTTONS_YES_NO_CANCEL + DEFAULT_BUTTON_YES)
        return {
            0: (True, None),
            2: (False, [ selectedText ]),
            3: (False, [])
            }.get(answer, None)
    else:
        return None

model = XSCRIPTCONTEXT.getDocument()
controller = model.CurrentController

addresses = list()
vc = controller.ViewCursor
(abort, addresses) = queryUseSelection(vc.String)
if abort:
    return None

Searching by style

The first thing to do is search for text with the “Addressee” style. After reading the documentation of XSearchable, we can try:

def getTextWithStyle(styleName, model=XSCRIPTCONTEXT.getDocument()):
    searcher = model.createSearchDescriptor()
    searcher.SearchStyles = True
    searcher.SearchString = styleName
    results = model.findAll(searcher)
    return results

This works as expected; we get each paragraph with the Addressee style individually. Note that – of course – paragraph breaks should not be used for every line of the address. We can use the same function to get the sender (Sender style).

if not addresses:
    addressees = getTextWithStyle("Addressee")
    for i in xrange(0, addressees.getCount()):
        addresses.append(addressees.getByIndex(i).String)

sender = getTextWithStyle("Sender")
if sender.getCount():
    sender = sender.getByIndex(0).String
else:
    sender = None

messageBox(sender)
for address in addresses:
    messageBox(address)

Creating the envelope

Next, we have to actually create the envelope. Perhaps the simplest way to do this is to record a macro of what happens when you manually use the menus, and convert that to code. This creates a new page, alters the Envelope style according to parameters and then inserts two frames: one with Addressee style and one with Sender style. Here are the helper functions:

def createUnoService(serviceName, context=None):
    if not context:
        context = XSCRIPTCONTEXT.getComponentContext()
        service = context.ServiceManager.createInstance(serviceName)
    else:
        service = context.ServiceManager.createInstanceWithContext(serviceName, context)
    return service

def unoDispatch(command, args=None, frame=None):
    if not frame:
        frame = XSCRIPTCONTEXT.getDocument().CurrentController.Frame

    dispatcher = createUnoService('com.sun.star.frame.DispatchHelper')
    args2 = []
    for (key, value) in args.items():
        pv = PropertyValue()
        pv.Name = key
        pv.Value = value
        args2.append(pv)

    return dispatcher.executeDispatch(frame, ".uno:" + command, "", 0, tuple(args2))

This is, though, quite an ugly solution; it would be nicer to show the dialogue and not have to use UNO dispatch. The default GUI also seems to use the last settings it was called with from any Writer document, which is an odd choice. Documentation is not easy to find. This is the equivalent code for Insert Envelope now:

for address in addresses:
    args = { "Envelope.AddrText": address, "Envelope.Send": bool(sender), "Envelope.SendText": sender }
    unoDispatch("InsertEnvelope", args, controller.Frame)

Problems

However, it does give our first working solution if you are happy with hard-coding any of the layout information that will not match the last-used values. The nature of Insert Envelope is that it will only ever create one envelope per document, which is not the real aim of our solution. Time to look for a solution that is either, in order of preference:

  1. able to show the standard dialogue, with specified defaults
  2. able to show the standard dialogue, with at least sender & addressee correct
  3. show our own dialogue or manually create the page (just styles or from a template?)
  4. be more “native” than using UNO dispatch

I think the most logical solution may be to use purely styles. That way the user can have the envelope, frame and paragraph style already set up in the same template that they use for letters, and we simply fill in the two obvious entries.