dimarts, 12 d’octubre del 2010

Com fer una llibreria de macros java de l'OpenOffice.org que accedeixen a la textbox d'un form de Writer.

Sense cap mena de dubte, la millor forma de fer macros per a l'OpenOffice.org és amb el OOoBasic.

Dit això, en ocasions pot ser interessant plantejar aplicacions java que modifiquin documents d'OpenOffice.org, les aproximacions java a OpenOffice poden ser de tres formes principals:

1. Aplicació StandAlone que és capaç d'obrir un OpenOffice.org i manipular-ne els documents
2. Una llibreria de macros java que és invocada des d'un OpenOffice.org
3. Component UNO java que és invocat des del menú o des d'una macro en OOoBasic (o en java, o python...) i que és una extensió del core de l'OpenOffice.org

En aquest post d'avui, faré una llibreria de macros java que invocaré des de botons al document de text i que escriuran  frases al final del document i modificaran el text d'una textbox que anomenaré textbox1.


parcel-descriptor.xml
La llibreria de macros java la posaré a /home/elmeuusuari/.openoffice.org/3/user/Scripts/java/ de forma que formarà part del conjunt de "les meves macros". La llibreria també podria formar part de "les macros de l'OpenOffice.org", de forma que estaria compartida a tots els usuaris d'aquesta instal·lació d'OpenOffice.org i aleshores caldria posar-la a /opt/openoffice.org/basis3.2/share/Scripts/java. No és possible associar  macros java a un document.

Per compilar una macro java de l'OpenOffice.org és necesari enllaçar, almenys, les llibreries:
/opt/openoffice.org/ure/share/java/jurt.jar
/opt/openoffice.org/ure/share/java/ridl.jar
/opt/openoffice.org/basis3.2/program/classes/unoil.jar

A més, per a que l'OpenOffice.org sàpiga que té una nova llibreria de macros, cal preparar un fitxer descriptor, el parcel-descriptor.xml, on s'indica quina és la nova llibreria, en quin jar es troba i quin mètode corresponen a cada macro.

En el cas que m'ocupa, el parcel-descriptor.xml és el següent:


<?xml version="1.0" encoding="UTF-8"?>
<parcel language="Java" xmlns:parcel="scripting.dtd">
  <!-- mètode 1 -->
  <script language="Java">
    <locale lang="Es-ca">
      <displayname value="MacroProves.DispName"/>
      <description>Mostra un missatge</description>
    </locale>

    <functionname value="com.serveisticiprogramarilliure.MacroProves.print"/>
    <logicalname value="MacroProves.print"/>

    <languagedepprops>
      <prop name="classpath" value="OOo-java-macro-01.jar"/>
    </languagedepprops>
  </script>

  <!-- mètode 2 -->
  <script language="Java">
    <locale lang="Es-ca">
      <displayname value="MacroProves.DispName"/>
      <description>Mostra un missatge</description>
    </locale>

    <functionname value="com.serveisticiprogramarilliure.MacroProves.print2"/>
    <logicalname value="MacroProves.print2"/>

    <languagedepprops>
      <prop name="classpath" value="OOo-java-macro-01.jar"/>
    </languagedepprops>
  </script>
</parcel>



parcel. Document que agrupa les macros, conceptualment: la llibreria
script. Bloc que defineix cada macro
functioname. És el nom del mètode java que correspon a la macro
logicalname. El nom lògic de la macro
languagedepprops. Indica la llibreria jar que cal carregar
displayname. Nom de la llibreria
description. Descripció de la llibreria

Vaig per la llibreria pròpiament dita. Faré una classe amb el parell de mètodes print i print2 que seran els que invocaré des dels botons.



MacroProves.java

Vet aquí la classe: MacroProves.java

