[Java] Send an email from an OOo Writer document

Keyboard macros or custom scripts

[Java] Send an email from an OOo Writer document

Postby hol.sten » Mon Mar 24, 2008 9:20 pm

Introduction

This post covers a script for sending a text email from an OOo Writer document. The script is written in Java and runs as a macro in OpenOffice.org.

Ths script puts in the mail body the selected text from the OOo Writer document or the whole document, if nothing is selected. To get the mail subject and the mail recipients from the user, the script creates a simple dialog that uses OOo's com.sun.star.awt API.

For sending a mail this script uses the JavaMail API (mail.jar from javamail-1_4_3.zip). Additionally this script uses Apache log4j (log4j-1.2.16.jar from apache-log4j-1.2.16.zip) for logging some messages.

If you need a simpler example to get started with OOo macros in Java, you should read first [Java] OOo Writer and Calc macro examples.

I've used OOo 2.3.1, NetBeans IDE 6.0, Java 1.6_04 and Windows XP to develop this example. To check, if the macro is still working I've used OOo 3.2.0, NetBeans IDE 6.8, Java 1.6_16 and Ubuntu 10.04 LTS. It might work with other versions, too. Although using older versions of Java, it might be necessary to additionally use the JavaBeans Activation Framework (activation.jar from jaf-1_1-fr). In this case the activation.jar should be deployed together with the mail.jar and added to the classpath of the parcel-descriptor.



Source code

The source code of this script consists of six Java classes:
  1. TextEmailSender.java is the main script
  2. Dialog.java creates the OOo dialog
  3. SmtpMailSender.java, MailSender.java, MailSenderConfiguration.java, and MailContentFactory.java are used for sending the mail

TextEmailSender.java
Code: Select all   Expand viewCollapse view
package ooo.scripting.writer;

import java.io.IOException;
import java.util.Properties;
import javax.mail.MessagingException;

import org.apache.log4j.Logger;

import com.sun.star.awt.ActionEvent;
import com.sun.star.awt.KeyEvent;
import com.sun.star.awt.MouseEvent;
import com.sun.star.awt.PushButtonType;
import com.sun.star.awt.XTextComponent;
import com.sun.star.container.XIndexAccess;
import com.sun.star.lang.IndexOutOfBoundsException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.text.XText;
import com.sun.star.text.XTextDocument;
import com.sun.star.text.XTextRange;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.view.XSelectionSupplier;

import ooo.dialog.Dialog;
import util.mail.MailSender;
import util.mail.SmtpMailSender;
import util.mail.MailSenderConfiguration;

public class TextEmailSender {

    /** Logger */
    private static final Logger LOGGER = Logger.getLogger(TextEmailSender.class);

    /** New line separator */
    public static final String NEWLINE = System.getProperty("line.separator");

    /** Name of the mail properties file */
    private static final String MAIL_PROPERTIES = "mail.properties";

    /** Max length of automatically created mail subject */
    private static final int MAX_SUBJECT_LENGTH = 80;



    /** OOo component context for showing a dialog */
    private XComponentContext xcomponentcontext;

    /** OOo Writer text document */
    private XTextDocument xtextdocument;

    /** Mail subject */
    private String subject;

    /** Mail recipients */
    private String recipients;



    /**
     * Constructs a text email sender which uses the OOo component context and
     * OOo Writer text document.
     *
     * @param   xcomponentcontext   The OOo component context
     * @param   xtextdocument       The OOo Writer text document
     */
    public TextEmailSender(XComponentContext xcomponentcontext, XTextDocument xtextdocument) {

        this.xcomponentcontext = xcomponentcontext;
        this.xtextdocument = xtextdocument;

        this.subject = null;
        this.recipients = null;

        LOGGER.info("TextEmailSender constructed");
    }


    /**
     * Returns the current selections of the text document or the whole text of
     * the text document, if no text is selected.
     *
     * @return               The current selections or the whole text of the text document
     */
    private String getDocumentText() throws IndexOutOfBoundsException, WrappedTargetException {

        // Get all selected regions of the document
        XSelectionSupplier xSelectionSupplier = (XSelectionSupplier) UnoRuntime.queryInterface(XSelectionSupplier.class, xtextdocument.getCurrentController());
        XIndexAccess xIndexAccess = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSelectionSupplier.getSelection());

        String text = null;

