Saving and Reading Data for the Options Page

From Apache OpenOffice Wiki
Jump to: navigation, search



An options page typically allows the user to enter some data, which of course must be saved when the user presses the OK button. When the options page is displayed it should show the data which the user entered previously. In case nothing has ever been entered, the options page could show some “default” data or nothing at all.

How the data is saved and where it is stored is not covered by the specification. It only defines the events “ok”, “initialize”, and “back” which the extension needs to process in order to save the entered data, initialize the controls with data, or restore the state of the controls with the previously saved data. The “ok” and “back” events are triggered by the “OK” and “Back” button of the options dialog. “initialize” is called before the options page is being displayed. In most cases “initialize” and “back” have the same meaning.

In order to receive these events one has to provide a service that implements the interface com.sun.star.awt.XContainerWindowEventHandler. The component is then installed like any other component. That is, one provides for example a jar file or a dll and adds the proper entries to the manifest.xml.

The action events are processed in the callHandlerMethod. This method takes three parameters. The first is a XWindow which represents the dialog for which the event is called. The second is an com.sun.star.uno.Any, which describes the actual event. Therefore the IDL calls it the “EventObject”. The last parameter is a string which contains a “method name”. This method may not exists, but the name identifies an action which should be invoked.

In case of our previously mentioned events the method is called with the respective XWindow interface, a method name of “external_event”, and an any containing either “ok”, “back”, or “initialize”. For example, the java code could look like this:

public boolean callHandlerMethod(com.sun.star.awt.XWindow aWindow,
  Object aEventObject, String sMethod)
  throws WrappedTargetException {
  if (sMethod.equals("external_event") ){
    try {
      return handleExternalEvent(aWindow, aEventObject);
    } catch (com.sun.star.uno.RuntimeException re) {
    throw re;
    } catch (com.sun.star.uno.Exception e) {
      throw new WrappedTargetException(sMethod, this, e);
    }
  } else if (sMethod.equals("another_method_name") ){
  ...
  }
 
  return false;
}
 
private boolean handleExternalEvent(com.sun.star.awt.XWindow aWindow, Object aEventObject)
  throws com.sun.star.uno.Exception {
  try {
    String sMethod = AnyConverter.toString(aEventObject);
    if (sMethod.equals("ok")) {
      saveData(aWindow);
    } else if (sMethod.equals("back") || sMethod.equals("initialize")) {
      loadData(aWindow);
    }
  } catch (com.sun.star.lang.IllegalArgumentException e) {
    throw new com.sun.star.lang.IllegalArgumentException(
      "Method external_event requires a string in the event object argument.", this,
      (short) -1);
  }
}

The method saveData and loadData need to be implemented according to where the data actually is stored. In most cases the OpenOffice.org's registry is a suitable place. Then, of course, one needs to provide a configuration schema (requires an appropriate entry in the manifest.xml as well).

For example:

<?xml version="1.0" encoding="UTF-8"?>
<oor:component-schema xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="ExtensionData" oor:package="org.openoffice.desktop.deployment.options" xml:lang="en-US">
  <info>
    <author></author>
    <desc>Contains the options data used for the test extensions.</desc>
  </info>
  <templates>
    <group oor:name="Leaf">
      <info>
        <desc>The data for one leaf.</desc>
      </info>
      <prop oor:name="String0" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String1" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String2" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String3" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String4" oor:type="xs:string">
        <value></value>
      </prop>
    </group>
  </templates>
  <component>
    <group oor:name="Leaves">
      <node-ref oor:name="Writer1" oor:node-type="Leaf" />
      <node-ref oor:name="Writer2" oor:node-type="Leaf" />
      <!-- .... -->
    </group>
  </component>
</oor:component-schema>

Please make sure that the package (oor:package) together with the name (oor:name) for this schema are unique. For example, it should start with YOUR reversed domain name (do not use org.openoffice in your code), followed by the product name and other values which together uniquely identify this registry node.

In the example I have defined a group “Leaves”, which contains several entries and which are all of the same type. Each entry holds the data for one options page. In this case, each options page may provide five different strings.

If a new version of the extension uses the same schema, then data, which have been entered by a user for the previous version, will be automatically applied for the new version. If this is not wanted then one need to provide a new schema. In our case we could just change the attribute oor:component-schema@ oor:name to a value, for example, ExtensionData2.

Now the question is, how one can access the controls on the options page in order to set the data or read from them. The following code example shows the whole service as Java implementation. Please have look at the loadData and saveData method. Please be aware that is is only an example and may need to be adapted to personal needs.

package com.sun.star.comp.extensionoptions;
 