package com.serveisticiprogramarilliure;


  import com.sun.star.script.provider.XScriptContext;
  import com.sun.star.uno.UnoRuntime;
  import com.sun.star.text.XTextDocument;
  import com.sun.star.text.XTextRange;
  import com.sun.star.text.XText;
  import com.sun.star.awt.MouseEvent;
  import com.sun.star.container.XIndexAccess;
  import com.sun.star.container.XNameAccess;
  import com.sun.star.container.XNameContainer;
  import com.sun.star.drawing.XDrawPage;
  import com.sun.star.drawing.XDrawPageSupplier;
  import com.sun.star.form.XForm;
  import com.sun.star.form.XFormComponent;
  import com.sun.star.form.XFormsSupplier;
  import com.sun.star.frame.XModel;
  import com.sun.star.frame.XController;


  /**
   *  MacroProves
   *
   */


  public class MacroProves {
    public static void main(String[] args) {
     // TODO Auto-generated method stub
    }


    public static void print(XScriptContext xSc) {
      // getting the text document object
      XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());
      XText xText = xtextdocument.getText();
      XTextRange xTextRange = xText.getEnd();
      xTextRange.setString( "MacroProves.print.\n" );
      snippet(xSc, "a");
    }// printHW




    public static void print(XScriptContext xSc, MouseEvent meEvent) {
        // getting the text document object
        XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());
        XText xText = xtextdocument.getText();
        XTextRange xTextRange = xText.getEnd();
        xTextRange.setString( "MacroProves.print. Invocació des de botó.\n" );
        snippet(xSc, "b");
    }   


    public static void print2(XScriptContext xSc) {
        // getting the text document object
        XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());
        XText xText = xtextdocument.getText();
        XTextRange xTextRange = xText.getEnd();
        xTextRange.setString( "MacroProves.print2.\n" );
        snippet(xSc, "c");
      }// printHW




      public static void print2(XScriptContext xSc, MouseEvent meEvent) {
          // getting the text document object
          XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());
          XText xText = xtextdocument.getText();
          XTextRange xTextRange = xText.getEnd();
          xTextRange.setString( "MacroProves.print2. Invocació des de botó.\n" );
          snippet(xSc, "d");
      }    
    
    
    private static void snippet(XScriptContext xSc, String sXY) {
        try {
              XTextDocument xdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());
              XController xcontroller = (XController) UnoRuntime.queryInterface(XController.class, xdocument.getCurrentController());
              XModel xmodel = (XModel) UnoRuntime.queryInterface(XModel.class, xcontroller.getModel());
              XDrawPageSupplier xDrawPageSupplier = (XDrawPageSupplier) UnoRuntime.queryInterface(XDrawPageSupplier.class, xmodel);
              XDrawPage xDrawPage = xDrawPageSupplier.getDrawPage();
              XFormsSupplier xFormsSupplier = (XFormsSupplier) UnoRuntime.queryInterface(XFormsSupplier.class, xDrawPage);
              XNameContainer xNameContainer = xFormsSupplier.getForms();
              XIndexAccess xIndexAccess = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xNameContainer);
              XForm xForm = (XForm) UnoRuntime.queryInterface(XForm.class, xIndexAccess.getByIndex(0));
              XNameAccess xNameAccess = (XNameAccess) UnoRuntime.queryInterface(XNameAccess.class, xForm);
              XFormComponent xFormComponent = (XFormComponent) UnoRuntime.queryInterface(XFormComponent.class, xNameAccess.getByName("textbox1"));
              XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, xFormComponent);
              String sActual = xTextRange.getString();
              xTextRange.setString(sActual + " " + sXY);
        } catch (Exception e4) {
         // getByName 
         e4.printStackTrace();
        }
    }    
  }  

Vàries a coses a comentar:

1. Els mètodes print i print2 estan sobrecarregats. amb signatures (XScriptContext xSc) i  (XScriptContext xSc, MouseEvent meEvent). El motiu és que depenent de l'esdeveniment que invoca la macro es passa un segon argument que pot ser de diferents tipus.
  • Els mètodes amb un únic argument són invocats des del Dialeg d'Executar Macro, o des d'un menú, o des d'una barra d'eines. Als fòrums d'OpenOffice.org es diu que quan s'invoca el mètode de macro des d'una barra d'eines es rep un Short com segon argument. Però en les meves proves amb un OOo3.2.1 sobre Ubuntu 10.04 quan invoco des d'una barra d'eines no es rep aquest segon argument.
  • Els mètodes de la macro que s'executen en resposta a un esdeveniment de mouse, com "en prémer el botó del ratolí", de la llista d'esdeveniments del botó, reben un MouseEvent.
  • Els mètodes de la macro que s'executen en resposta a un esdeveniment d'acció, com "Executa l'acció" de la llista d'esdeveniments del botó, reben un ActionEvent.
  • Els mètodes de la macro que s'executen en resposta a un esdeveniment de focus, com "En rebre el focus" de la llista d'esdeveniments del botó, reben un FocusEvent.
2. Els mètodes de macro han de ser public static void

3. El primer argument que reben els mètodes de macro sempre és un XScriptContext.
XScripContext és una interfase UNO que permet accedir a la resta d'interfases i de serveis (la funcionalitat) del document.

En particular, la forma d'accedir al Document de Writer a partir del XScripContext és:

XTextDocument xtextdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());