        int count = xIndexAccess.getCount();
        if (count > 0) {
            // Get text inside selected regions
            for (int i=0; i<count; i++) {
                XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, xIndexAccess.getByIndex(i));
                text = (text == null)? "": text+NEWLINE+NEWLINE;
                text += xTextRange.getString();
            }
        }

        if (text == null || text.trim().length() == 0) {
            // Get text of the document
            XText xtext = xtextdocument.getText();
            text = (xtext != null)? xtext.getString(): "";
        }

        LOGGER.info("Document text determined");
        return text;
    }

    /**
     * Returns a mail subject proposal for mailing the text.
     *
     * Takes the first line of the text. If the first line is longer than the
     * maximum subject line, everything after the maximum subject line length
     * and the last blank of that subject will be removed.
     *
     * @param   text         The text
     * @return               The subject proposal
     */
    private String getSubjectProposal(String text) {

        int maxSubjectLength = (text.indexOf(NEWLINE) > 0)? text.indexOf(NEWLINE): MAX_SUBJECT_LENGTH + 1;
        String subjectProposal = text.substring(0, Math.min(text.length(), maxSubjectLength));
        if (subjectProposal.length() > MAX_SUBJECT_LENGTH) {
            subjectProposal = text.substring(0, MAX_SUBJECT_LENGTH);
            if (subjectProposal.lastIndexOf(" ") > 0) {
                subjectProposal = subjectProposal.substring(0, subjectProposal.lastIndexOf(" "));
            }
        }

        LOGGER.info("Subject proposal determined: '"+subjectProposal+"'");
        return subjectProposal;
    }

    /**
     * Shows a dialog to initialize the subject and recipients of the mail.
     *
     * @param   subjectProposal           The subject proposal
     * @param   recipientsProposal        The recipients proposal
     */
    private void initSubjectAndRecipients(String subjectProposal, String recipientsProposal) throws Exception {
       
        Dialog dialog = new Dialog(xcomponentcontext,100,100,80,280,"Send Mail","sendMailDialog");

        dialog.insertFixedText(10, 10, 30, "Subject:");
        XTextComponent subjectField = dialog.insertTextField(42, 10, 228, subjectProposal);
        dialog.insertFixedText(10, 30, 30, "Recipients:");
        XTextComponent recipientsField = dialog.insertTextField(42, 30, 228, recipientsProposal);

        dialog.insertButton(105, 60, 30, "OK", PushButtonType.OK_value);
        dialog.insertButton(145, 60, 30, "Cancel", PushButtonType.CANCEL_value);

        short returnValue = dialog.execute();

        if (returnValue == PushButtonType.OK_value) {
            subject = (subjectField.getText().trim().length()>0)? subjectField.getText(): null;
            recipients = (recipientsField.getText().trim().length()>0)? recipientsField.getText(): null;
        }

        dialog.dispose();
    }

    /**
     * Sends the email.
     *
     * @param   text           The email text
     */
    private void sendMail(String text) throws Exception, IOException, MessagingException {

        Properties mailProperties = new Properties();
        mailProperties.load(this.getClass().getClassLoader().getResourceAsStream(MAIL_PROPERTIES));
       
        String subjectProposal = getSubjectProposal(text);
        String recipientsProposal = mailProperties.getProperty("mail.recipients.proposal");
       
        initSubjectAndRecipients(subjectProposal, recipientsProposal);

        if (subject != null && recipients != null) {
            MailSenderConfiguration mailSenderConfiguration = new MailSenderConfiguration();
            mailSenderConfiguration.setSender(mailProperties.getProperty("mail.smtpauth.sender"));
            mailSenderConfiguration.setSmtpHost(mailProperties.getProperty("mail.smtpauth.host"));
            mailSenderConfiguration.setUser(mailProperties.getProperty("mail.smtpauth.user"));
            mailSenderConfiguration.setPassword(mailProperties.getProperty("mail.smtpauth.password"));
            mailSenderConfiguration.setSubject(subject);
            mailSenderConfiguration.setRecipients(recipients);
           
            MailSender mailSender = new SmtpMailSender(mailSenderConfiguration);
            mailSender.send(text);

            LOGGER.info("Email send");
        }
        else {
            LOGGER.warn("No email send");
        }
    }

    /**
     * Gets the text from the OOo Writer text document and sends an email.
     */
    public void send() {

        try {
            String documentText = getDocumentText();
            sendMail(documentText);
        } catch (Exception ex) {
            String message = "Sending text email failed: "+ex.getMessage()+ex.getStackTrace();
            LOGGER.error(message);
            throw new RuntimeException(message,ex);
        }
    }   



    /**
     * Sending text email called from a toolbar.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored number
     */
    public static void sendTextEmail(XScriptContext xScriptContext, Short ignored) {
        LOGGER.info("Sending text email called from a toolbar");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a button with an action.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored action event
     */
    public static void sendTextEmail(XScriptContext xScriptContext, ActionEvent ignored) {
        LOGGER.info("Sending text email called from a button with an action");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a button with a key.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored key event
     */
    public static void sendTextEmail(XScriptContext xScriptContext, KeyEvent ignored) {
        LOGGER.info("Sending text email called from a button with a key");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a button with the mouse.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored mouse event
     */
    public static void sendTextEmail(XScriptContext xScriptContext, MouseEvent ignored) {
        LOGGER.info("Sending text email called from a button with the mouse");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a menu or the "Run Macro..." menu.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     */
    public static void sendTextEmail(XScriptContext xScriptContext) {

        LOGGER.info("Sending text email called");

        XComponentContext xcomponentcontext = xScriptContext.getComponentContext();
        XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xScriptContext.getDocument());
        if (xtextdocument != null) {
            TextEmailSender textEmailSender = new TextEmailSender(xcomponentcontext,xtextdocument);
            textEmailSender.send();
        }
    }
}



Dialog.java
Code: Select all   Expand viewCollapse view
package ooo.dialog;

import com.sun.star.awt.XButton;
import com.sun.star.awt.XControl;
import com.sun.star.awt.XControlContainer;
import com.sun.star.awt.XControlModel;
import com.sun.star.awt.XDialog;
import com.sun.star.awt.XFixedText;
import com.sun.star.awt.XTextComponent;
import com.sun.star.awt.XToolkit;
import com.sun.star.awt.XWindow;
import com.sun.star.awt.XWindowPeer;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.XNameAccess;
import com.sun.star.container.XNameContainer;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.uno.Exception;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;

/**
* The source code of this class bases heavily on the OOo Developer's Guide:
* http://api.openoffice.org/docs/DevelopersGuide/GUI/GUI.xhtml#1_Graphical_User_Interfaces
*
* This class provides methods to create, execute and dispose an OOo Dialog
* with labels (fixed text), buttons, text fields, and password fields.
*/
public class Dialog {

    private XComponentContext xComponentContext;
    private XMultiComponentFactory xMultiComponentFactory;
    private XMultiServiceFactory xMultiComponentFactoryDialogModel;
    private XNameContainer xDialogModelNameContainer;
    private XControl xDialogControl;
    private XControlContainer xDialogContainer;

    public Dialog(XComponentContext xComponentContext, int posX, int posY, int height, int width, String title, String name) throws Exception {

        this.xComponentContext = xComponentContext;
        init(posX, posY, height, width, title, name);
    }

    private void init(int posX, int posY, int height, int width, String title, String name) throws Exception {

        xMultiComponentFactory = xComponentContext.getServiceManager();

        Object oDialogModel =  xMultiComponentFactory.createInstanceWithContext("com.sun.star.awt.UnoControlDialogModel", xComponentContext);

        // The XMultiServiceFactory of the dialogmodel is needed to instantiate the controls...
        xMultiComponentFactoryDialogModel = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, oDialogModel);

        // The named container is used to insert the created controls into...
        xDialogModelNameContainer = (XNameContainer) UnoRuntime.queryInterface(XNameContainer.class, oDialogModel);

        // create the dialog...
        Object oUnoDialog = xMultiComponentFactory.createInstanceWithContext("com.sun.star.awt.UnoControlDialog", xComponentContext);
        xDialogControl = (XControl) UnoRuntime.queryInterface(XControl.class, oUnoDialog);

        // The scope of the control container is public...
        xDialogContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, oUnoDialog);

        // link the dialog and its model...
        XControlModel xControlModel = (XControlModel) UnoRuntime.queryInterface(XControlModel.class, oDialogModel);
        xDialogControl.setModel(xControlModel);

        // Create a unique name
        String uniqueName = createUniqueName(xDialogModelNameContainer, (name != null)? name: "OOoDialog");

        // Define the dialog at the model
        XPropertySet xDialogPropertySet = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, xDialogModelNameContainer);
        xDialogPropertySet.setPropertyValue("PositionX",new Integer(posX));
        xDialogPropertySet.setPropertyValue("PositionY",new Integer(posY));
        xDialogPropertySet.setPropertyValue("Height",new Integer(height));
        xDialogPropertySet.setPropertyValue("Width",new Integer(width));
        xDialogPropertySet.setPropertyValue("Title",(title != null)? title: "OpenOffice.org Dialog");
        xDialogPropertySet.setPropertyValue("Name",uniqueName);
        xDialogPropertySet.setPropertyValue("Moveable",Boolean.TRUE);
        xDialogPropertySet.setPropertyValue("TabIndex",new Short((short) 0));
    }

    public short execute() throws Exception {

        XWindow xWindow = (XWindow) UnoRuntime.queryInterface(XWindow.class, xDialogContainer);

        // set the dialog invisible until it is executed
        xWindow.setVisible(false);

        Object oToolkit = xMultiComponentFactory.createInstanceWithContext("com.sun.star.awt.Toolkit", xComponentContext);
        XWindowPeer xWindowParentPeer = ((XToolkit) UnoRuntime.queryInterface(XToolkit.class, oToolkit)).getDesktopWindow();
        XToolkit xToolkit = (XToolkit) UnoRuntime.queryInterface(XToolkit.class, oToolkit);
        xDialogControl.createPeer(xToolkit, xWindowParentPeer);

        // the return value contains information about how the dialog has been closed
        XDialog xDialog = (XDialog) UnoRuntime.queryInterface(XDialog.class, xDialogControl);
        return xDialog.execute();
    }

    public void dispose() {

        // Free the resources
        XComponent xDialogComponent = (XComponent) UnoRuntime.queryInterface(XComponent.class, xDialogControl);
        xDialogComponent.dispose();
    }

    public XFixedText insertFixedText(int posX, int posY, int width, String label) throws Exception {

        // Create a unique name
        String uniqueName = createUniqueName(xDialogModelNameContainer, "FixedText");

        // Create a fixed text control model
        Object oFixedTextModel = xMultiComponentFactoryDialogModel.createInstance("com.sun.star.awt.UnoControlFixedTextModel");

        // Set the properties at the model
        XPropertySet xFixedTextPropertySet = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, oFixedTextModel);
        xFixedTextPropertySet.setPropertyValue("PositionX",new Integer(posX));
        xFixedTextPropertySet.setPropertyValue("PositionY",new Integer(posY+2));
        xFixedTextPropertySet.setPropertyValue("Height",new Integer(8));
        xFixedTextPropertySet.setPropertyValue("Width",new Integer(width));
        xFixedTextPropertySet.setPropertyValue("Label",(label != null)? label: "");
        xFixedTextPropertySet.setPropertyValue("Name",uniqueName);

        // Add the model to the dialog model name container
        xDialogModelNameContainer.insertByName(uniqueName, oFixedTextModel);

        // Reference the control by the unique name
        XControl xFixedTextControl = xDialogContainer.getControl(uniqueName);

        return (XFixedText) UnoRuntime.queryInterface(XFixedText.class, xFixedTextControl);
    }

    public XButton insertButton(int posX, int posY, int width, String label, int pushButtonType) throws Exception {

        // Create a unique name
        String uniqueName = createUniqueName(xDialogModelNameContainer, "Button");

        // Create a button control model
        Object oButtonModel = xMultiComponentFactoryDialogModel.createInstance("com.sun.star.awt.UnoControlButtonModel");

        // Set the properties at the model
        XPropertySet xButtonPropertySet = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, oButtonModel);
        xButtonPropertySet.setPropertyValue("PositionX",new Integer(posX));
        xButtonPropertySet.setPropertyValue("PositionY",new Integer(posY));
        xButtonPropertySet.setPropertyValue("Height",new Integer(14));
        xButtonPropertySet.setPropertyValue("Width",new Integer(width));
        xButtonPropertySet.setPropertyValue("Label",(label != null)? label: "");
        xButtonPropertySet.setPropertyValue("PushButtonType",new Short((short) pushButtonType));
        xButtonPropertySet.setPropertyValue("Name",uniqueName);

        // Add the model to the dialog model name container
        xDialogModelNameContainer.insertByName(uniqueName, oButtonModel);

        // Reference the control by the unique name
        XControl xButtonControl = xDialogContainer.getControl(uniqueName);

        return (XButton) UnoRuntime.queryInterface(XButton.class, xButtonControl);
    }

    public XTextComponent insertTextField(int posX, int posY, int width, String text) throws Exception {

        return insertEditableTextField(posX, posY, width, text, ' ');
    }

    public XTextComponent insertPasswordField(int posX, int posY, int width, String text, char echoChar) throws Exception {
       
        return insertEditableTextField(posX, posY, width, text, echoChar);
    }

    private XTextComponent insertEditableTextField(int posX, int posY, int width, String text, char echoChar) throws Exception {

        // Create a unique name
        String uniqueName = createUniqueName(xDialogModelNameContainer, "EditableTextField");

        // Create an editable text field control model
        Object oEditableTextFieldModel = xMultiComponentFactoryDialogModel.createInstance("com.sun.star.awt.UnoControlEditModel");

        // Set the properties at the model
        XPropertySet xEditableTextFieldPropertySet = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, oEditableTextFieldModel);
        xEditableTextFieldPropertySet.setPropertyValue("PositionX",new Integer(posX));
        xEditableTextFieldPropertySet.setPropertyValue("PositionY",new Integer(posY));
        xEditableTextFieldPropertySet.setPropertyValue("Height",new Integer(12));
        xEditableTextFieldPropertySet.setPropertyValue("Width",new Integer(width));
        xEditableTextFieldPropertySet.setPropertyValue("Text",(text != null)? text: "");
        xEditableTextFieldPropertySet.setPropertyValue("Name",uniqueName);
       
        if (echoChar != 0 && echoChar != ' ') {
            // Useful for password fields
            xEditableTextFieldPropertySet.setPropertyValue("EchoChar",new Short((short) echoChar));
        }

        // Add the model to the dialog model name container
        xDialogModelNameContainer.insertByName(uniqueName, oEditableTextFieldModel);

        // Reference the control by the unique name
        XControl xEditableTextFieldControl = xDialogContainer.getControl(uniqueName);

        return (XTextComponent) UnoRuntime.queryInterface(XTextComponent.class, xEditableTextFieldControl);
    }

    /**
     * Makes a string unique by appending a numerical suffix.
     *
     * @param elementContainer   The container the new element is going to be inserted to
     * @param elementName        The name of the element
     */
    private static String createUniqueName(XNameAccess elementContainer, String elementName) {

        String uniqueElementName = elementName;
       
        boolean elementExists = true;
        int i = 1;
        while (elementExists) {
            elementExists = elementContainer.hasByName(uniqueElementName);
            if (elementExists) {
                i++;
                uniqueElementName = elementName + Integer.toString(i);
            }
        }

        return uniqueElementName;
    }
}