import com.sun.star.lib.uno.helper.Factory;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XTypeProvider;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.Any;
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.Exception;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.awt.XContainerWindowEventHandler;
import com.sun.star.awt.XControl;
import com.sun.star.awt.XControlModel;
import com.sun.star.awt.XControlContainer;
import com.sun.star.container.XNameAccess;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.PropertyState;
import com.sun.star.beans.XPropertySet;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.beans.PropertyVetoException;
import com.sun.star.util.XChangesBatch;
 
/** A handler which supports multiple options pages which all
* have the same controls.
*/
public class OptionsEventHandler {
 
public static class _OptionsEventHandler extends WeakBase
implements XServiceInfo, XContainerWindowEventHandler {
 
static private final String __serviceName =
"com.sun.star.comp.extensionoptions.OptionsEventHandler";
 
private XComponentContext m_cmpCtx;
 
private XMultiComponentFactory m_xMCF;
 
private XNameAccess m_xAccessLeaves;
 
/**Names of supported options pages. 
*/
private String[] m_arWindowNames = {
"Writer1", "Writer2", "Writer3", "Calc1", "Calc2", "Calc3",
"Draw1", "Draw2", "Draw3", "Node1_1", "Node1_2", "Node1_3",
"Node2_1", "Node2_2", "Node2_3", "Node3_1", "Node3_2", "Node3_3"};
 
/**Names of the controls which are supported by this handler. All these
*controls must have a "Text" property.
*/
private String[] m_arStringControls = {
"String0", "String1", "String2", "String3", "String4"};
 
public _OptionsEventHandler(XComponentContext xCompContext) {
  m_cmpCtx = xCompContext;
  m_xMCF = m_cmpCtx.getServiceManager(); 
 
  //Create the com.sun.star.configuration.ConfigurationUpdateAccess
  //for the registry node which contains the data for our option
  //pages.
  XMultiServiceFactory xConfig;
  try {
    xConfig = (XMultiServiceFactory) UnoRuntime.queryInterface(
      XMultiServiceFactory.class,
      m_cmpCtx.getServiceManager().createInstanceWithContext(
        "com.sun.star.configuration.ConfigurationProvider", m_cmpCtx));
  } catch (com.sun.star.uno.Exception e) {
    e.printStackTrace();
    return;
  }
 
  //One argument for creating the ConfigurationUpdateAccess is the "nodepath".
  //Our nodepath point to the node of which the direct subnodes represent the
  //different options pages.
  Object[] args = new Object[1];
  args[0] = new PropertyValue(
    "nodepath", 0, "/org.openoffice.desktop.deployment.options.ExtensionData/Leaves",
    PropertyState.DIRECT_VALUE);
 
  //We get the com.sun.star.container.XNameAccess from the instance of
  //ConfigurationUpdateAccess and save it for later use.
  try {
    m_xAccessLeaves = (XNameAccess) UnoRuntime.queryInterface(
      XNameAccess.class, xConfig.createInstanceWithArguments(
        "com.sun.star.configuration.ConfigurationUpdateAccess", args));
 
  } catch (com.sun.star.uno.Exception e) {
    e.printStackTrace();
    return;
  }
}
 
/** This method returns an array of all supported service names.
* @return Array of supported service names.
*/
public String[] getSupportedServiceNames() {
  return getServiceNames();
}
 
/** This method is a simple helper function to used in the
* static component initialisation functions as well as in
* getSupportedServiceNames.
*/
public static String[] getServiceNames() {
  String[] sSupportedServiceNames = { __serviceName };
  return sSupportedServiceNames;
}
 
/** This method returns true, if the given service will be
* supported by the component.
* @param sServiceName Service name.
* @return True, if the given service name will be supported.
*/
public boolean supportsService( String sServiceName ) {
  return sServiceName.equals( __serviceName );
}
 
/** Return the class name of the component.
* @return Class name of the component.
*/
public String getImplementationName() {
  return _OptionsEventHandler.class.getName();
}
 
//XContainerWindowEventHandler
public boolean callHandlerMethod(com.sun.star.awt.XWindow aWindow,
  Object aEventObject, String sMethod)
  throws WrappedTargetException {
  if (sMethod.equals("external_event") ){
    try {
      return handleExternalEvent(aWindow, aEventObject);
    } catch (com.sun.star.uno.RuntimeException re) {
      throw re;
    } catch (com.sun.star.uno.Exception e) {
      throw new WrappedTargetException(sMethod, this, e);
    }
  }
 
  // return false when event was not handled 
  return false;
}
 
//XContainerWindowEventHandler
public String[] getSupportedMethodNames() {
  return new String[] {"external_event"};
}
 
private boolean handleExternalEvent(com.sun.star.awt.XWindow aWindow, Object aEventObject)
  throws com.sun.star.uno.Exception {
  try {
    String sMethod = AnyConverter.toString(aEventObject);
    if (sMethod.equals("ok")) {
      saveData(aWindow);
    } else if (sMethod.equals("back") || sMethod.equals("initialize")) {
      loadData(aWindow);
    }
    } catch (com.sun.star.lang.IllegalArgumentException e) {
      throw new com.sun.star.lang.IllegalArgumentException(
        "Method external_event requires a string in the event object argument.",
        this, (short) -1);
    }
  return true;
}
 
private void saveData(com.sun.star.awt.XWindow aWindow)
  throws com.sun.star.lang.IllegalArgumentException, com.sun.star.uno.Exception { 
 
  //Determine the name of the options page. This serves two purposes. First, if this
  //options page is supported by this handler and second we use the name two locate
  //the corresponding data in the registry.
  String sWindowName = getWindowName(aWindow);
  if (sWindowName == null)
    throw new com.sun.star.lang.IllegalArgumentException(
      "This window is not supported by this handler", this, (short) -1);
 
  //To access the separate controls of the window we need to obtain the
  //XControlContainer from the window implementation
  XControlContainer xContainer = (XControlContainer) UnoRuntime.queryInterface(
    XControlContainer.class, aWindow);
  if (xContainer == null)
    throw new com.sun.star.uno.Exception(
      "Could not get XControlContainer from window.", this);
 
  //This is an implementation which will be used for several options pages
  //which all have the same controls. m_arStringControls is an array which
  //contains the names. 
  for (int i = 0; i < m_arStringControls.length; i++) {
    //To obtain the data from the controls we need to get their model.
    //First get the respective control from the XControlContainer.
    XControl xControl = xContainer.getControl(m_arStringControls[i]);
 
    //This generic handler and the corresponding registry schema support
    //up to five text controls. However, if a options page does not use all
    //five controls then we will not complain here.
    if (xControl == null)
      continue;
 
    //From the control we get the model, which in turn supports the
    //XPropertySet interface, which we finally use to get the data from
    //the control. 
    XPropertySet xProp = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, xControl.getModel());
 
    if (xProp == null)
      throw new com.sun.star.uno.Exception(
        "Could not get XPropertySet from control.", this);
    //Get the "Text" property.
    Object aText = xProp.getPropertyValue("Text");
    String sValue = null;
 
    //The value is still contained in a com.sun.star.uno.Any - so convert it.
    try {
      sValue = AnyConverter.toString(aText);
    } catch (com.sun.star.lang.IllegalArgumentException e) {
      throw new com.sun.star.lang.IllegalArgumentException(
        "Wrong property type.", this, (short) -1);
    }
 
    //Now we have the actual string value of the control. What we need now is
    //the XPropertySet of the respective property in the registry, so that we
    //can store the value.
    //To access the registry we have previously created a service instance
    //of com.sun.star.configuration.ConfigurationUpdateAccess which supports
    //com.sun.star.container.XNameAccess. The XNameAccess is used to get the
    //particular registry node which represents this options page.
    //Fortunately the name of the window is the same as the registry node.
    XPropertySet xLeaf = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, m_xAccessLeaves.getByName(sWindowName));
    if (xLeaf == null)
      throw new com.sun.star.uno.Exception(
        "XPropertySet not supported.", this);
 
    //Finally we can set the value
    xLeaf.setPropertyValue(m_arStringControls[i], sValue);
  }
  //Committing the changes will cause or changes to be written to the registry.
  XChangesBatch xUpdateCommit = 
  (XChangesBatch) UnoRuntime.queryInterface(XChangesBatch.class, m_xAccessLeaves);
  xUpdateCommit.commitChanges();
}
 