3.1 La invocació al UnoRuntime .queryInterface és un mantra que es repeteix constantment a l'hora d'accedir als diferents elements del document. Quan invoco UnoRuntime.queryInterface el resultat és que l'objecte fa un "casting" a la interfase demanada. Adonem-nos que les interfases no són interfases java, sinó interfases UNO. Un objecte UNO "implementa" un conjunt de interfases UNO. La forma d'accedir a les diferents interfases és amb el mecanisme UnoRuntime.queryInterface. 


3.2 L'altra clau de l'èxit en el treball amb macros java és disposar de documentació sobre les interfases i els mètodes que les defineixen. No hi ha més remei que fer servir la documentació: Hi han centenars (milers?) d'interfases. Consulteu http://api.openoffice.org/.

Per exemple,  XScriptContext té els següents mètodes:







getDocumentObtain the document reference on which the script can operate  
getInvocationContextprovides access to the context where the script was invoked  
getDesktopObtain the desktop reference on which the script can operate  
getComponentContextObtain the component context which the script can use to create other uno components



I XTextDocument, aquests altres:







getText
reformatreformats the contents of the document.  



També he fet servir XText i XTextRange. En realitat XText és una interfase que deriva de la interfase XSimpleText, que a la seva vegada deriva de XTextRange. O sigui: XText té tots els mètodes de XSimpleText i de XTextRange. 


XText






insertTextContentinserts a content, such as a text table, text frame or text field.  
removeTextContentremoves the specified content from the text object. 



XSimpleText






createTextCursor
createTextCursorByRange
insertStringinserts a string of characters into the text.  
insertControlCharacterinserts a control character (like a paragraph break or a hard space) into the text.


XTextRange






getText
getStart
getEnd
getString
setStringthe whole string of characters of this piece of text is replaced.



Per tant, les línies 






XText xText = xtextdocument.getText();
XTextRange xTextRange = xText.getEnd();
xTextRange.setString( "MacroProves.print.\n" )



Es poden simplificar així:






xtextdocument.getText().getEnd().setString( "MacroProves.print.\n" )





3.3 la tercera clau de l'èxit, juntament amb el mantra UnoRuntime.queryInterface i la documentació és la comprensió de l'estructura interna dels documents d'OpenOffice.org.


El mètode snippet és molt interessant i desvetlla part d'aquesta estructura interna. Vet aquí el detall del que fa:


Primer de tot, obtinc la interfase del XTextDocument a partir de l'ScriptContext 






XTextDocument xdocument = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, xSc.getDocument());

Ara obtinc el Controlador, interfase XController,  del XTextDocument. Els document d'OpenOffice segueixen internament el patró FMC (Frame Model Controller) que és una variant del prou conegut patró MVC (Model View Controller).  
XController xcontroller = (XController) UnoRuntime.queryInterface(XController.class, xdocument.getCurrentController());


A través del controlador obtinc el model, interfase XModel, que representa les dades i els objectes del document.
XModel xmodel = (XModel) UnoRuntime.queryInterface(XModel.class, xcontroller.getModel());


Del model obtinc el proveïdor de la DrawPage, és dir la interfase XDrawPageSupplier. Al tractar-se d'un TextDocument només té una DrawPage. Si es tractes d'un document de Draw, Impress o Calc, aleshores podria tenir vàries DrawPages (en plural), la interfase que caldria obtenir seria la XDrawPagesSupplier (adoneu-vos del plural). 
La DrawPage és un contenidor dels objectes dibuixats al document. En particular conté formularis i els controls del formulari.  
XDrawPageSupplier xDrawPageSupplier = (XDrawPageSupplier) UnoRuntime.queryInterface(XDrawPageSupplier.class, xmodel);


Un cop tinc el proveïdor, obtinc la DrawPage. En aquest cas, el proveïdor em proporciona l'objecte i per això és una crida de mètode directa. No cal invocar la interfase amb UnoRuntime.queryInterface.
XDrawPage xDrawPage = xDrawPageSupplier.getDrawPage();


De la DrawPage obtinc la interfase del proveïdor de Forms (XFormsSupplier):
XFormsSupplier xFormsSupplier = (XFormsSupplier) UnoRuntime.queryInterface(XFormsSupplier.class, xDrawPage);


El proveïdor de forms em dona, directament, "l'enumeració" (XNameContainer) dels forms.    
XNameContainer xNameContainer = xFormsSupplier.getForms();


De l'enumeració dels forms obtinc una interfase per accedir als forms per índex (XIndexAccess)
XIndexAccess xIndexAccess = (XIndexAccess) UnoRuntime.queryInterface(XIndexAccess.class, xNameContainer);


En el cas que ens ocupa només hi ha un form, per tant és el d'índex 0. Obtinc la interfase XForm  per índex.
XForm xForm = (XForm) UnoRuntime.queryInterface(XForm.class, xIndexAccess.getByIndex(0));