SmtpMailSender.java
Code: Select all   Expand viewCollapse view
package util.mail;

import java.util.Properties;
import java.util.StringTokenizer;

import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
* Mail sender for sending mails via smtp with password authentication.
*/
public class SmtpMailSender implements MailSender {

    /** Mail sender configuration */
    private MailSenderConfiguration config;

    /**
     * Constructs a mail sender which uses the mail sender configuration.
     *
     * @param   mailSenderConfiguration   The mail sender configuration
     */
    public SmtpMailSender(MailSenderConfiguration mailSenderConfiguration) {

        config = mailSenderConfiguration;

        try {
            DataHandler.setDataContentHandlerFactory(new MailContentFactory());
        }
        catch (java.lang.Error error) {
            System.out.println("DataContentHandlerFactory already defined: "+error.getMessage());
        }
    }

    /**
     * Sends a text mail with the given content.
     *
     * @param   content   The mail content
     */
    public void send(Object content) throws MessagingException {

        Properties mailProperties = new Properties();
        mailProperties.put("mail.smtp.host", config.getSmtpHost());
        mailProperties.put("mail.smtp.auth", "true");

        Authenticator smtpAuthenticator = new SmtpMailAuthenticator(config.getUser(),config.getPassword());
        Session smtpSession = Session.getInstance(mailProperties,smtpAuthenticator);
        if (smtpSession != null) {
            StringTokenizer tokenizer = new StringTokenizer(config.getRecipients(),";");
            Address[] addresses = new InternetAddress[tokenizer.countTokens()];
            int i = 0;
            while (tokenizer.hasMoreTokens()) {
                String recipient = tokenizer.nextToken();
                addresses[i] = new InternetAddress(recipient);
                i++;
            }

            Message mimeMessage = new MimeMessage(smtpSession);
            mimeMessage.setFrom(new InternetAddress(config.getSender()));
            mimeMessage.setRecipients(Message.RecipientType.TO,addresses);
            mimeMessage.setSubject(config.getSubject());
            mimeMessage.setContent(content, "text/plain");
            mimeMessage.saveChanges();

            Transport transport = smtpSession.getTransport("smtp");
            transport.connect();
            transport.sendMessage(mimeMessage,addresses);
            transport.close();
        }
    }