private void loadData(com.sun.star.awt.XWindow aWindow)
  throws com.sun.star.uno.Exception {
 
  //Determine the name of the window. This serves two purposes. First, if this
  //window is supported by this handler and second we use the name two locate
  //the corresponding data in the registry.
  String sWindowName = getWindowName(aWindow);
  if (sWindowName == null)
    throw new com.sun.star.lang.IllegalArgumentException(
      "The window is not supported by this handler", this, (short) -1);
 
  //To acces the separate controls of the window we need to obtain the
  //XControlContainer from window implementation
  XControlContainer xContainer = (XControlContainer) UnoRuntime.queryInterface(
    XControlContainer.class, aWindow);
  if (xContainer == null)
    throw new com.sun.star.uno.Exception(
      "Could not get XControlContainer from window.", this);
 
  //This is an implementation which will be used for several options pages
  //which all have the same controls. m_arStringControls is an array which
  //contains the names.
  for (int i = 0; i < m_arStringControls.length; i++) {
    //load the values from the registry
    //To access the registry we have previously created a service instance
    //of com.sun.star.configuration.ConfigurationUpdateAccess which supports
    //com.sun.star.container.XNameAccess. We obtain now the section
    //of the registry which is assigned to this options page.
    XPropertySet xLeaf = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, m_xAccessLeaves.getByName(sWindowName));
    if (xLeaf == null)
      throw new com.sun.star.uno.Exception(
        "XPropertySet not supported.", this);
 
    //The properties in the registry have the same name as the respective
    //controls. We use the names now to obtain the property values.
    Object aValue = xLeaf.getPropertyValue(m_arStringControls[i]);
 
    //Now that we have the value we need to set it at the corresponding
    //control in the window. The XControlContainer, which we obtained earlier
    //is the means to get hold of all the controls.
    XControl xControl = xContainer.getControl(m_arStringControls[i]);
 
    //This generic handler and the corresponding registry schema support
    //up to five text controls. However, if a options page does not use all
    //five controls then we will not complain here.
    if (xControl == null)
      continue;
 
    //From the control we get the model, which in turn supports the
    //XPropertySet interface, which we finally use to set the data at the
    //control
    XPropertySet xProp = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, xControl.getModel());
 
    if (xProp == null)
      throw new com.sun.star.uno.Exception(
        "Could not get XPropertySet from control.", this);
 
    //This handler supports only text controls, which are named "Pattern Field"
    //in the dialog editor. We set the "Text" property.
    xProp.setPropertyValue("Text", aValue);
  }
}
 