Ara buscaré dins del form al meu objecte textbox1 per nom, per tant obtinc una interfase d'acceś per nom. (XNameAccess)
XNameAccess xNameAccess = (XNameAccess) UnoRuntime.queryInterface(XNameAccess.class, xForm);


Finalment obtinc el component textbox1 (XFormComponent) 
XFormComponent xFormComponent = (XFormComponent) UnoRuntime.queryInterface(XFormComponent.class, xNameAccess.getByName("textbox1"));


I del component, n'obtinc les dades de text (XtextRange)
XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, xFormComponent);


N'obtinc el text actual i li afegeixo una lletra
String sActual = xTextRange.getString();
xTextRange.setString(sActual + " " + sXY)





build.xml


I tot això com és desplega?
Com he dit al principi, posaré la llibreria de macros a /home/elmeuusuari/.openoffice.org/3/user/Scripts/java/

Si observem aquesta carpeta trobarem l'exemple de la macro java HelloWorldJava que és el que ha servit de base per al post d'avui. A la carpeta HelloWorldJava hi trobem el jar, el fitxer font java i el parcel-descriptor.xml.  Es tracta de fer el mateix.

Una forma de fer ho és amb un fitxer d'ant. Jo he fet servir aquest:
  

<project name="MacroProves" default="dist" basedir=".">
  <description>
  compila i genera el jar de la macro per a l'OpenOffice
  </description>
  
  <!-- estableix propietats globals -->
  <property name="src" location="src"/>
  <property name="build" location="build"/>
  <property name="config" location="config"/>
  <property name="dist"  location="MacroProves"/>
  <property name="cp-java-OOo-1" 
            location="/opt/openoffice.org/ure/share/java/" />
  <property name="cp-java-OOo-2" 
            location="/opt/openoffice.org/basis3.2/program/classes/" />
  <property name="OOo-user-macros"
            location="/home/albert/.openoffice.org/3/user/Scripts/java/" />
  <property name="OOo-shared-macros" 
            location="/opt/openoffice.org/basis3.2/share/Scripts/java/" />



  <target name="init" depends="clean">
    <!-- time stap -->
    <tstamp/>
    <!-- Crea el directory build utilitzat en la compilació -->
    <mkdir dir="${build}"/>
  </target>


  <target name="compile" depends="init"
    description="compila els fitxers font" >


    <!-- Compila les fonts java de ${src} en ${build} -->
    <javac srcdir="${src}" destdir="${build}">
      <classpath>
        <pathelement location="${cp-java-OOo-1}/jurt.jar"/>
       <pathelement location="${cp-java-OOo-1}/ridl.jar"/>
       <pathelement location="${cp-java-OOo-2}/unoil.jar"/>
      </classpath>
   </javac>
  </target>


  <target name="dist" depends="compile" description="genera la  distribució" >
    <!-- Crea el directori de distribució -->
    <mkdir dir="${dist}"/>


    <!-- Posa tot el que hi ha a ${build} dins de OOo-java-macro-01.jar -->
    <jar jarfile="${dist}/OOo-java-macro-01.jar" basedir="${build}" />
  
   <!-- també posa el fitxer font -->
   <copy todir="${dist}"> 
   <fileset dir="${src}" />
   </copy>
  
   <!-- i el parcel-descriptor.xml -->
   <copy file="${config}/parcel-descriptor.xml" todir="${dist}" />
  
   <!-- i ho copia tot a la carpeta de macros java de l'usuari-->
   <copy todir="${OOo-user-macros}/MacroProves">
   <fileset dir="${dist}" />
   </copy>
  
   <!-- també ho podria copiar a la carpeta de macros java compartides -->
   <!--
   <copy todir="${OOo-shared-macros}/HelloWorldJava">
   <fileset src="${dist}" />
   </copy>
   -->
  </target>


  <target name="clean" description="fa neteja" >
    <!-- esborra els directoris ${build} and ${dist}-->
    <delete dir="${build}"/>
    <delete dir="${dist}"/>
  </target>
</project>




El document de Writer amb el formulari
Finalment, creo un document de text amb Writer. En aquest document hi posaré una textbox que anomenaré textbox1; un botó que anomeno boto1, i enllaço l'esdeveniment "en prémer el botó del mouse" amb MacroProves.print; i un botó que anomeno boto2 i enllaço l'esdeveniment "en prémer el botó del mouse" amb MacroProves.print2;
Cada botó  activa respectivament una macro java.




Albert Baranguer
Barcelona 12/10/2010

Cap comentari:

Publica un comentari a l'entrada