    /**
     * Authenticator for sending mails via smtp with password authentication.
     *
     * @param   content   The mail content
     */
    private class SmtpMailAuthenticator extends Authenticator {

        private String user;
        private String password;

        public SmtpMailAuthenticator(String user, String password) {

            super();

            this.user = user;
            this.password = password;
        }

        @Override
        public PasswordAuthentication getPasswordAuthentication() {

            return new PasswordAuthentication(user,password);
        }
    }
}



MailSender.java
Code: Select all   Expand viewCollapse view
package util.mail;

import javax.mail.MessagingException;

public interface MailSender {

    /**
     * Sends a text mail with the given content.
     *
     * @param   content   The mail content
     */
    void send(Object content) throws MessagingException;
}



MailSenderConfiguration.java
Code: Select all   Expand viewCollapse view
package util.mail;

/**
* Configuration for sending a mail.
*/
public class MailSenderConfiguration {

    private String sender;
    private String smtpHost;
    private String user;
    private String password;
    private String subject;
    private String recipients;

    public MailSenderConfiguration() {
        this.sender     = null;
        this.smtpHost   = null;
        this.user       = null;
        this.password   = null;
        this.subject    = null;
        this.recipients = null;
    }

    public MailSenderConfiguration(String sender, String smtpHost, String user, String password, String subject, String recipients) {
        this.sender     = sender;
        this.smtpHost   = smtpHost;
        this.user       = user;
        this.password   = password;
        this.subject    = subject;
        this.recipients = recipients;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getSmtpHost() {
        return smtpHost;
    }

    public void setSmtpHost(String smtpHost) {
        this.smtpHost = smtpHost;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getRecipients() {
        return recipients;
    }

    public void setRecipients(String recipients) {
        this.recipients = recipients;
    }
}



MailContentFactory.java
Code: Select all   Expand viewCollapse view
package util.mail;

import javax.activation.DataContentHandler;
import javax.activation.DataContentHandlerFactory;

/**
* Creates a data content handler to handle plain text mails.
*
* http://forum.java.sun.com/thread.jspa?threadID=152756&messageID=441269
*/
public class MailContentFactory implements DataContentHandlerFactory {

    public DataContentHandler createDataContentHandler(String contentType) {

        if (contentType == null)
            return null;

        if(contentType.equalsIgnoreCase("text/plain")) {
            return new com.sun.mail.handlers.text_plain();
        }

        return null;
    }
}




Configuration files

The script needs three configuration files:
  1. parcel-descriptor.xml contains the script configuration so that OOo can find an execute the script
  2. log4j.properties contains the logging configuration
  3. mail.properties contains the settings for sending the mail via smtp with password authentication

parcel-descriptor.xml
Code: Select all   Expand viewCollapse view
<?xml version="1.0" encoding="UTF-8"?>
<parcel language="Java" xmlns:parcel="scripting.dtd">
  <script language="Java">
    <locale lang="en">
      <description>Sends a text email from an OOo Writer document.</description>
    </locale>
   <logicalname value="TextEmailSender"/>
    <functionname value="ooo.scripting.writer.TextEmailSender.sendTextEmail"/>
    <languagedepprops>
      <prop name="classpath" value=".:config:OOoTextMailSender.jar:apache/log4j-1.2.16.jar:mail/mail.jar"/>
    </languagedepprops>
  </script>
</parcel>



log4j.properties (change the names of the folders of the logging files if necessary)
Code: Select all   Expand viewCollapse view
log4j.rootLogger = DEBUG, Console, DebugFile, ErrorFile

log4j.appender.Console                          = org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout                   = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern = %d{ISO8601} %-5p [%t] %c: %m%n

log4j.appender.DebugFile                          = org.apache.log4j.FileAppender
log4j.appender.DebugFile.append                   = true
log4j.appender.DebugFile.file                     = c:/temp/debug.log
log4j.appender.DebugFile.layout                   = org.apache.log4j.PatternLayout
log4j.appender.DebugFile.layout.ConversionPattern = %d{ISO8601} %-5p [%t] %c: %m%n
log4j.appender.DebugFile.level                    = DEBUG

log4j.appender.ErrorFile                          = org.apache.log4j.FileAppender
log4j.appender.ErrorFile.append                   = true
log4j.appender.ErrorFile.file                     = c:/temp/error.log
log4j.appender.ErrorFile.layout                   = org.apache.log4j.PatternLayout
log4j.appender.ErrorFile.layout.ConversionPattern = %d{ISO8601} %-5p [%t] %c: %m%n
log4j.appender.ErrorFile.threshold                = ERROR



mail.properties (add your mail account settings and recipients proposal here)
Code: Select all   Expand viewCollapse view
#
# mail properties
#

# email address of sender account
mail.smtpauth.sender =

# host name of sender account
mail.smtpauth.host =

# user name of sender account
mail.smtpauth.user =

# password of sender account
mail.smtpauth.password =

# mail recipients
mail.recipients.proposal =




Deploy the script

The following steps are necessary to get the script running:
  1. Follow the steps described in the post [Java] OOo Writer and Calc macro examples (steps 1) through 6) below the second Java source code, add additionally the above mentioned JAR files in step 4) and use in step 5) the source code files of this script with their appropriate package names ooo.dialog, ooo.scripting.writer and util.mail) to compile the source code and to create a JAR file containing the script. According to the "parcel-descriptor.xml" as shown above this JAR file should be named OOoTextMailSender.jar. Otherwise the "parcel-descriptor.xml" must be change adequately.
  2. Create a new folder in the OOo user directory script folder. On a Windows system the name of this folder is something like "c:\documents and settings\<USERNAME>\application data\OpenOffice.org2\user\Scripts\". Create inside this folder a subfolder "java" and within that another like "sendmail" for example. The complete name of the folder for this script is than something like "c:\documents and settings\<USERNAME>\application data\OpenOffice.org2\user\Scripts\java\sendmail". Create additionally inside this folder three subfolders: "apache", "config", "mail".
  3. Following the "parcel-descriptor.xml" file from above put the following files in the newly-created subfolders:
    - parcel-descriptor.xml and the JAR file containing the script (OOoTextMailSender.jar, see above) from step 1. into "sendmail"
    - log4j-1.2.16.jar into "sendmail\apache"
    - log4j.properties and mail.properties into "sendmail\config"
    - mail.jar into "sendmail\mail"



