Tutorial Openoffice Automation VFP
Tutorial Openoffice Automation VFP
Copyright 2000 Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, Californie 94303-4900 Etats-Unis. Tous droits réservés.
Ce produit ou document est protégé par un copyright et distribué avec des licences qui en restreignent l’utilisation, la copie, la
distribution, et la décompilation. Aucune partie de ce produit ou document ne peut être reproduite sous aucune forme, par quelque
moyen que ce soit, sans l’autorisation préalable et écrite de Sun et de ses bailleurs de licence, s’il y en a. Le logiciel détenu par des tiers, et
qui comprend la technologie relative aux polices de caractères, est protégé par un copyright et licencié par des fournisseurs de Sun.
Des parties de ce produit pourront être dérivées du système Berkeley BSD licenciés par l’Université de Californie. UNIX est une marque
déposée aux Etats-Unis et dans d’autres pays et licenciée exclusivement par X/Open Company, Ltd.
Sun, Sun Microsystems, le logo Sun, docs.sun.com, AnswerBook, AnswerBook2, et Solaris sont des marques de fabrique ou des marques
déposées, ou marques de service, de Sun Microsystems, Inc. aux Etats-Unis et dans d’autres pays. Toutes les marques SPARC sont utilisées
sous licence et sont des marques de fabrique ou des marques déposées de SPARC International, Inc. aux Etats-Unis et dans d’autres pays.
Les produits portant les marques SPARC sont basés sur une architecture développée par Sun Microsystems, Inc.
L’interface d’utilisation graphique OPEN LOOK et SunTM a été développée par Sun Microsystems, Inc. pour ses utilisateurs et licenciés.
Sun reconnaît les efforts de pionniers de Xerox pour la recherche et le développement du concept des interfaces d’utilisation visuelle ou
graphique pour l’industrie de l’informatique. Sun détient une licence non exclusive de Xerox sur l’interface d’utilisation graphique Xerox,
cette licence couvrant également les licenciés de Sun qui mettent en place l’interface d’utilisation graphique OPEN LOOK et qui en outre
se conforment aux licences écrites de Sun.
CETTE PUBLICATION EST FOURNIE “EN L’ETAT” ET AUCUNE GARANTIE, EXPRESSE OU IMPLICITE, N’EST ACCORDEE, Y
COMPRIS DES GARANTIES CONCERNANT LA VALEUR MARCHANDE, L’APTITUDE DE LA PUBLICATION A REPONDRE A UNE
UTILISATION PARTICULIERE, OU LE FAIT QU’ELLE NE SOIT PAS CONTREFAISANTE DE PRODUIT DE TIERS. CE DENI DE
GARANTIE NE S’APPLIQUERAIT PAS, DANS LA MESURE OU IL SERAIT TENU JURIDIQUEMENT NUL ET NON AVENU.
Please
Recycle
Contents
1. Introduction 7
2.2 Modules 15
2.3 Components 16
3.2 Properties 21
3
3.3.1 Named access 22
3.4 Events 25
3.5.1 Properties 25
3.5.2 Types 26
3.5.3 Constants 28
3.5.4 URLs 28
4.1 Styles 31
4.2.3 Printing 44
4.3 Text 46
4.4 Sheet 64
4.5 Drawing 84
5. Code Complete 99
5.1 Text 99
Contents 5
A.1 UML 125
Glossary 131
Introduction
7
4 Chapter 4 contains examples of building blocks. These short programs can be used
stand-alone or as parts of bigger applications.
4 Finally, Chapter 5 features several larger programs that show you how to put the
building blocks together.
Introduction 9
how to use StarBasic, but you can use StarScript (aka ECMAScript), C(++) or Java as
well.
Although in this tutorial we will focus on the usage of StarBasic, you will be able to
use the examples as patterns for developing StarOffice API Java applications as well.
This language is particularly important because it permits you to access and provide
JavaBeans. These are components used by the StarPortal. With Java, you could thus
integrate a StarOffice Writer Bean in your own applet.
StarBasic is similar to other dialects of BASIC, for example the one used by Microsoft
in their office products. It provides some object orientation and most simple data
types like real and integer number, booleans, strings and arrays. StarBasic offers
some shortcuts for StarOffice API so that it might be easier to use than other
programming languages.
4 Literal text
4 File names
4 Programming examples
Letter Meaning
a Structure
b Boolean (TRUE or FALSE)
e Enumeration. This variable can only have
one of a limited set of values.
f Float or double
m Array (aka sequence)
n Integer or long
o Object, service, or interface
s String
x Interface, to indicate that only operations of
a particular interface of an object are used
v Variant, Any
Introduction 11
12 StarOffice Programmer’s Tutorial ♦ May 2000
CHAPTER 2
13
Figure 2–1 StarOffice API service concept
The Car service implements another service called GearChange. This service can be
implemented by either AutomaticGear or ControlGearEach of these services
contains one interface exporting different methods depending on the type of
2.2 Modules
Modules group services, interfaces, types, enumerations and data structures. Some
StarOffice API modules are text, sheet, table, and drawing. Although they
correspond with certain parts of StarOffice, there is no strict one-to-one relationship
between modules in StarOffice API and StarOffice components: modules like style
and document provide generic services and interfaces that are not specific for one
part of StarOffice.
This chapter explains how to obtain a service. Every StarOffice API program has to
do this at least once, so this is a very important aspect. Furthermore, we’ll show you
how to set and get properties, how to access collections and how to handle events.
You will finally read about the StarOffice Reference Manual and how to read it.
17
"com.sun.star". The following part is the name of the module proper, for example
text or sheet. More accurately, com.sun.star.text is the complete name of the
module. The last part of the parameter is the service itself. This naming reflects the
hierarchical structure of StarOffice API. Module com.sun.star contains the module
text which itself contains a service TextDocument.
To get access to the ColorTable, you’d use
Dim oColorTable As Object
oColorTable=createUnoService("com.sun.star.drawing.ColorTable")
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = _
"file:///home/testuser/Office52/work/test.sdw"
oDocument = _
oDesktop.loadComponentFromURL(sUrl,"_blank",0,mNoArgs())
As you can see, you first obtain the Desktop() service. You then use the
loadComponentFromURL( ) method of its XComponentLoader interface to open
an existing document.
Alternatively, you could create a new StarOffice Calc or StarOffice Writer document
like this:
Dim oSpreadsheetDocument As Object
Dim oTextDocument As Object
The format and meaning of URLs used in StarOffice API are explained in Section
3.5.4 “URLs” on page 28.
When StarOffice API opens a document, it recognizes its type either from its file
name (for existing documents) or from the name of the factory (for new documents).
To open a file with a filter enforcing a certain format, cf. Section 4.2.1 “Importing
other Formats” on page 41.
Finally, you can use an active document to obtain access to its interfaces:
Dim oText As Object
oDocument = ThisComponent
oText = oDocument.Text
oRectangleShape = _
oCalcDocument.createInstance("com.sun.star.drawing.RectangleShape")
oStyle=oDocument.createInstance("com.sun.star.style.ParagraphStyle")
3.2 Properties
Properties (also called attributes sometimes) are values that determine the
characteristics of a service. For example, the fill color is a property of the
ShapeDescriptor service. Some services have a fixed set of properties (again,
ShapeDescriptor is an example). Others use varying property sets, since certain
properties need not always exist.
Properties are "name-value" pairs. The "name" is the name of the property, while
"value" contains its current value. For example, FillColor is the name of the
property describing the fill color in a ShapeDescriptor and its value can be
something like RGB(255,0,0)( ) (which is full red). If the number of properties is
fixed, you can use simple assignments in StarBasic to set or retrieve their values:
Dim nOldColor As Long
nOldColor = oRectangleShape.FillColor
oRectangleShape.FillColor = RGB(255,0,0)
REM ...
oRectangleShape.FillColor = nOldColor
In this example, we save the current setting of the fill color in nOldColor and then
change the fill color to red. Later, the old color is restored.
Sometimes you are not dealing with single properties, but with a sequence of them.
Such sequences are implemented as arrays in StarBasic. For example, to open a
mFileProperties(0).Name="FilterName"
mFileProperties(0).Value="swriter: StarWriter 5.0"
mFileProperties(1).Name="AsTemplate"
mFileProperties(1).Value=true
Note that in StarBasic’s Dim() statement you specify the highest array index, not the
number of elements. See Section 3.5.2.2 “Structures” on page 26 for related
information.
1. Each element has a name, like the tables in a spreadsheet (named access).
2. Elements have no names but can be accessed by index.
3. Elements have no names and can be accessed only in sequential order
(enumeration access).
Some containers provide several access methods. For instance, tables in a sheet
document can be addressed either by name or by index.
oSheets = oCalcDocument.Sheets
oSheet = oSheets.getByName("Sheet1")
Dim oStyleFamilies As Object
oStyleFamilies = oDocument.StyleFamilies
oParagraphStyles = oStyleFamilies.getByName("ParagraphStyles")
stores the table with the name "Sheet1" in oSheet. We assume here that oSheets is
a collection of spreadsheets. The next lines copy the names of all paragraph styles to
oParagraphStyles.
To find out if a named collection contains a certain element, you can use the
hasByName( ) method. It returns TRUE if the element exists, FALSE otherwise.
While the methods mentioned so far are available for all objects providing the
XNameAccess( ) interface, some of the more advanced facilities are only available
with the XNameContainer( ) interface. It provides two methods to add and remove
elements by name. To add a new element to a name container, you’d use
insertByName( ) like this:
oParagraphStyles.insertByName("myParagraphStyle", oStyle)
Please note that some interfaces might overwrite the insertByName( ) method with
its own version, requiring additional parameters. You should therefore always check
the reference manual. A third interface, namely XNameReplace( ), permits you to
replace existing elements:
oParagraphStyles.replaceByName("myParagraphStyle", oStyle)
Notice that the object you replace the old one with must exist before you can use
replaceByName().
This code gets the collection of sheets from the current document and assigns the
first of them to oSheet. It will work only if the current document is a spreadsheet.
The simple usage of parentheses to access an indexed collection is some syntactic
sugar provided by StarBasic. The complete method call would be oSheet =
oSheets.getByIndex(0)( ).
For i%=0 To 2
oSheet=oSheets(i%)
REM do something with oSheet
Next i%
You can determine the number of elements in an indexed collection with the
getCount( ) method:
For i%=0 To oSheets.getCount() - 1
REM do something with oSheets(i%)
Next i%
To add a new element to an indexed collection, you can use the method
insertByIndex(), if the service provides the XIndexContainer interface. To
append an element, use the highest index plus one. In some cases,
insertByIndex() might require additional parameters, so you should always
make sure how to call it by checking the StarOffice API Reference Manual for the
particular service. Similarly, to remove an element you use removeByIndex(),
provided that the service provides the XIndexContainer interface.
Finally, you can replace an element with another object if the object provides the
XIndexReplace() service.
oList.replaceByIndex(0,oNew)
This replaces the first element of oList with oNew, which you must have created
before you can call replaceByIndex().( )
This code can be used to step through all paragraphs in a text document.
3.5.1 Properties
All properties are accessed directly by name in StarBasic. To set the fill color of a
rectangle, you write for example
oRectangleShape.fillColor = RGB(255,0,0)
sFont = oStyle.CharFontName
3.5.2 Types
StarOffice API implements its own datatypes. These types fall in three categories:
enumerations, structures, and sequences. We’ll explain each of these categories in the
following sections.
3.5.2.1 Enumerations
Enumerations are sets of named constants. For example, an enumeration color
might consist of red, blue, and green. A variable of type color could only
represent one of these values. Usually, the values are small integers (0, 1, ...), but this
is an implementation detail. You should always use the name of the enumeration
constant, never the number itself. Enumerations are represented like this in the
StarOffice API Reference Manual:
enum VerticalAlignment: VerticalAlignment
Field Summary
TOP
MIDDLE
BOTTOM
3.5.2.2 Structures
The use of structures is straightforward. You append the name of the structure
element to the variable name like for properties:
Dim aProperty As New com.sun.star.beans.PropertyValue
aProperty.Name="Font"
aProperty.Value="Times"
Here aStruct is a structure with at least one element element1. This element is set
to value. The whole structure is then assigned to the structMember1 of oObj,
assuming that oObj is a structured object. Trying to assign a value to an element
inside an object’s struct in one step will not work in StarBasic. Consequently, you
can’t say
REM The following code will NOT work
oObj.structMember1.element1 = value
3.5.2.3 Sequences
sequence <aType> before a value means that you have to provide an array of
values or that an array of values is returned. For example sequence <string>
getAvailableServiceNames() means that the method
getAvailableServiceNames( ) returns an array of strings. To step through it,
you’d use something like
Dim n As Integer
Dim mServiceNames As Variant
mServiceNames=oDocument.getAvailableServiceNames()
For n = LBound(mServiceNames) To UBound(mServiceNames)
print mServiceNames(n)
Next n
Objects like mPropertyArray are defined in StarBasic using the new( ) operator
and the name of the object:
Dim mPropertyArray(9) As New com.sun.star.beans.PropertyValue
If a method expects a sequence as input parameter, you have to pass it with trailing
empty parentheses in StarBasic, for example
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = "file:///home/testuser/Office52/work/csv.doc"
mFileProperties(0).Name = "FilterName"
mFileProperties(0).Value = _
"scalc: Text - txt - csv (StarCalc)"
oDocument = oDesktop.loadComponentFromURL(sUrl,"_blank",_
0,mFileProperties())
3.5.3 Constants
Similarly, constants are used in StarBasic by writing down their full name. For
example, the Reference Manual for text module contains this part
constants ControlCharacter
{
const short PARAGRAPH_BREAK = 0;
const short LINE_BREAK = 1;
const short HARD_HYPHEN = 2;
const short SOFT_HYPHEN = 3;
const short HARD_SPACE = 4;
};
3.5.4 URLs
Some functions expect a URL as a parameter, for example
loadComponentFromUrl( ). Most URLs are written as usual. However, to specify a
file on a Windows machine, you have to provide the drive like this: "file:///E|/
...". "E" is the drive letter.
Some URLs are used to create empty documents of a certain type. For example
"private:factory/swriter" is an URL that causes StarOffice API to create an
empty StarOffice Writer document. All these URLs begin with private:factory:.
This chapter explains some basic techniques useful for StarOffice API applications.
Some of the examples are not standalone programs but building blocks which you
can fit together with others to build an application. They show you how to solve the
basic tasks in each StarOffice component. We’ll occasionally use UML diagrams to
illustrate the interfaces and services. Appendix A explains the meaning of these
diagrams.
4.1 Styles
Styles are collections of formatting attributes that are accessible under a common
name. If you have ever worked with a word processor, you are probably already
familiar with styles: You use a heading1 style for first level headings, heading2
for second level headings, and body or standard for normal text. If you alter one
aspect of the style, all paragraphs using it change as well. This makes it possible to
change the font for all level one headings from Times to Helvetica or to change the
font size for all third level headings with one single command.
Usage of styles is not limited to paragraphs, nor even to text documents. Frames,
cells in a spreadsheet, graphics shapes, even single characters can be formatted with
styles. Since they are ubiquitous, we present them here rather then in the sections on
text or spreadsheets. Some of the examples here will contain code that becomes clear
only later when you read the section on the corresponding module.
31
4.1.1 Style basics
For the following examples, we will assume that you have a small text document
containing a headline and just one paragraph of text. It can be created like this:
Global oDesktop As Object
Global oDocument As Object
Global oText As Object
Global oCursor As Object
Global oStyleFamilies As Object
Global oParagraphStyles As Object
Global oStyle As Object
Global n As Integer
Sub style_init
Dim mNoArgs() REM Empty Sequence
Dim sMyText As String
Dim sUrl As String
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = "private:factory/swriter"
oDocument = oDesktop.LoadComponentFromURL(sUrl,"_blank",0,mNoArgs)
oText = oDocument.Text
sMyText = "A very short paragraph for illustration only"
oCursor = oText.createTextCursor()
oText.insertString(oCursor,"Headline",FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,FALSE)
oText.insertString(oCursor,sMyText, FALSE)
End Sub
The new document contains two paragraphs, both formatted with the standard
style. Don’t worry about the details here, the meaning of insertString() and the
other methods will be explained in the section on text. To change the first paragraph
so that it uses a heading style, you’d use these lines:
oCursor.gotoStart(FALSE)
oCursor.gotoEndOfParagraph(TRUE)
oCursor.paraStyle = "Heading"
This code first selects the first paragraph, which contains only the single word
Headline, by moving the cursor to the start of the document and then to the end of
this paragraph. We’ll explain cursors in more detail later (see Section 4.3.2 “Moving
around” on page 51). Finally, the paraStyle property of the paragraph is set to the
name of the heading style. This last step effectively applies the style to the
paragraph.
The approach just shown works if you are certain about the predefined paragraph
styles. However, in a German version of StarOffice, a heading style would be called
Überschrift or Titel. If you are not sure which paragraph styles are available,
you can find out by:
Dim sMsg As String
oStyleFamilies = oDocument.StyleFamilies
oParagraphStyles = oStyleFamilies.getByName("ParagraphStyles")
The styles are grouped in named families. Which families are available depends on
the type of document you are working on. For example, text documents provide
PageStyles, CharacterStyles, FrameStyles, and NumberingStyles besides
the ParagraphStyles. A spreadsheet, on the other hand, knows about
CellStyles and PageStyles families only.
To get the names of all style families available in a document, you can use this code:
Dim mFamilyNames As Variant
sMsg=""
oStyleFamilies = oDocument.StyleFamilies
mFamilyNames = oStyleFamilies.getElementNames()
For n = LBound(mFamilyNames) To UBound(mFamilyNames)
sMsg=sMsg + mFamilyNames(n) + " "
Next n
MsgBox sMsg,0,"StyleFamilies"
To summarize: If you want to be sure that a certain style exists, you must
oStyleFamilies = oDocument.StyleFamilies
oParagraphStyles = oStyleFamilies.getByName("ParagraphStyles")
nFontSize = -1
For n = 0 To oParagraphStyles.Count - 1
oStyle = oParagraphStyles(n)
sFontName=lCase(oStyle.charFontName)
If ( sFontName = "helvetica" And _
oStyle.charHeight > nFontSize) Then
nFontSize = oStyle.charHeight
sFoundStyle = oStyle.Name
oCursor.paraStyle = sFoundStyle
Exit For
End If
Next n
You should be aware that this example might not produce exactly what you want. It
will certainly find the paragraph style providing the tallest bold Helvetica setting, if
such a style exists at all. However, if all paragraph styles use a different font (e.g.
Arial), it will use the style heading for the first paragraph. This approach certainly
fails if heading is not defined. Furthermore, although the style found by this
program provides the tallest bold Helvetica setting, but it might have other
attributes that you don’t like. For example, the alignment could be defined as
centered or right, while you’d rather have a left aligned heading. Defining your own
style overcomes these shortcomings.
First of all, this example gets the named collection of all paragraph styles. It then
creates a new paragraph style using createInstance( ) and adds it as new
element to the collection with insertByName( ). Note that this must be done before
you can set the properties for the new style. Those properties are then defined so that
a 36 point bold Helvetica is used. We set CharAutoKerning because kerning should
always be used at this point size. The paragraph will be left-adjusted, and we don’t
want any indentation. The breakType property specifies if a page or column break
occurs before this paragraph, after it, both before and after, or not at all. It is set to
"before" here, because we want each myheading heading to start on a new page.
This last point is fairly important. If you have ever worked with StarWriter, you may
have inserted a page break manually via Insert/Manual Break. The same dialog
offers to insert a line break, a column break or a page break. While a line break is
represented by a control character (see Section 4.3.4 “Inserting paragraph breaks,
special characters, and page breaks” on page 53), column and page breaks are
actually properties of a paragraph. They can be set for the paragraph style or directly
for the paragraph itself. In the first case they apply to all paragraphs with this style,
in the latter case they influence only the current paragraph.
When all properties for the new heading are set, it is assigned to the paragraph as in
the previous section.
Hard formatting is generally considered bad practice because it makes changes very
difficult. Suppose you had a large document with captions of figures like this:
Figure 1.1: Descriptive Text. In order to make the Figure 1.1: part stand
out, you have it hard formatted as bold. The first time your boss sees the
document, she tells you to change all these captions to use normal weight for the
whole text - who’ll have to walk through your document manually to modify every
single caption. Had you soft formatted them, using a character format for the first
part of the caption, you could have simply changed that.
Having said that, we will show you how to hard format text in StarOffice API. Since
many documents contain hard formats, you should know how to create and change
them. We assume the document has been created as described before and contains
only the headline and the single paragraph .
The preceding piece of code changes the word very to bold. As you can see, most of
the code is needed to position the cursor so that it addresses the desired word.
Changing it to bold is just a simple assignment.
As you have seen above, you can format a paragraph in a certain style by assigning
the style’s name to the paraStyle property of the paragraph. Of course, you can
also inquire a paragraph’s style. However, the result of this inquiry might require
some interpretation if the paragraph contains hard formatting. If you change the
previous code so that it looks like this:
oCursor.gotoStart(FALSE)
oCursor.gotoNextParagraph(FALSE)
oCursor.gotoEndOfParagraph(TRUE)
msgbox "Style: " + oCursor.paraStyle _
+ Chr(13) + "Font: " + oCursor.charFontName _
+ Chr(13) + "Weight: " + oCursor.charWeight
oCursor.gotoStartOfParagraph(FALSE)
oCursor.gotoNextWord(FALSE)
oCursor.gotoEndOfWord(TRUE)
oCursor.charWeight = com.sun.star.awt.FontWeight.BOLD
oCursor.gotoStartOfParagraph(FALSE)
oCursor.gotoEndOfParagraph(TRUE)
msgbox "Style: " + oCursor.paraStyle _
+ Chr(13) + "Font: " + oCursor.charFontName _
+ Chr(13) + "Weight: " + oCursor.charWeight _
+ Chr(13) + "Weight(Default): " + oCursor.getPropertyDefault("CharWeight")
Both message boxes will display the style name, the font name and a weight of
usually 100, which is the value of the constant
This code does not reset any italic portions to upright (or vice versa), you’d need to
reset the property CharPosture for this.
Note - Please note that the strings passed to setPropertyToDefault( ) are case
sensitive, charFontName would not work in the sample code above.
oPageStyles = oStyleFamilies.getByName("PageStyles")
oStdPage = oPageStyles.getByName("Standard")
oStdPage.HeaderOn = TRUE
oStdPage.FooterOn = TRUE
Here we enable headers and footers for the Standard page style. To have the
header display some text, you use something like this:
Dim oHeader As Object
oHeader = oStdPage.HeaderText
This is obviously the simplest thing to do; it will display the text "My Header" in the
header on every page. If you want some fancy formatting, you have to modify the
style used for the header as in the next lines of code:
Dim oHeaderText As Object
Dim oHeaderCursor As Object
oHeaderText = oHeader.Text
oHeaderCursor = oHeaderText.createTextCursor()
oParagraphStyles = oStyleFamilies.getByName("ParagraphStyles")
oStyle = oParagraphStyles.getByName(oHeaderCursor.paraStyle)
oStyle.CharPosture = com.sun.star.awt.FontSlant.ITALIC
As you can see, it is easy to get at the style used for the header. Its name is found in
the paraStyle property of the XTextCursor interface for the header. Once you
know the name of the style (which depends on the local language settings), you can
change its properties as shown before. The sample above changes the CharPosture
property so that the header text appears in italics.
While a static header is appropriate to show the title of a document, there a more
dynamic data that might you want to appear in the footer. One of them is obviously
the page number. To have it shown in the footer, you can write something like the
following:
Dim oFooter
Dim oFooterText As Object
Dim oFooterCursor As Object
Dim oPageNumber As Object
oFooter = oStdPage.FooterText
oFooterText = oFooter.Text
oFooterCursor = oFooterText.createTextCursor()
oFooterText.insertString(oFooterCursor,"Page ", FALSE)
oPageNumber = _
oDocument.createInstance("com.sun.star.text.TextField.PageNumber")
oPageNumber.NumberingType = _
com.sun.star.style.NumberingType.ARABIC
oFooterText.insertTextContent(oFooterCursor, oPageNumber, FALSE)
The first few lines are similar to the previous examples showing the usage of
headers. We then insert the string Page in the footer and create the text field
pageNumber which is inserted right afterwards. Its NumberingType property is set
so that the page numbers are displayed using arabic digits. To learn more about text
fields, refer to Section 4.3.7 “Inserting tables, frames, etc.” on page 58.
If you have more than one page in your document, you will notice that the page
number inserted with the code above appears always at the left margin. Typically,
one would want the even numbers to appear at the left margin and the odd numbers
at the right margin. To accomplish this, you have to use a left and a right footer like
this:
oStdPage.FooterShareContent=TRUE
oFooterLeft = oStdPage.FooterTextLeft
oFooterTextLeft = oFooterLeft.Text
oFooterCursorLeft = oFooterTextLeft.createTextCursor()
oFooterTextLeft.insertString(oFooterCursorLeft,"Page ", FALSE)
oPageNumber = _
oDocument.createInstance("com.sun.star.text.TextField.PageNumber")
oPageNumber.NumberingType = com.sun.star.style.NumberingType.ARABIC
oFooterTextLeft.insertTextContent(oFooterCursorLeft, oPageNumber, FALSE)
oFooterRight = oStdPage.FooterTextRight
oFooterTextRight = oFooterRight.Text
oFooterCursorRight = oFooterTextRight.createTextCursor()
oFooterTextRight.insertString(oFooterCursorRight,"Page ", FALSE)
oPageNumber = _
oDocument.createInstance("com.sun.star.text.TextField.PageNumber")
oPageNumber.NumberingType = com.sun.star.style.NumberingType.ARABIC
oFooterTextRight.insertTextContent(oFooterCursorRight, oPageNumber, FALSE)
This does not introduce anything really new. Instead of using the FooterText
property of the Standard page style, you use FooterTextLeft and
FooterTextRight. If you run this code, you’ll notice that the page numbers appear
twice on each page. To have the FooterTextRight appear only on right (odd)
pages, you would have to set the page style’s FooterShareContent property to
FALSE.
With this value, the page number appears only once on every page. However, it is
flush left on even and odd pages, and one would rather want it to appear at the
right margin on an odd page. To put the odd page numbers at the right margin, you
must use a tab stop:
Dim newstops (0) As Object
Dim tabStop As New com.sun.star.style.TabStop
Dim oFooterStyle As Object
Dim h As Long
oFooterStyle = _
oParagraphStyles.getByName(oFooterCursorRight.paraStyle)
h = oStdPage.Size.Width - oStdPage.LeftMargin - _
oStdPage.RightMargin
tabStop.position = h
tabStop.alignment = com.sun.star.style.TabAlign.RIGHT
newstops(0) = tabStop
oFooterStyle.paraTabStops = newstops()
Tab stops belong to a paragraph style, so we have to get the style used for the right
footer first. We then calculate the width of the footer line by subtracting the page’s
left and right margin from its width and store this value in h. The position of the tab
stop is set to this value and right alignment is specified for it. The footer style’s
paraTabStops property is then overwritten with an array which contains only the
Note the usage of Chr(9) in the call to insertString( ) to insert a literal tab
character.
Finally, we will show you how to include the title of the current chapter in the footer
of each page. First of all, you have to create a text field that contains the chapter’s
title:
Dim oChapterField As Object
oChapterField = _
oDocument.createInstance("com.sun.star.text.TextField.Chapter")
oChapterField.Level = 0
oChapterField.chapterFormat = 1
The preceding lines create a text field which uses the heading for numbering level 0
(the top level) and includes the heading’s text (this is achieved by setting the
chapterFormat property). StarOffice API has certain default settings associating
paragraph styles with numbering levels. To be sure that it uses the style you want,
you have to set it like this:
Dim oChapterSettings As Object
Dim mLevel As Variant
Dim vProperty As Variant
oChapterSettings = oDocument.ChapterNumberingRules
mLevel = oChapterSettings.getByIndex(0)
For n = LBound(mLevel) To UBound(mLevel)
vProperty = mLevel(n)
If (vProperty.Name = "HeadingStyleName") Then
vProperty.Value = "Heading 1"
End If
mLevel(n) = vProperty
Next n
oChapterSettings.replaceByIndex(0,mLevel)
This piece of code retrieves the current settings for numbering level 0, which is the
one used in the text field above. It then loops over all properties until it finds the one
called HeadingStyleName. Its value is then set to Heading 1 and the chapter
This code inserts the text field with the chapter name at the left bottom and the
page number after a tab character at the right margin. This is ok for a right (odd)
page. You’d have to insert the fields for chapter and page the other way round for
the left (even) footer.
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = "file:///home/testuser/Office52/work/csv.doc"
mFileProperties(0).Name = "FilterName"
mFileProperties(0).Value =_
"scalc: Text - txt - csv (StarCalc)"
oDocument = oDesktop.loadComponentFromURL(sUrl,"_blank",_
As before, we use the Desktop() service to open the document. The important part
here is the property set mFileProperties. It contains only the property
"FilterName" with the value <factory>: <filtername>. Here, <factory> is one
of swriter, scalc, simpress, sdraw, smath, simage. The
<filtername> can be found in the file install.ini. In the future, you will be
able to retrieve it from the Registry( ) service of StarOffice API.
To use a document that contains a foreign file format, you may have to provide the
FilterFlags property to loadComponentFromUrl( ) to ensure that the
document is imported in a certain way. A CSV file, for example, can use commas or
semicolons to separate fields or it may use fields of fixed width. FilterFlags is
needed to specify these details. The value of this property is a single string that
contains all required information. For a CSV file, it is made up of five tokens,
separated by comma. The tokens are
1. Field separator(s) as ASCII values or the three letters FIX. If your fields are
separated by commas, this token is 44, if they are separated by semicolons and
tabs, the token would be 59/9. To treat several consecutive separators as one,
append the four letters /MRG to this token. If the file contains fixed width fields,
use the three letters FIX as the token.
2. The text delimiter as ASCII value. Use 34 for double quotes and 39 for single
quotes.
3. The character set in the file as string.
4. Number of the first line to convert. Set this to something other then 1 if you want
to skip lines at the beginning of the file.
5. This last token is the most complicated one. Its content depends on the value of
the first token.
sUrl = "file:///home/ck/ix/ix0399/javaperf/jview.csv"
mFileProperties(0).Name = "FilterName"
mFileProperties(0).Value = "scalc: Text - txt - csv (StarCalc)"
mFileProperties(1).Name = "FilterFlags"
mFileProperties(1).Value = "44,34,SYSTEM,1,1/1/1/1/1/1/1/1"
oDesktop = createUNOService("com.sun.star.frame.Desktop")
oDocument = oDesktop.loadComponentFromURL(sUrl,_
"_blank",0,mFileProperties())
End Sub
saves the document under its original name. This works only if it existed before you
worked on it or if you have already saved a new document. To save a new
document for the first time, you’ll use
sUrl = "file:///complete/path/To/New/document"
mFileProperties(0).Name = "Overwrite"
mFileProperties(0).Value = FALSE
oDocument.storeAsURL(sUrl, mFileProperties())
This method will save the document in its native StarOffice format, but it will not
overwrite a possibly existing file of the same name. To replace existing files, you’ll
have to set the overwrite parameter to TRUE. To save the document in a foreign
format, you’ll have to specify a FilterName property as shown above in Section
4.2.1 “Importing other Formats” on page 41.
To make a save-document subroutine more robust, you can use two other properties
that help you decide what to do:
oDocument = ThisComponent
If (oDocument.isModified) Then
If (oDocument.hasLocation And (Not oDocument.isReadOnly)) Then
oDocument.store()
Else
oDocument.storeAsURL(sURL, mFileProperties())
End If
End If
This sample code checks first if the current document has been modified - there is no
need to save anything if it has not been changed. The sample chooses between
store( ) and storeAsURL( ) depending on two criteria: In order to use store( ),
the document must have been saved before and it must be writeable. Only if both
conditions are met, store( ) can be used to save the document. In all other cases, a
new file is created. You’ll have to specify the correct values for the parameters URL
and mFileProperties before you can use this code.
4.2.3 Printing
Printing is not related to one particular type of document but it is still most
important for text. The interface xPrintable is contained in the service
OfficeDocument since all office documents have to provide printing.
oDocument.Print(mPrintopts1())
Now suppose you wanted to print only the first two pages:
Dim mPrintopts2(0) As New com.sun.star.beans.PropertyValue
mPrintopts2(0).Name="Pages"
mPrintopts2(0).Value="1-2"
oDocument.Print(mPrintopts2())
As usual, you define a property set and define the property Pages in it. To print
single pages, separate them with a semicolon:
mPrintopts2(0).Name="Pages"
mPrintopts2(0).Value="1-3; 7; 9"
mPrinter(0).Name="Name"
mPrinter(0).value="Other printer"
oDocument.Printer = mPrinter()
Then you can use print( ) as before or set print options first. Other useful printer
options are
4 CanSetPaperFormat; this boolean indicates if you can use the PaperFormat
property to change the paper format.
4 CanSetPaperOrientation; this boolean indicates if you can use the
PaperOrientation property to change the paper format.
4 IsBusy; this boolean informs you if the printer is busy.
4 PaperFormat; a constant selecting the paper format, use
com.sun.star.view.PaperFormat.USER for a user defined format.
4 PaperOrientation; an integer selecting the paper orientation,
com.sun.star.view.PaperOrientation.PORTRAIT or
com.sun.star.view.PaperOrientation.LANDSCAPE
4 PaperSize; if a user defined format has been set with the PaperFormat
property, specify the desired size here in 100ths of a millimeter. The size is a
structure of type com.sun.star.awt.Size with the components Width and
Height
The first three properties in this list are read-only - you can not set them for obvious
reasons.
4.3 Text
In this section, you’ll learn about some basic operations for text: Inserting and
retrieving it, navigating through it, searching and replacing text, importing foreign
formats and printing text documents.
Sub textdoc_init
Dim mNoArgs() REM Empty Sequence
Dim sUrl As String
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = "private:factory/swriter"
REM Or: sUrl = "file:///home/testuser/office52/work/text.sdw"
oDocument = oDesktop.LoadComponentFromURL(sUrl,"_blank",0,mNoArgs)
oText = oDocument.Text
End Sub
The paragraphs of a document are neither named nor indexed so that you must
access them sequentially starting with the first paragraph. Although tables are
available in their own named collection they are always anchored to the preceding
oTextEnum = oDocument.Text.createEnumeration
While oTextEnum.hasMoreElements()
oTextElement = oTextEnum.nextElement()
REM Do something with the paragraph Or table
Wend
This piece of code will move you through all paragraphs and tables in the order in
which they appear in the document. It creates an enumeration of the objects in the
XText( ) interface with createEnumeration( ). Then it uses
hasMoreElements( ) and nextElement( ) to iterate through this numeration.
nextElement( ) does two things at the same time: it returns the next element of the
enumeration and it moves beyond it. To skip tables, check each element for the
Paragraph( ) service like this:
If oTextElement.supportsService(_
"com.sun.star.text.Paragraph") Then
REM Code that works With paragraph only
End If
oTextPortionEnum = oTextElement.createEnumeration()
While oTextPortionEnum.hasMoreElements()
oTextPortion = oTextPortionEnum.nextElement()
REM Do something With the Text portion
Wend
Some examples:
4 If the text of a document’s paragraph is formatted with the same attributes (font,
font weight, color etc.), the enumeration contains just one text portion which is the
complete paragraph.
4 If you have one bold word in the middle of a document’s paragraph, the
enumeration will contain three text portions: First the text up to the bold word,
then the bold word itself, and finally the rest of the paragraph.
The font name for oTextElement is the one of the style used for this paragraph.
Each text portion may or may not override this default by specifying its own font
name. The actual font name for a portion is stored in its charFontName property.
n = oTextPortion.getPropertyState("CharFontName")
oText = oDocument.Text
oCursor = oText.createTextCursor()
In these cases, the cursor position is the text range specified by a single character.
You can move around in the document and change the existing text by replacing it.
To do so, you must eventually set the last parameter of the movement functions to
TRUE, like in the following example.
oText = oDocument.Text
oCursor = oText.createTextCursor()
oCursor.gotoStart(FALSE)
oText.insertString(oCursor,"A piece of New Text And another one.",_
FALSE)
oCursor.gotoStart(FALSE)
oCursor.gotoNextWord(FALSE)
oCursor.gotoEndOfWord(TRUE)
oText.insertString(oCursor,"chunk",TRUE)
The current position is first set to the beginning of the document and text is inserted.
The cursor is then moved to the beginning of the next word. Now we "span" the
textRange with oCursor.gotoEndOfWord(TRUE)( ), so that it extends from the
last position (the first character of "piece") to the current one (the last character of
"piece"). Finally, this span of text is replaced with the word "chunk".
insertString( ) replaces the text span, because its last parameter is TRUE. If you
wanted to insert text after the end of the spanned region, you’d set this value to
FALSE.
A region spanned by a cursor survives a call to insertString( ). To switch back
to normal cursor movement (i.e. withouth spanning a region), you have to use
collapseToEnd() or collapseToStart( ). The first function sets the current
cursor position to the end of the range, the second function sets it to the start.
The control characters are actually constants that can be inserted in your program
code by prepending com.sun.star.text.ControlCharacter. to the name of
the character to insert, as shown above in the short examples. The following code
segment illustrates the use of all control characters.
oCursor = oText.createTextCursor()
oCursor.gotoStart(FALSE)
oText.insertString(oCursor,"My first piece of Text",FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,FALSE)
oText.insertString(oCursor,_
"second Line In first paragraph", FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.LINE_BREAK,FALSE)
oText.insertString(oCursor,"Second paragraph", FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, FALSE)
oText.insertString(oCursor,_
"First Line In 3rd paragraph,", FALSE)
oText.insertString(oCursor," a fixed hyphen", FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.HARD_HYPHEN,FALSE)
oText.insertString(oCursor,", a soft hyphen,", FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.SOFT_HYPHEN,FALSE)
oText.insertString(oCursor," And a fixed space", FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.HARD_SPACE,FALSE)
oText.insertString(oCursor,"between two words", FALSE)
oText.insertControlCharacter(oCursor, _
com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,FALSE)
oText.insertString(oCursor,"New page starts here",FALSE)
oCursor.gotoStartOfParagraph(TRUE)
oCursor.breakType = com.sun.star.style.BreakType.PAGE_BEFORE
This piece of code creates a SearchDescriptor and then sets the string to search
for to the word find. You have to start searching like this:
oFound = oDocument.findFirst( oSearchDesc )
Do While mFound
REM Do something With the Text
oFound = oDocument.findNext( oFound.End, aSearch)
Loop
findNext( ) returns the next range of text that contains the search string. It starts at
the text range specified in its first parameter. We use oFound.End here, which is the
end of the last text found. End is a property of the XTextRange( ) interface. Since
findNext( ) returns an XTextRange() interface, you can use all properties and
methods available with it. That makes it easy to change attributes. To change all
appearances of the word "Bauhaus" in a document so that it is displayed in the font
with the same name, you’d use something like this:
Two additional properties of the search descriptor ensure that "Bauhaus" is changed
only when it appears on its own and when the letter case match exactly (i.e.
"bauhaus" would not be changed).
Instead of walking through the whole document with findNext( ), you can use
findAll() to get a sequence of text ranges where the search string occurs:
oFound = oDocument.findAll( oSearch )
For n = 0 To oFound.Count - 1
REM Do something With the oFound.getByIndex(n)
Next n
oSearchDesc = oDocument.createSearchDescriptor()
oSearchDesc.SearchString = "^\[[0-9]+\]"
oSearchDesc.SearchRegularExpression = TRUE
oFoundAll = oDocument.findAll( oSearchDesc )
For n = 0 To oFoundAll.Count - 1
oFound = oFoundAll(n)
REM Or: oFound=oFoundAll.getByIndex(n)
oCursor = oText.createTextCursorByRange(oFound)
oCursor.gotoNextWord(FALSE)
oCursor.gotoEndOfParagraph(TRUE)
All lines matching the regular expression are then found with findAll( ). It returns
an XTextRange() interface which is passed to createTextCursorByRange( ).
The new cursor is thus automatically positioned at the matching regular expression.
By moving it to the next word and then to the end of the paragraph, we span a text
range that contains the whole bibliography entry but the leading number. This string
is finally modified with some lines of pure StarBasic. They simply find the semicolons
separating the author and the title and re-assemble the string from these parts.
A table is a special object that you can insert into a text document. You can think of
it as a simple spreadsheet - a very simple one, in fact. First of all, you will see how to
insert a simple table:
Dim oTable As Object
oCursor = oText.createTextCursor()
oCursor.gotoStart(FALSE)
oTable = oDocument.createInstance("com.sun.star.text.TextTable")
oTable.initialize(5,9)
oText.insertTextContent(oCursor, oTable, FALSE)
oTableCursor = oTable.createCursorByCellName(oTable.CellNames(0))
oTableCursor.gotoStart(FALSE)
mTableHeaders(0) = "Field"
mTableHeaders(1) = "Format"
mTableHeaders(2) = "AutoIncrement"
mTableHeaders(3) = "Descending"
mTableHeaders(4) = "PartOfPrimaryKey"
mTableHeaders(5) = "Required"
mTableHeaders(6) = "Scale"
mTableHeaders(7) = "Size"
mTableHeaders(8) = "Type"
For n = 0 To 8
sCellName = oTableCursor.getRangeName()
oCell = oTable.getCellByName(sCellName)
oCell.String=mTableHeaders(n)
oTableCursor.goRight(1,FALSE)
Next n
To move around in a table, you have to create a tableCursor first. The sample
code above uses the method createCursorByCellName() for this and then
moves the cursor to the top left cell of the table. After initializing the array
tableHeaders with the column headers of the table, it moves the cursor to each of
the cells in the first row with goRight( ). The content of the cell is then set by
assigning to its String property.
If you want to insert numbers into a table, you’ll probably not want them to be
displayed left justified, which is the default for all cells. To change the justification
for a cell depending on its content, you can use this code snippet:
s=oCell.String
If IsNumeric(s) Then
oCellCursor = oCell.createTextCursor()
oCellCursor.paraAdjust = com.sun.star.style.ParagraphAdjust.RIGHT
End If
We are using a text cursor here to hard format a cell if it contains a number.
Alternatively, you might define a special style for numbers and assign it to the cell:
oCellCursor.paraStyle="myNumberStyle"
aSize.width = 2000
aSize.height = 1000
oFrame.Size = aSize
oFrame.AnchorType = _
com.sun.star.text.TextContentAnchorType.AS_CHARACTER
oFrame.TopMargin = 0
oFrame.BottomMargin = 0
oFrame.LeftMargin = 0
oFrame.RightMargin = 0
oFrame.BorderDistance = 0
oFrame.HoriOrient = _
com.sun.star.text.HoriOrientation.NONE
oFrame.VertOrient = _
com.sun.star.text.VertOrientation.LINE_TOP
oText.insertTextContent(oCursor, oFrame, FALSE)
Here we begin with two simple text paragraphs and position the text cursor at the
beginning of the first paragraph. Then the frame is created and some properties are
set to reasonable values. In particular, the anchorType is specified as
AS_CHARACTER, meaning that the frame is treated as if it were a character.
Consequently, the line spacing is adjusted if the frame grows vertically. Another
anchorType value is AT_CHARACTER, which simply anchors the frame at a specific
character. The other properties ensure that the new frame is properly placed with
respect to the text line.
oCursor = oFrame.createTextCursor
oCursor.charWeight = com.sun.star.awt.FontWeight.BOLD
oCursor.paraAdjust = com.sun.star.style.ParagraphAdjust.CENTER
oFrame.insertString(oCursor, "New", TRUE)
These four lines insert the centered word New in bold characters into the frame. The
important thing here is to create a text cursor at the frame and to use the
insertString( ) method of the frame as well.
oCursor = oText.createTextCursor()
oText.insertString(oCursor,"Today is the ", FALSE)
oDateTime = oDocument.createInstance("com.sun.star.text.TextField.DateTime")
oDateTime.Fix = FALSE
oText.insertTextContent(oCursor,oDateTime,FALSE)
oText.insertString(oCursor," ",FALSE)
This code creates a single line showing the words Today is the followed by the
current date. You reate the text field with createInstance( ) and insert it at the
cursor position with insertTextContent(). The only property set here ensures
that the date is not fixed. If you save the document and open it two days later, it will
show the actual date.
The current page number is most helpful in the header or footer of a page. In the
next sample, you’ll see how to insert it into the footer of a page style.
Dim oStyleFamlies As Object
Dim oPageStyles As Object, oStdPage As Object
Dim oFooterLeft As Object, oFooterCursor As Object
Dim oFooterText As Object
Dim oPageNumber As Object
oPageNumber = oDocument.createInstance(_
"com.sun.star.text.TextField.PageNumber")
oPageNumber.numberingType = _
com.sun.star.style.NumberingType.ARABIC
oStyleFamilies = oDocument.StyleFamilies
oPageStyles = oStyleFamilies.getByName("PageStyles")
oStdPage = oPageStyles("Standard")
oStdPage.FooterOn = TRUE
oFooterLeft = oStdPage.FooterTextLeft
oFooterText = oFooterLeft.Text
oFooterCursor = oFooterText.createTextCursor()
oFooterText.insertString(oFooterCursor,_
"Page ", FALSE)
oFooterText.insertTextContent(oFooterCursor, _
oPageNumber, FALSE)
As before, the text field for the page number is created with createInstance( ).
The footer is then turned on for the page style Standard - you must do that before
you can access the style’s FooterTextLeft property. Since this is the xText( )
interface of the footer, it provides all the well known methods to insert text. We use
insertString( ) to put the word Page before the number, which is added to the
footer with insertTextContent( ).
oCursor = oText.createTextCursor()
oText.insertString(oCursor,"First paragraph", FALSE)
oText.insertControlCharacter(oCursor,_
com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK, _
FALSE)
oText.insertString(oCursor,"Second paragraph" , FALSE)
oCursor.gotoStart(FALSE)
oCursor.gotoNextWord(FALSE)
oRectangleShape = createRect(1000,1000,2000,500)
oRectangleShape.TextRange = oCursor.start
oRectangleShape.fillColor = RGB(255,0,0)
oRectangleShape.anchorType = _
com.sun.star.text.TextContentAnchorType.AS_CHARACTER
oRectangleShape.HoriOrient = _
com.sun.star.text.HoriOrientation.NONE
oRectangleShape.VertOrient = _
com.sun.star.text.VertOrientation.LINE_TOP
oText.insertTextContent(oCursor,oRectangleShape,FALSE)
As before, we create two dummy paragraphs and position the text cursor at the
beginning of the first word paragraph. The rectangle is then created using a function
which will be introduced later (see Section 4.5.2 “Making things easier” on page 90 ).
The differences begin now: We could insert tables, frames and text fields directly
using insertTextContent( ). The rectangle, however, is created on the DrawPage
associated with the current page of the text document. To create it, you have to
provide its position and dimensions in 100ths of a millimeter. Since you can’t deduce
the position from your text document, you simply specify a dummy position and
link the graphic object to the correct position by setting its TextRange property to
the start position of oCursor. The rectangle is then made visible and attached to the
cursor position by inserting it as before with the insertTextContent() method.
oTextTables = oDocument.getTextTables()
For n = 0 to oTextTables.count - 1
oTable = oTextTables(n)
REM Do something with oTable
Next n
4.4 Sheet
Sheet is the module that contains spreadsheet services. It is used like the text
service, since it needs a document to work with:
Global oDesktop As Object
Global oDocument As Object
Sub sheetdoc_init
Dim mNoArgs() REM Empty Sequence
Dim sUrl As String
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = "private:factory/scalc"
REM Or: sUrl = "file:///home/testuser/Office52/work/table.sdc"
oDocument = oDesktop.LoadComponentFromURL(sUrl,"_blank",0,mNoArgs)
End Sub
In the following examples, we will assume you have opened the document as
described. You’ll learn how to address cells and ranges of cells, how to navigate
through sheets, and how to draw a chart from sheet data.
Rows and columns can be specified numerically, starting with 0, or as strings. In the
latter case, the cell in the upper left corner of a sheet has the address A1. The next
cell to the right is named A2, the one below is B1. A rectangular region of cells is
oSheet1 = oDocument.Sheets(0)
As usual, the first sheet is numbered zero. If you don’t know how many sheets your
document has, use count():
nSheetCount = oDocument.Sheets.Count
oSheets = oDocument.Sheets()
oSheet = oSheets.getByIndex(nSheet)
GetCell = oSheet.getCellByPosition (nColumn , nRow)
End Function
This function returns an XCell interface of the SheetCell service. A range of cells
can be specified like this:
sTopLeft = "b2"
sBottomRight = "d8"
oCellRange = oSheet1.getCellRangeByName(sTopLeft + _
":" + sBottomRight)
would return the cell at the top left corner of the range. All values passed to
getCellByPosition( ) are relative to the range. To get the right bottom cell, you’d
use
nCols = oCellRange.Columns.Count
nRows = oCellRange.Rows.Count
oCell = oCellRange.getCellByPosition(nCols - 1, nRows -1)
Note that we subtract 1 from the number of rows and columns here, because the
numbering starts at zero.
4.4.1.1 Calculations
You can perform some simple calculations on ranges without using a formula.
oRange = GetCellRange(oDocument,0, 1, 0, 1, 5)
nCols = oRange.Columns.Count
nRows = oRange.Rows.Count
For n = 0 To nRows-1
oCell = oRange.getCellByPosition(0,n)
oCell.Value = n
Next n
eSumFunc = _
com.sun.star.sheet.GeneralFunction.SUM
eAvgFunc = _
com.sun.star.sheet.GeneralFunction.AVERAGE
nSum = oRange.computeFunction(eSumFunc)
nAvg = oRange.computeFunction(eAvgFunc)
oCell = GetCell(oDocument,0,0,nRows)
oCell.String = "Sum"
oCell = GetCell(oDocument,0,0,nRows +1)
oCell.String = "Average"
oCellSum = GetCell(oDocument,0,1,nRows)
oCellSum.Value = nSum
oCellAvg = GetCell(oDocument,0,1,nRows + 1)
oCellAvg.Value = nAvg
This program fills all cells in the range B1 through B6 with the numbers 0 through 5
(see for loop). It then computes the sum and the average over these values. Cells A7
and A8 are labeled with Sum and Average, respectively and the calculated values
are then written into the cells B7 and B8. Although the values are as correct as they
can be, this calculation method has a serious disadvantage: Whenever one of the
values used changes, you must recalculate the sum and average yourself. Since this
is precisely the job of a spreadsheet, you should use a formula for this purpose:
oCellAvg.Formula = "=SUM(B1:B6)/"+ CStr(nRows)
This line changes the content of cell B7 so that it contains a formula instead of the
value.
To summarize: To set the content of a cell to text, you use the String property, to
enter a value in a cell, you set the Value property. If you want to put a formula in a
cell, you assign it (including the equals sign) to the Formula property. Note that
function names must be English if you use the Formula property. To use functions
in your local language, you must use the FormulaLocal property. If you want to
know what a cell contains, you can retrieve its Type property:
Sub Printinfo (oCell As Object)
Dim eType as Long
eType = oCell.Type
If eType = com.sun.star.table.CellContentType.VALUE Then
Print CStr(oCell.Value)
Elseif eType = com.sun.star.table.CellContentType.TEXT Then
This piece of code simply outputs the content of a cell as a string. If it is a formula, it
is shown in the English and the local variant.
The key concept here is the NumberFormatter( ) interface. It provides access to all
formats available in the sheet cells and permits you to define new formats as well.
StarOffice API comes with a huge collection of predefined number formats from
Predefined formats can be accessed by language and category. As long as you don’t
want to use a language different from the default, you should always use 0 as value
for the language parameter.
Dim oFormats As Object
Dim mKeys As Variant
Dim oLocale As Object
oFormats = oDocument.NumberFormats
mKeys = oFormats.queryKeys(0, oLocale, FALSE)
These two lines return the list of all available formats, which is usually more then
you really want. To get just the currency formats defined for your language, you
could use
oFormats = oDocument.NumberFormats
mKeys = oFormats.queryKeys(_
com.sun.star.util.NumberFormat.CURRENCY, oLocale, FALSE)
First of all, we initialize the variable nFoundFormat to -1. This is never a valid
format key. If the value is still -1 after we have searched through all formats, we
know that none of them suited our needs. We then loop over the keys array. The
method getByKey( ) returns an array of properties associated with each format.
The next loop iterates over these properties and copies the values of
LeadingZeros, NegativeRed, and ThousandsSeparator in the variables nLZ,
bNR, and bTS, respectively. If these variables have the desired values after the loop
is terminated, the current format’s key is saved in nFoundFormat. When we have
looked at all currency formats, we check whether nFoundFormat is positive. If not,
there was no matching format found and we have to define one ourselves. The
method generateFormat( ) takes care of the next most important step. Despite its
name, it doesn’t generate a new format. Instead, it translates a format definition into
a format string. We pass in all the attributes we want the new format to have as well
as the key of a base format from which it inherits all the non-specified attributes.
We can then use the format string returned by generateFormat( ) to ask
queryKey( ) for a format which matches the string. If it returns -1, no such format
exists and we add a new one by passing the format string to addNew( ). The next
few lines will show you how to apply the new format:
Dim oRange As Object
Dim oCell As Object
oRange = oDocument.Sheets(0).getCellRangeByPosition(0,0,0,2)
oRange.numberformat = nFoundFormat
oCell = oRange.getCellByPosition(0,0)
oCell.Value = 10000.2693
oCell = oRange.getCellByPosition(0,1)
oCell.Value = 0.3
oCell = oRange.getCellByPosition(0,2)
oCell.Value = -20
This code first creates a range spanning the top three cells in the first row. It then sets
the numberFormat property of these cells to the key of the just found or created
format. Finally, three numbers are inserted in the three cells. The first illustrates the
insertion of a thousands separator, the second one will show up without a leading
zero (which is not particularly useful for a currency format), and the last one will be
displayed in red.
We will assume that the numbers are sales for two products.
Sub chart1_sample
chart_init()
oCharts.addNewByName("BarChart",aRect,mRangeAddress(),TRUE, TRUE)
End Sub
The rect used in this example is the area on the physical page where the diagram is
to be drawn. Its position and size are given in 100ths of a millimeter. The rectangle
is thus positioned eight centimeters from the left and one centimeter from the upper
margin, it is 10 centimeters high and wide. There is no direct relationship between
the rectangle in which the diagram is drawn and the position of the cells in the sheet.
The data used to draw the chart is found in the range specified by rangeAddress.
It starts at the upper left corner of the sheet (cell A1) and extends to the cell in the
third column, sixth row (C6). To indicate that the first row and column of this range
contain the labeling for the chart, the last two parameters of addNewByName( ) are
set to TRUE.
oChart = oCharts.getByName("BarChart").embeddedObject
oChart.diagram.DataRowSource = com.sun.star.chart.ChartDataRowSource.COLUMNS
End Sub
chart_init()
oCharts.addNewByName("LineDiagram",aRect,mRangeAddress(),TRUE, TRUE)
oChart = oCharts.getByName("LineDiagram").embeddedObject
oChart.diagram =_
oChart.createInstance("com.sun.star.chart.LineDiagram")
oChart.diagram.DataRowSource = com.sun.star.chart.ChartDataRowSource.COLUMNS
End Sub
This gives you one row of pie charts to the right side of the table - unless you have
changed the default table settings. In this case, the charts may well end up obscuring
part or all of your table. The reason for this is that we are using a fixed starting
point (8000/1000) for the first chart and draw every other chart just below the
preceding one.
The sample code above illustrates one thing we have not mentioned before: Your
data doesn’t have to be contained in consecutive rows or columns. As you can see
above, the second element of rangeAddress changes for each pie, because we need
one pie per row. The first element of rangeAddress remains the same, because it
contains the strings for the legend.
To make chart placement more reasonable, you should figure out the dimensions of
your table and decide on the position of your chart depending on them. To illustrate
this, we will use the same data as before and decide on the chart placement based on
the table’s dimension.
REM ... same code As before
oRange = GetCellRange(oDocument,0,0,0,2,5)
nTableWidth = oRange.Size.Width + oRange.Position.X
nPieRadius = 1500
aRect.X = oRange.Position.X
aRect.Y = oRange.Position.Y + oRange.Size.Height * 1.1
aRect.Width = nPieRadius * 2
aRect.Height = nPieRadius * 2
oDesktop = createUnoService("com.sun.star.frame.Desktop")
sUrl = "private:factory/sdraw"
REM Or: sUrl = "file:///home/testuser/office52/work/image.sdd"
oDocument = oDesktop.LoadComponentFromURL(sUrl,_
"_blank",0,mNoArgs())
oPage = oDocument.drawPages(0)
End Sub
Note that the first URL creates a new document, while the second one (after the "or"
comment) opens an existing document. oDocument now provides the
XDrawPages() interface which in turn is the main entry point for all drawing
functionality. We use the first drawing page here and assume in the following
examples that you have opened or created a document as described.
The draw module differs from other StarOffice API modules we have seen so far in
that in can actually draw on any type of document. It is therefore not necessary to
create an sdraw document or open an sdd file. You could as well create a text
document or open a spreadsheet. The important point is that you get the document’s
drawing page, because all graphics objects are attached to it.
The main concepts in the drawing module are the drawing page and shapes. Shapes
are the actual graphic objects which you see. They are always attached to a drawing
page which is responsible for displaying them. A shape remains invisible as long as
it is not added to a drawing page.
oRectangleShape = oDocument.createInstance("com.sun.star.drawing.RectangleShape")
oRectangleShape.Size = aSize
oRectangleShape.Position = aPoint
oRectangleShape.FillColor = RGB(255,0,0)
oPage.add(oRectangleShape)
oEllipseShape = oDocument.createInstance("com.sun.star.drawing.EllipseShape")
oEllipseShape.Position = aPoint
oEllipseShape.Size = aSize
oEllipseShape.FillColor = RGB(0,255,0)
oPage.add(oEllipseShape)
This example creates a red square whose side is 10 centimeters long (aSize). Its
upper left corner is one centimeter right and down from the upper left corner of the
page (aPoint). The same values are used for the green circle. In general, the Size
property of a shape defines the bounding box in which it is contained. The
Position property specifies the position of the bounding box’s upper left corner on
the page. Coordinates on a page start at the upper left with (0,0) and extend to the
right and the bottom.
You can learn several important points from the preceding example:
1. All shapes are first created by createInstance( ) which expects the name of
the shape as its parameter. Then all properties are set, and finally the shapes are
made visible with a call to the draw page’s add( ) method.
2. StarOffice API does not provide a circle shape. Instead, you use an ellipse with a
size property that specifies a square.
3. The string of a text shape can be set only after it has been added to the page. In
general, you can only set those properties that are provided directly by the service
ShapeDescriptor and indirectly by the interface XShape.
If you want to change the visual appearance of a shape after you have created it, you
simply modify the corresponding property. This works as long as you didn’t add this
shape to a group. Shapes forming a group can be modified separately using an index
access method. You can apply geometric transformations to the whole group.
createSquare = createRect(nX,nY,nWidth,nWidth)
End Function
aPoint.X = nX
aPoint.Y = nY
aSize.Width = nWidth
aSize.Height = nHeight
oRectangleShape = oDocument.createInstance("com.sun.star.drawing.RectangleShape")
oRectangleShape.Size = aSize
oRectangleShape.Position = aPoint
createRect = oRectangleShape
End Function
aPoint.X = nX - nRadius
aPoint.X = nY - nRadius
aSize.Width = nRadius * 2
aSize.Height = nRadius * 2
oCircle = oDocument.createInstance("com.sun.star.drawing.EllipseShape")
oCircle.Size = aSize
oCircle.Position = aPoint
createCircle = oCircle
End Function
oShapes = createUnoService("com.sun.star.drawing.ShapeCollection")
oShapes.add(oSquare2)
oShapes.add(oCircle)
oGroup = oPage.group(oShapes)
nHeight = oPage.Height
nWidth = oPage.Width
aNewPos.X = nWidth / 2
aNewPos.Y = nHeight / 2
nHeight = oGroup.Size.Height
nWidth = oGroup.Size.Width
aNewPos.X = aNewPos.X - nWidth/2
aNewPos.Y = aNewPos.Y - nHeight/2
oGroup.Position = aNewPos
As you can see, you have to create and add( ) shapes before you can group them.
The example draws two squares (one filled with a light red and the other one filled
with a dark red) and one circle. The circle is drawn on top of the bottom square.
These two shapes are then merged into one group in three steps:
1. Create a new shape collection oGroupwith createUnoService( )
2. Add existing shapes to this collection with aGroup.add()
3. Merge all shapes in the collection with group( )
After you have created the group, you can no longer change the visual appearance of
the shapes it consists of. That means that oCircle.fillColor=RGB(0,0,255)( )
will not change the circle’s color to blue after you have added it to the shape
collection.
oPolyPolygonShape = oDocument.createInstance("com.sun.star.drawing.PolyPolygonShape")
aPoint.X = 5000
aPoint.Y = 3000
aSize.Width = 5000
aSize.Height = 5000
oPolyPolygonShape.Size = aSize
oPolyPolygonShape.Position = aPoint
oPage.add(oPolyPolygonShape)
mCoordinates(0).x = 5000
mCoordinates(1).x = 7500
mCoordinates(2).x = 10000
mCoordinates(0).y = 5000
mCoordinates(1).y = 7500
mCoordinates(2).y = 5000
vPolyPolygon = oPolyPolygonShape.PolyPolygon
vPolyPolygon = Array(mCoordinates())
oPolyPolygonShape.PolyPolygon = vPolyPolygon
oPolyPolygonShape = oDocument.createInstance("com.sun.star.drawing.PolyPolygonShape")
aPoint.x = 5000
aPoint.y = 3000
aSize.width = 5000
aSize.height = 5000
oPolyPolygonShape.Size = aSize
oPolyPolygonShape.Position = aPoint
oPage.add(oPolyPolygonShape)
aSquare1(0).x = 5000
aSquare1(1).x = 10000
aSquare1(2).x = 10000
aSquare1(3).x = 5000
aSquare1(0).y = 5000
aSquare1(1).y = 5000
aSquare1(2).y = 10000
aSquare1(3).y = 10000
aSquare2(0).x = 6500
aSquare2(1).x = 8500
aSquare2(2).x = 8500
aSquare2(3).x = 6500
aSquare2(0).y = 6500
aSquare2(1).y = 6500
aSquare2(2).y = 8500
aSquare2(3).y = 8500
vPolyPolygon = oPolyPolygonShape.PolyPolygon
vPolyPolygon = Array(aSquare1(),aSquare2())
oPolyPolygonShape.PolyPolygon = vPolyPolygon
This example creates a PolyPolygon which consists of two squares. The coordinates
of the first square are stored in Square1, those of the second square are stored in
Square2. These two arrays are then assembled to an array of arrays which is assigned
to oPolyPolygonShape.
You might ask why one would ever want to have PolyPolygons. One possible
answer is the picture drawn when you execute the program above. It shows a square
that seems to have a square hole in the center. What appears to be a hole, though, is
in fact the second square. It is drawn as a hole because StarOffice API applies the
so-called even-odd rule when filling multiple polygons.
oPolyPolygonShape = oDocument.createInstance("com.sun.star.drawing.PolyPolygonShape")
aPoint.x = 5000
aPoint.y = 3000
aSize.width = 5000
aSize.height = 5000
oPolyPolygonShape.Size = aSize
oPolyPolygonShape.Position = aPoint
oPage.add(oPolyPolygonShape)
mStar(0).x = 8000
mStar(0).y = 3000
mStar(2).x = 4800
mStar(2).y = 4800
mStar(3).x = 11200
mStar(3).y = 4800
mStar(4).x = 5700
mStar(4).y = 8000
vPolyPolygon = oPolyPolygonShape.PolyPolygon
vPolyPolygon = Array(mStar())
oPolyPolygonShape.PolyPolygon = vPolyPolygon
Again, a line from any point in the center of the star would have to cross the
polygon an even number of times.
To draw lines instead of filled polygons, you’d use the same approach as presented.
Instead of com.sun.star.drawing.PolyPolygonShape you create an instance
of com.sun.star.drawing.PolyLineShape or
com.sun.star.drawing.LineShape. The first one allows you to draw several
unconnected line segments in one step while the second one draws a single line
segment. To draw a simple line connecting two points, you might want to use a
subroutine like this:
Function createLine (oDocument,page,x1,y1,x2,y2) As Object
REM Creates a Line from (x1,y1) To (x2,y2)
Dim aPoint As New com.sun.star.awt.Point
Dim aSize As New com.sun.star.awt.Size
aPoint.x = x1
aPoint.y = y1
aSize.Width = x2-x1
aSize.Height = y2-y1
oLine = oDocument.createInstance("com.sun.star.drawing.LineShape")
oLine.Size = aSize
oLine.Position = aPoint
page.add(oLine)
points(0).x = x1
points(0).y = y1
points(1).x = x2
points(1).y = y2
aLineShape = oLine.PolyPolygon
aLineShape = Array(points())
oLine.PolyPolygon = aLineShape
createLine = oLine
End Function
aNewPos.x = 10000
aNewPos.y = 15000
oShape.Position = aNewPos
This code would halve the size of aShape. If you have ever done graphics
programming before, you are probably aware of the "fixed point" problem: When
you scale an object, its coordinates are multiplied by a constant value. This not only
changes its size but also its position, unless you take special caution to avoid this
effect. The fixed point of an object is the one point that doesn’t move when you
scale it. This fixed point is given by the Position property.
To scale an object using another fixed point, you could use this subroutine:
Sub scale(oShape, nFx, nFy, nScaleX, nScaleY)
p = oShape.Position
px = p.X
py = p.Y
dx = px - nFx
dy = py - nFy
p.X = nFx
p.Y = nFy
oShape.Position = p
aSize = oShape.Size
aSize.Width = aSize.Width * nScaleX
aSize.Height = aSize.Height * nScaleY
oShape.Size = aSize
p.X = p.X + dx * nScaleX
p.Y = p.Y + dy * nScaleY
The scale( ) subroutine first moves the object’s Position so that it coincides
with the fixed point fx/fy. It then scales the object as described before and finally
moves it back to its original position, taking into account the object’s new size.
This subroutine rotates the object oShape around the point nFx/nFy by nAngle
degrees counter-clockwise. All it needs to do is to set three properties that determine
the rotation angle and fixed point. Since StarOffice API expects the rotation angle in
100ths of a degree, the subroutine scales its last parameter accordingly.
Code Complete
This chapter presents complete StarOffice API programs written in StarBasic. Each of
them is designed to solve a single problem or perform a single task. In most cases,
the complete program is printed with annotations explaining it. Some trivial code is
occasionally left out to save space.
Although the samples are arranged by modules like Text, Data etc. this grouping is
somewhat arbitrary as most of the examples use functions from several modules. We
have tried to organize these programs by sections that deal with similar problems.
5.1 Text
The examples in this section concentrate on automated treatment of text. You will see
how to change attributes, how to enforce spelling and stylistic rules, and how to
generate an index automatically. The two most used interfaces here are
XSearchDescriptor( ) and XTextCursor( ).
99
4 Call the findAll() or replaceAll( )method on this descriptor. We use the
method pair findFirst()/findNext() occasionally instead of findAll().
4 Perform the necessary changes for each text range returned by this method. These
changes are modifications of the style, to make the text range stand out or
insertion of bookmarks enabling direct navigation to them.
oDocument = ThisComponent
oSearch = oDocument.createSearchDescriptor
oSearch.SearchString = "the[a-z]"
oSearch.SearchRegularExpression = TRUE
oResult = oDocument.findAll(oSearch)
For n = 0 To oResult.count - 1
oFound = oResult(n)
oFoundCursor = oFound.Text.createTextCursorByRange(oFound)
oFoundCursor.CharWeight = com.sun.star.awt.FontWeight.BOLD
Next n
oSearch.SearchString = "all[a-z]"
oFound = oDocument.findFirst(oSearch)
While NOT IsNull(oFound)
oFoundCursor = oFound.Text.createTextCursorByRange(oFound)
oFoundCursor.CharPosture = com.sun.star.awt.FontSlant.ITALIC
oFound = oDocument.findNext(oFound, oSearch)
Wend
End Sub
This code searches for the regular expression the[a-z] which stands for the text
portion the followed by exactly one lowercase letter. All occurrences of these four
letters are then changed to be displayed in bold characters. The same happens in the
next part of the program, this time changing the appearance of all[a-z] to italic.
In order for this example to work, you must execute it from an open text document.
In order for this example to work, you must execute it from an open text document.
For a real world application, you’d probably want to read the words from an
external file.
mOffending() = Array("negro(e|es)?","bor(ed|ing)?", _
"bloody?", "bleed(ing)?")
mBad() = Array("possib(le|ilit(y|ies))", _
"real(ly)+", "brilliant", "\<[a-z]+n\’t\>")
nOffendColor = RGB(255,0,0)
nBadColor = RGB(255,128,0)
colorList(mOffending(), nOffendColor)
colorList(mBad(), nBadColor)
End Sub
oDocument = ThisComponent
oSearch = oDocument.createSearchDescriptor
oSearch.SearchRegularExpression = TRUE
For n = LBound(mList()) To UBound(mList())
oSearch.SearchString = mList(n)
oFound = oDocument.findFirst(oSearch)
While NOT IsNull(oFound)
oFoundCursor = _ oFound.Text.createTextCursorByRange(oFound)
oFoundCursor.CharColor = nColor
oFound = oDocument.findNext(oFound,oSearch)
Wend
Next n
End Sub
Here, we use two lists of regular expressions mOffending and mBad. Those words
that match one of the expressions in mOffending are marked red in the text,
because we have to get rid of them. Those in mBad are marked orange, as they are
considered bad style only. The regular expressions here are not overly complex, but
two of them might merit a closer look: possib(le|ilit(y|ies)) matches all
occurrences of possible, possibility, and possibilities. It looks for possib
followed by either le or by ilit(y|ies), which is ility or ilities. The other
interesting regular expression is \<[a-z]+n\’t\>. It finds all abbreviated negations
like don’t, can’t etc. by looking for the start of a word (\<), at least one lowercase
letter ([a-z]+)) followed by n’t at the end of the word (\>).
Most of the work is done by the subroutine colorList(). It creates a search
descriptor oSearch and sets its SearchRegularExpression property to TRUE. It
then loops over all strings in mList, changing the search descriptor’s
SearchString and finding all occurrences of them in the document. The color of
these words is then set to nColor.
mOffending() = Array("negro(e|es)?","bor(ed|ing)?", _
"bloody?", "bleed(ing)?")
oDocument = ThisComponent
oSearch = oDocument.createSearchDescriptor
oSearch.SearchRegularExpression = TRUE
nCount=0
For n = LBound(mList()) To UBound(mList())
oSearch.SearchString = mList(n)
oFound = oDocument.findFirst(oSearch)
While NOT IsNull(oFound)
nCount=nCount+1
oFoundCursor = _
oFound.Text.createTextCursorByRange(oFound)
oBookmark = _
oDocument.createInstance("com.sun.star.text.Bookmark")
oBookmark.Name=sPrefix + CStr(nCount)
oDocument.Text.insertTextContent(oFoundCursor,_
oBookmark,TRUE)
oFound = oDocument.findNext(oFound, oSearch)
Wend
Next n
End Sub
The main difference to the preceding example is the For loop in markList( ).
Instead of changing the color of the current word, it creates a new bookmark,
oBookmark, whose name is the current word with an integer appended. It then
inserts this bookmark at the word.
oText = oDocument.Text
nFileNumber = FreeFile
Open sIndexFilePath For Input As #nFileNumber
While Not eof(nFileNumber)
Line Input #nFileNumber, sLineText
If sLineText <> "" Then
SearchWordInTextAndInsertIndexEntry(oDocument, sLineText)
End If
Wend
Close #nFileNumber
InsertDocumentIndex(oDocument)
End Sub
The main part of the program opens the external file sIndexFilePath and reads it
line by line. For the sake of simplicity, each line is supposed to contain exactly one of
the index terms. The words are then passed to
SearchWordInTextAndInsertIndexEntry( ). When the file is exhausted, the
subroutine InsertDocumentIndex() is called to add the index to the document.
Sub SearchWordInTextAndInsertIndexEntry(oDocument As Object,_
sEntry As String)
oSearch = oDocument.createSearchDescriptor
oSearch.SearchString = sEntry
oFound = oDocument.findAll(oSearch)
For n = 0 To oFound.Count - 1
oFoundPos = oFound(n)
oIndexEntry = _
oDocument.createInstance("com.sun.star.text.DocumentIndexMark")
oFoundPos.text.insertTextContent(oFoundPos, oIndexEntry, TRUE)
Next n
End Sub
5.2 Sheet
5.2.1 Adapting to Euroland
Most of the member of the European Union will abandon their old currency in favor
of the new Euro in 2001. This requires modifications to all programs using the old
currencies. Since the exchange rates for the old currencies have been fixed at the end
of 1999, one can already convert old data. The following program does this for all
values in a table that are formatted with the currency string DM. Three steps are
necessary for this modification:
4 First, it finds all cells using a number format of type CURRENCY and containing DM
as currency string. We use an enumeration to loop over all cells here.
4 A new number format is then defined for these cells and applied to them.
4 Finally, the cell’s values are corrected to show the new amount in Euro.
Sub Main
Dim oDocument As Object
oDocument = ThisComponent
oDocument.addActionLock
Convert( oDocument.Sheets(0), oDocument.NumberFormats, "DM", _
"EUR", 1.95583 )
oDocument.removeActionLock
End Sub
The main program just calls the subroutine Convert() which does nearly all the
work. It receives the sheet, the NumberFormats interface, the names of the old and
the new currency, and the conversion factor as parameters. The call is embedded in
addActionLock() and removeActionLock() calls, which delays update of the
aLanguage.Country = "de"
aLanguage.Language = "de"
sSimple = "0 [$"+sNewSymbol+"]"
nSimpleKey = NumberFormat( oFormats, sSimple, aLanguage )
oRanges = oSheet.CellFormatRanges.createEnumeration
The first few lines of Convert() determine the key of a simple format using the
new currency symbol (nSimpleKey) and the function NumberFormat() (see
below). Then a collection of ranges is retrieved from the sheet. Every element in this
collection is a range whose cells are using the same format. Convert( ) will now
iterate over these ranges.
While oRanges.hasMoreElements
oRange = oRanges.nextElement
oFormat = oFormats.getByKey( oRange.NumberFormat )
Every range (oRange) is characterized by the fact that all its cells use the same
format. Its properties are retrieved here with the format’s getByKey( ) method.
If ( oFormat.Type AND com.sun.star.util.NumberFormat.CURRENCY )_
And ( oFormat.CurrencySymbol = sOldSymbol ) Then
The line above will be executed only if the current format is of a type that
encompasses currency formats and if the currency string is identical to the old one
(sOldSymbol). The condition in the if statement first calculates the integer AND of
oFormat.Type and com.sun.star.util.NumberFormat.CURRENCY. This
expression yields TRUE only if CURRENCY is a part of oFormat.Type. You cannot
simply check for equality here because the number formats in StarOffice API can be
combinations of several base formats.
If ( oFormat.Type AND com.sun.star.util.NumberFormat.CURRENCY )_
And ( oFormat.CurrencySymbol = sOldSymbol ) Then
sNew = oFormats.generateFormat( nSimpleKey, oFormat.Locale, _
oFormat.ThousandsSeparator, _
oFormat.NegativeRed, _
oFormat.Decimals, oFormat.LeadingZeros )
oRange.NumberFormat = NumberFormat( oFormats, sNew, oFormat.Locale )
oValues = oRange.queryContentCells( com.sun.star.sheet.CellFlags.VALUE )
If oValues.Count > 0 Then
The function generateFormat( ) create a new format string based on the simple
format (nSimpleKey) generated at the very beginning of the subroutine. All we
really do here is to change the currency’s name, the other properties of the current
format remain untouched. This string is passed to the function NumberFormat( )
which returns a suitable format key. The key is then assigned to the NumberFormat
property of the current range, which effectively sets its format to use the new
currency name. Finally, we loop over all cells in the current range which contain a
constant value and adjust this value according to the currency exchange rate.
Function NumberFormat( oFormats As Object,_
sFormat As String , aLanguage As Variant ) As Long
Dim nRetKey As Long
The function NumberFormat( ) searches for a given format string in the list of
formats using queryKey( ). This method returns either the key of the existing format
string or -1 if the string didn’t exist previously. In this case, the function creates a
new format using addNew(). In any case, the key of the format string is returned.
5.3 Drawing
The following example converts a textual form of a directory tree into a graphics.
This is an extract from a directory listing. Every directory is indented three spaces
from the preceding one. The complete path to the file Chinese is therefore
Explorer/Workspace/Translations/Chinese. This data would be displayed
as shown in the picture below. Every file and directory name is written in a text box.
Directories on the same level are connected by a vertical line. Files and subdirectories
in a directory are connected by a horizontal line to this vertical line.
5.3.1.1 Setting up
Before the real code starts, we define some constants that permit us to write down
the rest of the program more clearly. Since they represent values used in many places
in the code it’s reasonable to define them only once thus allowing for easier
modification.
Const SBBASEWIDTH = 8000
Const SBBASEHEIGHT = 1000
Const SBPAGEX = 800
Const SBPAGEY = 800
Const SBBASECHARHEIGHT = 12
Const SBRELDIST = 1.1
The constants above are used throughout the whole program. The following table
lists them together with their meaning.
Name Meaning
SBASEWIDTH Original width of the text fields in 100th mm
SBASEHEIGHT Original height of the text fields in 100th mm
SBPAGEX Left page margin in 100th mm
SBPAGEY Top/bottom page margin in 100th mm
SBBASECHARHEIGHT Character height in points
SBRELDIST Factor to increase spacing between entries
Const SBBASEX = 0
Const SBBASEY = 1
Const SBOLDSTARTX = 2
Const SBOLDSTARTY = 3
Const SBOLDENDX = 4
Const SBOLDENDY = 5
Const SBNEWSTARTX = 6
Const SBNEWSTARTY = 7
Const SBNEWENDX = 8
Const SBNEWENDY = 9
Dim nActLevel as Integer
Dim nConnectLevel as Integer
Dim mLevelPos(10,9) As Integer
Name Meaning
SBBASEX X coordinate of the starting point for the
level.
SBBASEY Y coordinate of the last text drawn in this
level.
SBOLDSTARTX X start coordinate for vertical connecting
lines.
SBOLDSTARTY Y start coordinate for vertical connecting
lines.
SBOLDENDX X end coordinate for vertical connecting
lines.
SBOLDENDY Y end coordinate for vertical connecting
lines.
SBNEWSTARTX X start coordinate for next vertical
connecting line.
SBNEWSTARTY Y start coordinate for next vertical
connecting lines.
SBNEWENDX X end coordinate for next vertical connecting
line.
SBNEWENDY Y end coordinate for next vertical connecting
line.
5.3.1.2 Initialization
We start by initializing some global variables and acquiring the desktop service. We
then request the drawPage of the current document and start reading the file
containing the textual representation of the tree.
Sub TreeInfo(sFilePath As String)
REM Variable declarations ommitted
bStartUpRun = TRUE
nOldHeight = 200
nOldY = SBPAGEY
nOldX = SBPAGEX
nOldWidth = SBPAGEX
nActPage = 0
oDocument = ThisComponent
oPage = oDocument.drawPages(nActPage)
nFileHandle = FreeFile
Open sFilePath For Input As nFileHandle
The While loop iterates over all lines of the input file. It reads the next file or
directory name into the variable sActDir and figures out the indentation level using
nBaseLength and nModLength. The first variable is the complete length of the line,
possibly including leading spaces. The second one is just the name of the file or
directory with leading spaces removed. The difference of these two variables divided
by three gives the indentation level for the current entry, stored in nActLevel. The
variable nConnectLevel indicates the number of the preceding level - it is thus
nActLevel-1.
This chunk of code takes care of page breaks. If the next entry would overflow the
current draw page, nActPage is incremented and a new draw page inserted in the
These lines add the entry for the current text using the function CreateText(). The
shape created by it is added to the current draw page and some properties are set to
ensure that the surrounding box grows with the text. To increase the margin of the
text, the default box dimensions are increased by multiplying them with SBRELDIST.
If bStartUpRun = FALSE Then
If nActLevel <> 0 Then
mLevelPos(nActLevel,SBOLDSTARTX) =_
mLevelPos(nConnectLevel,SBNEWSTARTX)
mLevelPos(nActLevel,SBOLDSTARTY) =_
oActText.Position.Y + 0.5 * oActText.Size.Height
mLevelPos(nActLevel,SBOLDENDX) =_
mLevelPos(nActLevel,SBBASEX)
mLevelPos(nActLevel,SBOLDENDY) =_
mLevelPos(nActLevel,SBOLDSTARTY)
oOldArrivingLine = DrawLine(nActLevel, SBOLDSTARTX,_
SBOLDSTARTY, SBOLDENDX,_
SBOLDENDY)
oPage.Add(oOldArrivingLine)
mLevelPos(nConnectLevel,SBNEWENDX) =_
mLevelPos(nConnectLevel,SBNEWSTARTX)
mLevelPos(nConnectLevel,SBNEWENDY) =_
oActText.Position.Y + 0.5 * oActText.Size.Height
Else
mLevelPos(nConnectLevel,SBNEWENDY) = oActText.Position.Y
mLevelPos(nConnectLevel,SBNEWENDX) =_
mLevelPos(nConnectLevel,SBNEWSTARTX)
End If
oOldLeavingLine = DrawLine(nConnectLevel, SBNEWSTARTX,_
SBNEWSTARTY, SBNEWENDX,_
SBNEWENDY)
The rest of the main part updates two entries in mLevelPos and a couple of global
variables to reflect the positions of the text and line(s) just drawn. The first two
assignments to mLevelPos update the start coordinates of the line leaving from the
current entry.
aSize.Width = SBBASEWIDTH
aSize.Height = SBBASEHEIGHT
aPoint.x = CalculateXPoint()
aPoint.y = nOldY + SBRELDIST * nOldHeight
nOldY = aPoint.y
oText = oDocument.createInstance("com.sun.star.drawing.TextShape")
oText.Size = aSize
CalculateXPoint( ) figures out the x coordinate of the current text box and stores
it in mLevelPos(nActLevel, SBBASEX). If the current level is 0, this is simply
the left page margin. If the current entry is the first one in its level (nActLevel >
nOldlevel) the x position is that of the last level incremented by the value of
nOldWidth plus a small margin of 1 millimeter, thereby increasing the indentation.
If the current level is less then the previous one, there is nothing to be done, because
the x values are already calculated
Function DrawLine(nLevel As Integer,_
nStartX As Integer, nStartY As Integer,_
nEndX As Integer, nEndY As Integer)
Dim oConnect As Object
aPoint.X = mLevelPos(nLevel,nStartX)
aPoint.Y = mLevelPos(nLevel,nStartY)
aSize.Width = mLevelPos(nLevel,nEndX) - mLevelPos(nLevel,nStartX)
aSize.Height = mLevelPos(nLevel,nEndY) - mLevelPos(nLevel,nStartY)
oConnect = oDocument.createInstance("com.sun.star.drawing.LineShape")
oConnect.Position = aPoint
oConnect.Size = aSize
DrawLine = oConnect
End Function
DrawLine( ) creates a line shape. It uses the nLevel and the nStartX/nStartY,
nEndX/nEndY pairs to retrieve the coordinates of the line from mLevelPos. The
newly created line shape is then returned to the caller.
The following example relies on the sheet module. It uses URLs to obtain the current
stock quotes. The quotes are displayed in sheets, one for each company. We show a
line diagram and the numerical values for this company on every sheet. The
functionality is hidden in the three subroutines GetValue( ), UpdateValue( ), and
UpdateChart( ).
Option Explicit
Const STOCK_COLUMN = 2
Const STOCK_ROW = 6
Sub UpdateAll
Dim sName As String
Dim oCells As Object
Dim fDate As Double, fValue As Double
Dim oDocument As Object, oInputSheet As Object
Dim oColumn As Object, oRanges As Object
oDocument = ThisComponent
oDocument.addActionLock
oInputSheet = oDocument.Sheets(0)
oColumn = oInputSheet.Columns(STOCK_COLUMN)
oRanges = _
oDocument.createInstance("com.sun.star.sheet.SheetCellRanges")
oRanges.insertByName("", oColumn)
oCells = oRanges.Cells.createEnumeration
oCells.nextElement
While oCells.hasMoreElements
sName = oCells.nextElement.String
If GetValue( oDocument, sName, fDate, fValue ) Then
UpdateValue( oDocument, sName, fDate, fValue )
UpdateChart( oDocument, sName )
Else
MsgBox sName + " Is Not available"
End If
Wend
oDocument.removeActionLock
End Sub
This macro should be run from a table document that contains the NASDAQ names
of the companies to look for in the column STOCK_COLUMN. The sample document
provided on the CD-ROM already contains a table and two buttons you can use to
insert new columns or to update the stock quotes. The program creates an unnamed
range for this column and then steps through all cells until it finds an emtpy one
using nextElement( ). The first cell is skipped because it contains no valid
NASDAQ name but just a label for the column. The function GetValue( ) is then
used to retrieve the current quote. If the value can’t be found, a message is
oSheets = oDocument.Sheets
If oSheets.hasByName("Link") Then
oSheet = oSheets.getByName("Link")
Else
oSheet = _
oDocument.createInstance("com.sun.star.sheet.Spreadsheet")
oSheets.insertByName("Link", oSheet)
oSheet.IsVisible = False
End If
sUrl = "http://quote.yahoo.com/d/quotes.csv?s=" +_
sName + "&f=sl1d1t1c1ohgv&e=.csv"
sFilter = "Text - txt - csv (StarCalc)"
sOptions = _
"44,34,SYSTEM,1,1/10/2/10/3/10/4/10/5/10/6/10/7/10/8/10/9/10"
oSheet.link(sUrl, "", sFilter, sOptions, _
com.sun.star.sheet.SheetLinkMode.NORMAL )
fDate = oSheet.getCellByPosition(2,0).Value
fValue = oSheet.getCellByPosition(1,0).Value
GetValue = (fDate <> 0)
End Function
The function GetValue( ) first checks if a table with the name Link exists and
creates one if it doesn’t. Since this table is just used to store temporary results, we
turn its visibility off. The next line builds the URL needed to retrieve the quotes from
Yahoo and stores it in sUrl. Since we retrieve the data in CSV format, we have to
specify this in sFilter and provide the correct options so that StarOffice API can
parse the returned data. For more information on the CSV options, see Section 4.2.1
“Importing other Formats” on page 41. URL, filter and options are then passed to the
sheet’s link() method which causes StarOffice API to retrieve the data and store it
in the sheet. Afterwards, the third cell in the first row contains the current date and
the second cell the quote for this date. These values are returned in fDate and
fValue, respectively. The function itself returns TRUE if it could retrieve the quote
and false otherwise.
aLanguage.Country = "de"
aLanguage.Language = "de"
oSheets = oDocument.Sheets
sName = SheetNameFor(sName)
If oSheets.hasByName(sName) Then
oSheet = oSheets.getByName(sName)
Else
oSheet =_
oDocument.createInstance("com.sun.star.sheet.Spreadsheet")
oSheets.insertByName(sName, oSheet)
End If
oRanges =_
oDocument.createInstance("com.sun.star.sheet.SheetCellRanges")
oRanges.insertByName("", oSheet)
oCells = oRanges.Cells
If oCells.hasElements Then
oCell = oCells.createEnumeration.nextElement
oCursor = oSheet.createCursorByRange(oCell)
oCursor.collapseToCurrentRegion
aAddress = oCursor.RangeAddress
nColumn = aAddress.StartColumn
nRow = aAddress.EndRow
If oSheet.getCellByPosition(nColumn,nRow).Value <> fDate Then
nRow = nRow + 1
End If
Else
oSheet.getCellByPosition(1,18).String = "Date"
oSheet.getCellByPosition(2,18).String = sName
oSheet.getCellRangeByPosition(1,18,2,18).CharWeight = 150
nColumn = 1
nRow = 19
End If
oCell = oSheet.getCellByPosition(nColumn,nRow)
oCell.Value = fDate
oCell.NumberFormat =_
oDocument.NumberFormats.getStandardFormat(_
com.sun.star.util.NumberFormat.DATE, aLanguage )
oSheet.getCellByPosition(nColumn+1,nRow).Value = fValue
End Sub
Subroutine UpdateValue( ) receives the NASDAQ name (sName), the current date
(fDate) and the quote (fValue) as parameters. First, it verifies if a sheet named
sName exists and if not, creates it. The subroutine then creates a cell range for this
sheet. If the program has been run before, this range contains non-emtpy cells, and
If this is the first time that a qoute for this company is retrieved, the cell range is
empty and we simply insert the strings Date and sName in cells B19 and C19,
respectively. The variables nColumn and nRow are then set to 1 and 19, which
addresses cell B20.
Finally, updateValue( ) inserts the values fDate and fValue in the cells at
nColumn and nColumn+1 in row nRow. The update for this company’s sheet is thus
complete.
aLanguage.Country = "de"
aLanguage.Language = "de"
oSheet = oDocument.Sheets.getByName( SheetNameFor(sName) )
oCharts = oSheet.Charts
oRanges = oDocument.createInstance("com.sun.star.sheet.SheetCellRanges")
oRanges.insertByName("", oSheet)
oCell = oRanges.Cells.createEnumeration.nextElement
oCursor = oSheet.createCursorByRange(oCell)
oCursor.collapseToCurrentRegion
oDataRanges = oDocument.createInstance("com.sun.star.sheet.SheetCellRanges")
oDataRanges.insertByName("", oCursor)
Subroutine UpdateChart( ) takes care of displaying and updating the stock quotes
chart for one company. It begins with the creation of cursor which points to the first
of the cells showing the dates and quotes. The cursor is then collapsed to this region,
thereby spanning a rectangle which contains only the date and quote values. Using
oRectangleShape.X = aPos.X
oRectangleShape.Y = aPos.Y
oRectangleShape.Width = aSize.Width
oRectangleShape.Height = aSize.Height
oCharts.addNewByName( sName, oRectangleShape,_
oDataRanges.RangeAddresses, true, false )
oChart = oCharts.getByName(sName).EmbeddedObject
oChart.diagram = oChart.createInstance("com.sun.star.chart.XYDiagram")
oChart.Title.String = sName
oDiagram = oChart.Diagram
oDiagram.DataRowSource = com.sun.star.chart.ChartDataRowSource.COLUMNS
oChart.Area.LineStyle = com.sun.star.drawing.LineStyle.SOLID
oXAxis = oDiagram.XAxis
oXAxis.StepMain = 1
oXAxis.StepHelp = 1
oXAxis.AutoStepMain = false
oXAxis.AutoStepHelp = false
oXAxis.TextBreak = false
oYAxis = oDiagram.getYAxis()
oYAxis.AutoOrigin = true
nDateFormat =_
oXAxis.NumberFormats.getStandardFormat(_
com.sun.star.util.NumberFormat.DATE, aLanguage )
oXAxis.NumberFormat = nDateFormat
If the charts collection (oCharts) of this sheet is empty, we have to create the chart
first. It is positioned just above the cells displaying the data. We use the Position
and Size properties of the range B2:H17 to find out the size and position of the
rectangle to contain the chart. As the first cell of the sheet is located at B20, this
rectangle lies above the stock quote values. The chart is then added to the charts
collection and its diagram type is set to XYDiagram, which ensures that it uses lines
to display the evolution of the quotes. The rest of the code just sets some of the
diagram properties.
Else
oChart = oCharts(0)
oChart.Ranges = oDataRanges.RangeAddresses
oChart.HasRowHeaders = false
oEmbeddedChart = oChart.EmbeddedObject
oDiagram = oEmbeddedChart.Diagram
oXAxis = oDiagram.XAxis
End If
The next lines are executed if the program has already been executed and the chart
therefore exists. They simply change its data range so that it contains all dates and
quotes and set the oXAxis variable.
This variable is needed because the minimum and maximum of the x axis have to be
updated each time the quotes are retrieved. The lines above simply get the first and
last date for the current quote (fMin and fMax) and adjust the x axis accordingly.
When you have run this program several times, the chart looks similar to the one
shown below.
5.5 Troubleshooting
5.5.1 Debugging
Most programs you write will not work right from the beginning. Some errors like
misspelled or missing keywords are caught by the StarBasic interpreter before it even
starts to execute your program. These are easy to correct. Others are a bit more
difficult to catch because they happen at runtime.
StarOffice API provides three functions that permit you to inspect objects at runtime:
4 DBG_properties( ) returns all properties defined for a class. Note that some
properties need not be defined for the particular object you are inquiring. Before
accessing this property, you should check its existence with isNull( ).
4 DBG_methods( ) returns all methods defined for a class.
4 DBG_supportedInterfaces( ) returns all interfaces supported by the current
object.
mProperties = oObject.PropertySetInfo.Properties
nCount = UBound(mProperties)-LBound(mProperties) + 2
oDocument = CreateTextDocument()
oText = oDocument.Text
oCursor = oText.createTextCursor()
oCursor.gotoStart(FALSE)
oTable = CreateTable()
oTable.initialize(nCount,2)
oText.insertTextContent(oCursor, oTable, FALSE)
oTableCursor = oTable.createCursorByCellName(oTable.CellNames(0))
oTableCursor.gotoStart(FALSE)
InsertNextItem("Name", oTableCursor, oTable)
InsertNextItem("Value", oTableCursor, oTable)
For i% = LBound(mProperties) To UBound(mProperties)
p = mProperties(i%)
n$ = p.name
vVariant = oObject.getPropertyValue(n$)
InsertNextItem(n$, oTableCursor, oTable)
If IsNull(vVariant) Then
sString = "NULL-Value"
ElseIf IsObject(vVariant) Then
sString = vVariant.dbg_properties
ElseIf IsArray(vVariant) Then
tmp$ = ""
For j% = LBound(vVariant) To UBound(vVariant)
vItem = vVariant(j%)
If IsNull(vItem) Then
sItemDescription = "NULL-Value"
ElseIf IsObject(vItem) Then
sItemDescription = vItem.dbg_properties
Else
sItemDescription = cstr(vItem)
End If
tmp$ = tmp$ + sItemDescription + "/"
Next j%
sString = tmp$
Else
sString = cstr(vVariant)
End If
InsertNextItem(sString, oTableCursor, oTable)
Next i%
End Sub
sName = oCursor.getRangeName()
oCell = oTable.getCellByName(sName)
oCell.String = what
oCursor.goRight(1,FALSE)
End Sub
Class diagrams show the static structure of the model, in particular, the things that
exist (such as classes and types), their internal structure, and their relationships to
other things. Class diagrams do not show temporal or behavioral information. All
class diagrams shown below are based on the UML 1.1 notation.
A.1 UML
The Unified Modeling Language (UML) is a language for specifying, visualizing and
documenting the artifacts of software systems. It represents a collection of the best
engineering practices that have proven successful in the modeling of large and
complex systems. The UML 1.1 specification was adopted by the Object Management
Group (OMG) as an official standard. The websites of Rational Software Corporation
and the OMG provide detailed information concerning the specification of UML.
125
by StarOffice API to reflect the definition of StarOffice API services described in
this book.
In the figure below you see both an interface and a service. XSearchDescriptor
provides operations to set and retrieve a string to search for. This set of operations
might be useful to various services. The service SearchDescriptor specifies such an
application. It describes a piece of software which supports the operations of
interface XSearchDescriptor and is able to handle properties like SearchCaseSensitiv
or SearchRegularExpression. These properties hold further information about the
search string and how to interpret its content.
A.3 Interface
An interface is a specifier for the externally-visible operations of a class, component,
or other entity without specification of internal structure. Interfaces do not have
implementation; they lack attributes, states, or associations; they only have
operations. An interface is formally equivalent to an abstract class with no attributes
and no methods and only abstract operations, but Interface is a peer of Class within
the UML metamodel; both are Classifiers.
BASIC Beginners all purpose simple instruction code: One of the oldest
and easiest to learn programming languages.
Cell The smallest addressable part of a spreadsheet or a table. A cell can
contain text, a number or a formula.
CSV Comma A format used to store tabular data in an independent form. The
Separated Value columns need not be separated by commas anymore, most
programs permit to choose the separator freely.
GUI Graphical The menus, dialogs, buttons etc. used to tell a program what it
User Interface should do.
131
Service Abstract concept providing interfaces and properties. All
implementations of the same service provide the same interfaces.
SQLStructured A language used to insert, update, delete and retrieve data from
Query Language relational databases. Although SQL is normed, each database
implements its own extensions.
TextRange A part of a text described by a text cursor. The range can consist only
of a text position if the start and end position of the cursor coincide.
UNO Universal Basic services such as the ServiceManager, Property APIs etc used
Network Objects by StarOffice API.
URL Universal String describing an object on any computer in the internet. http:/
Resource Locator /www.sun.com/index.html is an URL pointing to the file
index.html on the computer www.sun.com. http indicates the
service used to access the object, other services are ftp or news.