//Checks if the name property of the window is one of the supported names and returns
//always a valid string or null
private String getWindowName(com.sun.star.awt.XWindow aWindow)
throws com.sun.star.uno.Exception {
 
  if (aWindow == null)
    new com.sun.star.lang.IllegalArgumentException(
      "Method external_event requires that a window is passed as argument",
      this, (short) -1);
 
  //We need to get the control model of the window. Therefore the first step is
  //to query for it.
  XControl xControlDlg = (XControl) UnoRuntime.queryInterface(
    XControl.class, aWindow);
 
  if (xControlDlg == null)
    throw new com.sun.star.uno.Exception(
      "Cannot obtain XControl from XWindow in method external_event.");
  //Now get model
  XControlModel xModelDlg = xControlDlg.getModel();
 
  if (xModelDlg == null)
    throw new com.sun.star.uno.Exception(
      "Cannot obtain XControlModel from XWindow in method external_event.", this);
  //The model itself does not provide any information except that its
  //implementation supports XPropertySet which is used to access the data.
  XPropertySet xPropDlg = (XPropertySet) UnoRuntime.queryInterface(
    XPropertySet.class, xModelDlg);
  if (xPropDlg == null)
    throw new com.sun.star.uno.Exception(
      "Cannot obtain XPropertySet from window in method external_event.", this);
 
  //Get the "Name" property of the window
  Object aWindowName = xPropDlg.getPropertyValue("Name");
 
  //Get the string from the returned com.sun.star.uno.Any
  String sName = null;
  try {
    sName = AnyConverter.toString(aWindowName);
  } catch (com.sun.star.lang.IllegalArgumentException e) {
    throw new com.sun.star.uno.Exception(
      "Name - property of window is not a string.", this);
  }
 
  //Eventually we can check if we this handler can "handle" this options page.
  //The class has a member m_arWindowNames which contains all names of windows
  //for which it is intended
  for (int i = 0; i < m_arWindowNames.length; i++) {
    if (m_arWindowNames[i].equals(sName)) {
      return sName;
    }
  }
  return null;
}
}
 
/**
* Gives a factory for creating the service.
* This method is called by the <code>JavaLoader</code>
* <p>
* @return returns a <code>XSingleComponentFactory</code> for creating
* the component
* @param sImplName the name of the implementation for which a
* service is desired
* @see com.sun.star.comp.loader.JavaLoader
*/
public static XSingleComponentFactory __getComponentFactory(String sImplName)
{
XSingleComponentFactory xFactory = null;
 
if ( sImplName.equals( _OptionsEventHandler.class.getName() ) )
xFactory = Factory.createComponentFactory(_OptionsEventHandler.class,
_OptionsEventHandler.getServiceNames());
 
return xFactory;
}
 
/**
* Writes the service information into the given registry key.
* This method is called by the <code>JavaLoader</code>
* <p>
* @return returns true if the operation succeeded
* @param regKey the registryKey
* @see com.sun.star.comp.loader.JavaLoader
*/
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
return Factory.writeRegistryServiceInfo(_OptionsEventHandler.class.getName(),
_OptionsEventHandler.getServiceNames(),
regKey);
}
 
/** This method is a member of the interface for initializing an object
* directly after its creation.
* @param object This array of arbitrary objects will be passed to the
* component after its creation.
* @throws Exception Every exception will not be handled, but will be
* passed to the caller.
*/
public void initialize( Object[] object )
throws com.sun.star.uno.Exception {
}
 
}
Content on this page is licensed under the Public Documentation License (PDL).
Personal tools
In other languages