Use the script

The following steps are necessary to execute the script:
  1. Start OOo (if it is already running, exit OOo and restart it).
  2. Open an OOo Writer document and call the TextEmailSender script through
    - "Tools" > "Macros" > "Run Macro..."
    - Expand the tree Library of My Macros and select the library sendmail
    - Select the Java macro ooo.scripting.writer.TextEmailSender.sendTextEmail and press the button "Run"

If everything went ok, you've send an email from OOo. If you encounter any errors, look into the logging files and try [Java] Remote debugging of scripts in Java get to the bottom of the problem.



Classpath of the parcel-descriptor.xml

In this script I've used several JAR files, which must be put into the classpath of the parcel-descriptor.xml file. The ability of the parcel-descriptor's classpath is limited compared to a Java classpath in general. In a parcel-descriptor you cannot add an absolute folder or JAR file name to the classpath. All folders and JAR files must reside inside the folder, the parcel-descriptor.xml file is located in, or in subfolders of this folder. This classpath limitation is caused by the way the ScriptProviderForJava builds the classpath. The ScriptProviderForJava uses in its ScriptImpl the class ScriptMetaData. The ScriptMetaData provides a method getClassPath() which uses PathUtils.make_url(). In PathUtils.make_url() the StringBuffer which is used to build the URL is always initialized with the base url (the folder the parcel-descriptor.xml file is located in) of the script. This makes the usage of an absolute folder name impossible.

