The more you look, the more the documentation is really poor. Using UNO dispatch is often the only way to do things, regardless of language. Currently the solution of using .uno:InsertEnvelope
has the following problems:
- only one envelope can be added
- the dialogue is not shown if you provide some parameters
- the non-supplied parameters take the defaults from last time (both a feature and a bug, arguably)
I’d like to avoid using a second template just for the envelope, so the obvious solution is to create the document manually and allow the styles to control the layout. This way the user can control the styles in their own template. However, it looks like the current Insert Envelope command uses per-frame styles at least. These are easy enough to alter individually. Another problem is that we want different fonts for the Addressee and Sender on the envelope than in letter: font on the cover should be sans serif for automatic postal processing. It’s not clear to me what the best solution is here; perhaps alter the style on the cover, perhaps use different styles for the body and envelope. I think it’s more logical to go with the second method (use SenderBody in the body and fall-back to Sender if no such paragraph style exists). We could use the same trick with the frames to allow, for example, changing the layout if desired or just using the defaults. The start of an article on creating dialogues was referenced earlier, if that becomes necessary.
The default behaviour of Insert Envelope appears to be:
- insert a new page, with style Envelope, following style Default
- modify the style to include user-specified margins, and auto-adjust for print settings
- insert two frames, modify their styles to be in user-specified positions
- put sender and addressee into the frames with appropriate paragraph styles
There is some seemingly strange stuff going on with the print settings. For example, setting the paper to be deliberately larger than the envelope size and then setting a margin. This seems like a trick for certain common printer types/trays, and will probably only be clear with experimentation.
Adding a page
Pages in OpenOffice are unusual. This seems to work:
cursor = model.Text.createTextCursor() cursor.gotoStart(False) cursor.BreakType = PAGE_AFTER model.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
Styles
The documentation surrounding styles is a little better than average.
def getStyle(family, name, model=XSCRIPTCONTEXT.getDocument()): styleFamily = model.StyleFamilies.getByName(family) style = styleFamily.getByName(name) return style
As usual, we don’t need strange type-casts in Python to use the different interfaces that services provide – we can just treat them as any one of the interfaces. Unfortunately we are soon at a dead-end again:
messageBox(cursor.PageStyleName) # "Standard" (=Default) cursor.PageStyleName = "Envelope" # no such property cursor.setPropertyValue("PageStyleName", "Envelope") # property is read-only
The documentation claims that you change the page style by finding the current one and manually altering its properties. Reading more closely, we see that there is a second property, PageDescName
, that will do the job. Normally, styles are set by selecting the appropriate text unit of the model with a cursor and then using its PropertySet
, which in Python is as simple as:
cursor.ParaStyleName = "Sender"
Doing the same with the page style works, but the documentation claims it will insert a new page if not already at the start. This doesn’t seem to respect the cursor PageBreak
setting (always creates after) so our previous code remains useful.
Frames
Frames are used both in the traditional sense of windowing components and in the text layout sense. They are the same in OpenOffice, but the documentation leans towards the former. After hunting around for a while you can find the right page. There is also an overview and a GUI-oriented piece. So we do:
#!python frame = createUnoService(“com.sun.star.text.TextFrame”) model.Text.insertTextContent(vc, frame, False) frame.Text.String = sender
Naturally, this fails. It seems that the standard factory does not support TextFrame
(not mentioned in the documentation page). Not immediately clear how to get the required factory, but hypothesising that the TextFrame
is not a service and can be created by the document model:
frame = model.createInstance("com.sun.star.text.TextFrame") model.Text.insertTextContent(cursor, frame, False) frame.Text.String = sender
We have a frame!
To be continued…
I’m sure this used to work…
my $posterous = "not working"; for $foo (keys %bar) { print $posterous . "\n".; } # maybe if i combine with bash as before? # i was tricked! it was always broken! # just bash highlighting
Well, does it or doesn’t it?
Hopefully this post is in Markdown.
You want to extract some capture groups in Perl. You are using strict
and warnings
. Some of the capture groups are optional, but if they aren’t there the others should still work. It feels like one regex, but you don’t want to see the warnings for the missing groups:
#!/usr/bin/perl -p use strict; use warnings; { no warnings 'uninitialized'; s/^(1+).*?(?:(2+) (3+))?$/$1 $2 $3/; }
Which gives:
$ echo "1foobar22 33" | ./test.pl 1 22 333 $ echo "111 baz" | ./test.pl 111
Instead of:
$ echo "1foobar22 333" | perl -p ./test.pl 1 2 333 $ echo "111 baz" | perl -p ./test.pl 111 baz
without the optional non-capturing group, or:
$ echo "1foobar22 333" | perl -p ./test.pl 1 22 333 $ echo "111 baz" | perl -p ./test.pl Use of uninitialized value $2 in concatenation (.) or string at ./test.pl line 7, <> line 1. Use of uninitialized value $3 in concatenation (.) or string at ./test.pl line 7, <> line 1. 111
with optionality but without the no warnings
block. I’m sure there’s a better way to do it, but I can’t remember. Incidentally, Perl is decent to input on iPhone but Posterous’s App is not; the Markdown failed and it uses accurate location without per-post choice.
From an OpenOffice.org forum post:
XSCRIPTCONTEXT.getDocument()
is the equivalent of BasicThisComponent
XSCRIPTCONTEXT.getDesktop()
is the equivalent of BasicStarDesktop
XSCRIPTCONTEXT.getComponentContext()
provides acom.sun.star.uno.XComponentContext
which I understand as “root interface of a running office instance”, which provides aServiceManager
, 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:
- able to show the standard dialogue, with specified defaults
- able to show the standard dialogue, with at least sender & addressee correct
- show our own dialogue or manually create the page (just styles or from a template?)
- 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.