In the parcel-descriptor above I added the current folder ., the subfolder config and all the above mentioned JAR files with the folder they are located in. The current folder . is not necessary. Very important is the subfolder config in the classpath, so that log4j can find it's properties file and the mail properties can be located. Equally important is to put all the JAR files in the classpath.

A parcel-descriptor.xml can contain several <script> tags. Each <script> has its own <languagedepprops> tag. So every Java script can and must use its own classpath. If no classpath is provided at all, the current folder . (the folder the parcel-descriptor.xml file is located in) is provided for the script.
Last edited by hol.sten on Mon Dec 27, 2010 5:02 pm, edited 1 time in total.
OOo 3.2.0 on Ubuntu 10.04 • OOo 3.2.1 on Windows 7 64-bit and MS Windows XP
hol.sten
Volunteer
 
Posts: 495
Joined: Mon Oct 08, 2007 1:31 am
Location: Hamburg, Germany

Re: [Java] Send an email from an OOo Writer document

Postby hol.sten » Mon Mar 24, 2008 9:31 pm

27. Dec 2010: Just tried, if the example is still working, updated the names of some libraries and removed most references of activation.jar
24. Mar 2008: Creation of the Java script
OOo 3.2.0 on Ubuntu 10.04 • OOo 3.2.1 on Windows 7 64-bit and MS Windows XP
hol.sten
Volunteer
 
Posts: 495
Joined: Mon Oct 08, 2007 1:31 am
Location: Hamburg, Germany

Re: [Java] Send an email from an OOo Writer document

Postby hol.sten » Mon Dec 27, 2010 6:46 pm

Sending Mails With an Attachment

The source code in the first post of this thread handles only simple text mails. For sending mails with attachments the source code needs some enhancements:

1) The MailContentFactory must deal with multipart/mixed contents, too:
Code: Select all   Expand viewCollapse view
package util.mail;

import javax.activation.DataContentHandler;
import javax.activation.DataContentHandlerFactory;

import org.apache.log4j.Logger;

/**
* Creates a data content handler to handle plain text and multipart mails.
*
* http://forum.java.sun.com/thread.jspa?threadID=152756&messageID=441269
*/
public class MailContentFactory implements DataContentHandlerFactory {

    /** Logger */
    private static final Logger LOGGER = Logger.getLogger(MailContentFactory.class);

    public DataContentHandler createDataContentHandler(String contentType) {

        LOGGER.info("Content type:" + (contentType == null? "not available": contentType));

        if (contentType == null)
            return null;

        if (contentType.equalsIgnoreCase("text/plain")) {
            return new com.sun.mail.handlers.text_plain();
        }

        if (contentType.equalsIgnoreCase("multipart/mixed")) {
            return new com.sun.mail.handlers.multipart_mixed();
        }

        return null;
    }
}


2) A method for creating mail contents consisting of a text part and an attachment is needed. Just for simplicity such a method has been added to the SmtpMailSender as a public static method creating and returning the content (see createMultipartContent() in the following code):
Code: Select all   Expand viewCollapse view
package util.mail;

import java.util.Properties;
import java.util.StringTokenizer;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

/**
* Mail sender for sending mails via smtp with password authentication.
*/
public class SmtpMailSender implements MailSender {

    /** Mail sender configuration */
    private MailSenderConfiguration config;

    /**
     * Constructs a mail sender which uses the mail sender configuration.
     *
     * @param   mailSenderConfiguration   The mail sender configuration
     */
    public SmtpMailSender(MailSenderConfiguration mailSenderConfiguration) {

        config = mailSenderConfiguration;

        try {
            DataHandler.setDataContentHandlerFactory(new MailContentFactory());
        }
        catch (java.lang.Error error) {
            System.out.println("DataContentHandlerFactory already defined: "+error.getMessage());
        }
    }

    /**
     * Sends a text mail with the given content.
     *
     * @param   content   The mail content
     */
    public void send(Object content, String contentType) throws MessagingException {

        Properties mailProperties = new Properties();
        mailProperties.put("mail.smtp.host", config.getSmtpHost());
        mailProperties.put("mail.smtp.auth", "true");

        Authenticator smtpAuthenticator = new SmtpMailAuthenticator(config.getUser(),config.getPassword());
        Session smtpSession = Session.getInstance(mailProperties,smtpAuthenticator);
        if (smtpSession != null) {
            StringTokenizer tokenizer = new StringTokenizer(config.getRecipients(),";");
            Address[] addresses = new InternetAddress[tokenizer.countTokens()];
            int i = 0;
            while (tokenizer.hasMoreTokens()) {
                String recipient = tokenizer.nextToken();
                addresses[i] = new InternetAddress(recipient);
                i++;
            }

            Message mimeMessage = new MimeMessage(smtpSession);
            mimeMessage.setFrom(new InternetAddress(config.getSender()));
            mimeMessage.setRecipients(Message.RecipientType.TO,addresses);
            mimeMessage.setSubject(config.getSubject());
            mimeMessage.setContent(content, contentType);
            mimeMessage.saveChanges();

            Transport transport = smtpSession.getTransport("smtp");
            transport.connect();
            transport.sendMessage(mimeMessage,addresses);
            transport.close();
        }
    }


    /**
     * Authenticator for sending mails via smtp with password authentication.
     *
     * @param   content   The mail content
     */
    private class SmtpMailAuthenticator extends Authenticator {

        private String user;
        private String password;

        public SmtpMailAuthenticator(String user, String password) {

            super();

            this.user = user;
            this.password = password;
        }

        @Override
        public PasswordAuthentication getPasswordAuthentication() {

            return new PasswordAuthentication(user,password);
        }
    }


    /**
     * Create a simple multipart mail content from a plain text message and a file name.
     *
     * @param   message   The plain text message
     * @param   filename  The file name of the attachment
     * @return            The multipart mail content
     */
    public static MimeMultipart createMultipartContent(String message, String filename) throws MessagingException {

        MimeMultipart content = new MimeMultipart();

        MimeBodyPart text = new MimeBodyPart();
        text.setText(message);
        text.setHeader("MIME-Version", "1.0");
        text.setHeader("Content-Type", text.getContentType());
        content.addBodyPart(text);

        MimeBodyPart file = new MimeBodyPart();
        DataSource fileDataSource = new FileDataSource(filename);
        file.setDataHandler(new DataHandler(fileDataSource));
        String receiverFilename;
        int letzterSlash = filename.lastIndexOf("/");
        if (letzterSlash >= 0) {
            receiverFilename = filename.substring(letzterSlash+1);
        }
        else {
            receiverFilename = filename;
        }
        file.setFileName(receiverFilename);
        content.addBodyPart(file);

        return content;
    }
}


3) The TextEmailSender class needs some improvements to ask for an attachment and to send an email with a given attachment:
Code: Select all   Expand viewCollapse view
package ooo.scripting.writer;

import java.io.IOException;
import java.util.Properties;
import javax.mail.MessagingException;

import org.apache.log4j.Logger;

import com.sun.star.awt.ActionEvent;
import com.sun.star.awt.KeyEvent;
import com.sun.star.awt.MouseEvent;
import com.sun.star.awt.PushButtonType;
import com.sun.star.awt.XTextComponent;
import com.sun.star.container.XIndexAccess;
import com.sun.star.lang.IndexOutOfBoundsException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.text.XText;
import com.sun.star.text.XTextDocument;
import com.sun.star.text.XTextRange;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.view.XSelectionSupplier;
import javax.mail.internet.MimeMultipart;

import ooo.dialog.Dialog;
import util.mail.MailSender;
import util.mail.SmtpMailSender;
import util.mail.MailSenderConfiguration;

public class TextEmailSender {

    /** Logger */
    private static final Logger LOGGER = Logger.getLogger(TextEmailSender.class);

    /** New line separator */
    public static final String NEWLINE = System.getProperty("line.separator");

    /** Name of the mail properties file */
    private static final String MAIL_PROPERTIES = "mail.properties";

    /** Max length of automatically created mail subject */
    private static final int MAX_SUBJECT_LENGTH = 80;



    /** OOo component context for showing a dialog */
    private XComponentContext xcomponentcontext;

    /** OOo Writer text document */
    private XTextDocument xtextdocument;

    /** Mail subject */
    private String subject;

    /** Mail recipients */
    private String recipients;

    /** Mail attachment */
    private String attachment;



    /**
     * Constructs a text email sender which uses the OOo component context and
     * OOo Writer text document.
     *
     * @param   xcomponentcontext   The OOo component context
     * @param   xtextdocument       The OOo Writer text document
     */
    public TextEmailSender(XComponentContext xcomponentcontext, XTextDocument xtextdocument) {

        this.xcomponentcontext = xcomponentcontext;
        this.xtextdocument = xtextdocument;

        this.subject = null;
        this.recipients = null;
        this.attachment = null;

        LOGGER.info("TextEmailSender constructed");
    }


    /**
     * Returns the current selections of the text document or the whole text of
     * the text document, if no text is selected.
     *
     * @return               The current selections or the whole text of the text document
     */
    private String getDocumentText() throws IndexOutOfBoundsException, WrappedTargetException {

        // Get all selected regions of the document
        XSelectionSupplier xSelectionSupplier = (XSelectionSupplier) UnoRuntime.queryInterface(XSelectionSupplier.class, xtextdocument.getCurrentController());
        XIndexAccess xIndexAccess = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xSelectionSupplier.getSelection());

        String text = null;

        int count = xIndexAccess.getCount();
        if (count > 0) {
            // Get text inside selected regions
            for (int i=0; i<count; i++) {
                XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, xIndexAccess.getByIndex(i));
                text = (text == null)? "": text+NEWLINE+NEWLINE;
                text += xTextRange.getString();
            }
        }

        if (text == null || text.trim().length() == 0) {
            // Get text of the document
            XText xtext = xtextdocument.getText();
            text = (xtext != null)? xtext.getString(): "";
        }

        LOGGER.info("Document text determined");
        return text;
    }

    /**
     * Returns a mail subject proposal for mailing the text.
     *
     * Takes the first line of the text. If the first line is longer than the
     * maximum subject line, everything after the maximum subject line length
     * and the last blank of that subject will be removed.
     *
     * @param   text         The text
     * @return               The subject proposal
     */
    private String getSubjectProposal(String text) {

        int maxSubjectLength = (text.indexOf(NEWLINE) > 0)? text.indexOf(NEWLINE): MAX_SUBJECT_LENGTH + 1;
        String subjectProposal = text.substring(0, Math.min(text.length(), maxSubjectLength));
        if (subjectProposal.length() > MAX_SUBJECT_LENGTH) {
            subjectProposal = text.substring(0, MAX_SUBJECT_LENGTH);
            if (subjectProposal.lastIndexOf(" ") > 0) {
                subjectProposal = subjectProposal.substring(0, subjectProposal.lastIndexOf(" "));
            }
        }

        LOGGER.info("Subject proposal determined: '"+subjectProposal+"'");
        return subjectProposal;
    }

    /**
     * Shows a dialog to initialize subject, recipients and attachment of the mail.
     *
     * @param   subjectProposal           The subject proposal
     * @param   recipientsProposal        The recipients proposal
     * @param   attachmentProposal        The attachment proposal
     */
    private void initSubjectAndRecipientsAndAttachment(String subjectProposal, String recipientsProposal, String attachmentProposal) throws Exception {

        Dialog dialog = new Dialog(xcomponentcontext,100,100,100,280,"Send Mail","sendMailDialog");

        dialog.insertFixedText(10, 10, 30, "Subject:");
        XTextComponent subjectField = dialog.insertTextField(42, 10, 228, subjectProposal);
        dialog.insertFixedText(10, 30, 30, "Recipients:");
        XTextComponent recipientsField = dialog.insertTextField(42, 30, 228, recipientsProposal);
        dialog.insertFixedText(10, 50, 30, "Attachment:");
        XTextComponent attachmentField = dialog.insertTextField(42, 50, 228, attachmentProposal);

        dialog.insertButton(105, 80, 30, "OK", PushButtonType.OK_value);
        dialog.insertButton(145, 80, 30, "Cancel", PushButtonType.CANCEL_value);

        short returnValue = dialog.execute();

        if (returnValue == PushButtonType.OK_value) {
            subject = (subjectField.getText().trim().length()>0)? subjectField.getText(): null;
            recipients = (recipientsField.getText().trim().length()>0)? recipientsField.getText(): null;
            attachment = (attachmentField.getText().trim().length()>0)? attachmentField.getText(): null;
        }

        dialog.dispose();
    }

    /**
     * Sends the email.
     *
     * @param   text           The email text
     */
    private void sendMail(String text) throws Exception, IOException, MessagingException {

        Properties mailProperties = new Properties();
        mailProperties.load(this.getClass().getClassLoader().getResourceAsStream(MAIL_PROPERTIES));

        String subjectProposal = getSubjectProposal(text);
        String recipientsProposal = mailProperties.getProperty("mail.recipients.proposal");

        initSubjectAndRecipientsAndAttachment(subjectProposal, recipientsProposal, null);

        if (subject != null && recipients != null) {
            MailSenderConfiguration mailSenderConfiguration = new MailSenderConfiguration();
            mailSenderConfiguration.setSender(mailProperties.getProperty("mail.smtpauth.sender"));
            mailSenderConfiguration.setSmtpHost(mailProperties.getProperty("mail.smtpauth.host"));
            mailSenderConfiguration.setUser(mailProperties.getProperty("mail.smtpauth.user"));
            mailSenderConfiguration.setPassword(mailProperties.getProperty("mail.smtpauth.password"));
            mailSenderConfiguration.setSubject(subject);
            mailSenderConfiguration.setRecipients(recipients);

            if (attachment == null) {
                MailSender mailSender = new SmtpMailSender(mailSenderConfiguration);
                mailSender.send(text,"text/plain");
            }
            else {
                MimeMultipart content = SmtpMailSender.createMultipartContent(text,attachment);
                MailSender mailSender = new SmtpMailSender(mailSenderConfiguration);
                mailSender.send(content,content.getContentType());
            }

            LOGGER.info("Email send");
        }
        else {
            LOGGER.warn("No email send");
        }
    }

    /**
     * Gets the text from the OOo Writer text document and sends an email.
     */
    public void send() {

        try {
            String documentText = getDocumentText();
            sendMail(documentText);
        } catch (Exception ex) {
            String message = "Sending text email failed: "+ex.getMessage()+ex.getStackTrace();
            LOGGER.error(message);
            throw new RuntimeException(message,ex);
        }
    }



    /**
     * Sending text email called from a toolbar.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored number
     */
    public static void sendTextEmail(XScriptContext xScriptContext, Short ignored) {
        LOGGER.info("Sending text email called from a toolbar");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a button with an action.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored action event
     */
    public static void sendTextEmail(XScriptContext xScriptContext, ActionEvent ignored) {
        LOGGER.info("Sending text email called from a button with an action");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a button with a key.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored key event
     */
    public static void sendTextEmail(XScriptContext xScriptContext, KeyEvent ignored) {
        LOGGER.info("Sending text email called from a button with a key");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a button with the mouse.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     * @param   ignored            The ignored mouse event
     */
    public static void sendTextEmail(XScriptContext xScriptContext, MouseEvent ignored) {
        LOGGER.info("Sending text email called from a button with the mouse");
        sendTextEmail(xScriptContext);
    }

    /**
     * Sending text email called from a menu or the "Run Macro..." menu.
     *
     * @param   xScriptContext     The script context of OOo's scripting framework
     */
    public static void sendTextEmail(XScriptContext xScriptContext) {

        LOGGER.info("Sending text email called");

        XComponentContext xcomponentcontext = xScriptContext.getComponentContext();
        XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xScriptContext.getDocument());
        if (xtextdocument != null) {
            TextEmailSender textEmailSender = new TextEmailSender(xcomponentcontext,xtextdocument);
            textEmailSender.send();
        }
    }
}


Deploying everything together with the unmodified source codes and configurations of the first post in this thread OOo Writer can at least send once an email with an attachment.
OOo 3.2.0 on Ubuntu 10.04 • OOo 3.2.1 on Windows 7 64-bit and MS Windows XP
hol.sten
Volunteer
 
Posts: 495
Joined: Mon Oct 08, 2007 1:31 am
Location: Hamburg, Germany


Return to OpenOffice Basic, Python, BeanShell, JavaScript

Who is online

Users browsing this forum: No registered users and 1 guest