Volatile Function

Java, C++, C#, Delphi, ??? - Using the UNO bridges

Volatile Function

Postby saleem145 » Mon Jan 21, 2013 10:47 pm

Hello,

How do I define a volatile function in a C++ addin??

Thanks,

Saleem
OpenOffice 3.4.0
Mac OS X 10.5.8
saleem145
 
Posts: 130
Joined: Mon Jul 02, 2012 4:47 pm

Re: Volatile Function

Postby saleem145 » Tue Jan 22, 2013 8:42 pm

Some info here --

http://wiki.openoffice.org/wiki/Documen ... et_Add-Ins

Ideally I need a full example....

Saleem
OpenOffice 3.4.0
Mac OS X 10.5.8
saleem145
 
Posts: 130
Joined: Mon Jul 02, 2012 4:47 pm

Re: Volatile Function

Postby Charlie Young » Sat Feb 02, 2013 2:06 am

After some hesitation I took this up, since I was curious myself about how this works.

First thing I tried was to just translate the Java example into c++, but after a few hours of frustration I backed off and tried it in Python, and after a considerable struggle I got that working.

Then I went back to the c++ again. I don't want to go over all the stumbling blocks I encountered (though I'll mention a few), but I now have that working, I think. This isn't really useful for much besides illustrating the basic techniques, but since we've got to start somewhere, here goes...

The idl

Code: Select all   Expand viewCollapse view
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XInitialization.idl>
#include <com/sun/star/lang/XServiceName.idl>
#include <com/sun/star/lang/XLocalizable.idl>
#include <com/sun/star/sheet/XVolatileResult.idl>

module com {module volresult
{
   interface XVolatile
{
    string IncrementCounter( [in] string aName );
    com::sun::star::sheet::XVolatileResult getCounter( [in] string aName );
};
service Volatile
{
interface XVolatile;
interface com::sun::star::sheet::XVolatileResult;
interface com::sun::star::lang::XInitialization;
interface com::sun::star::lang::XServiceName;
};};
};


My first mistake was forgetting that "volatile" is a c++ keyword, and I originally used "volatile" instead of "volresult" in the namespace. Using Volatile as the service name doesn't cause problems, of course, since c++ is case sensitive.

First we need to define a ResultListener. To do OpenOffice listeners in c++, it turns out that helpers are called for, so

Code: Select all   Expand viewCollapse view
#include <cppuhelper/implbase1.hxx>


then we do

Code: Select all   Expand viewCollapse view
typedef ::cppu::WeakImplHelper1< ::com::sun::star::sheet::XResultListener> ResultListenerHelper;


And our listener class then looks like

Code: Select all   Expand viewCollapse view

class MyResultListener: public ResultListenerHelper{
public:
   void SAL_CALL modified(const ResultEvent &) throw (::com::sun::star::uno::RuntimeException);
   void SAL_CALL disposing(const EventObject &) throw (::com::sun::star::uno::RuntimeException);
};

void SAL_CALL MyResultListener::modified(const ResultEvent & aEvent) throw (::com::sun::star::uno::RuntimeException)
{
   return;
}

void SAL_CALL MyResultListener::disposing(const EventObject & aEvent) throw (::com::sun::star::uno::RuntimeException)
{
   return;
}


Then for the result object, that is the ExampleAddInResult class from the Java example, we can do

Code: Select all   Expand viewCollapse view
typedef ::cppu::WeakImplHelper1< ::com::sun::star::sheet::XVolatileResult> VolatileResultHelper;


then

Code: Select all   Expand viewCollapse view

class ExampleAddInResult: public VolatileResultHelper {
private:
   OUString aName;
   long nValue;
   vector<Reference< XResultListener >> myListeners;
   com::sun::star::sheet::ResultEvent getResult();
public:
   ExampleAddInResult(OUString);
   ExampleAddInResult();
   void SAL_CALL addResultListener(const Reference<XResultListener> & aListener) throw (::com::sun::star::uno::RuntimeException);
    void SAL_CALL removeResultListener(const Reference<XResultListener> & aListener) throw (::com::sun::star::uno::RuntimeException);
      
   void incrementValue() {
      long i;
      ++nValue;
      com::sun::star::sheet::ResultEvent aEvent = getResult();
      for(i = 0;i < myListeners.size(); i++)
         myListeners[i]->modified(aEvent);
      
    }
   
};

void SAL_CALL ExampleAddInResult::addResultListener(const Reference<XResultListener> & aListener) throw (::com::sun::star::uno::RuntimeException) {
   myListeners.push_back(aListener);
   // immediately notify of initial value
   com::sun::star::sheet::ResultEvent aEvent = getResult();
   myListeners.back()->modified(aEvent);
}

void SAL_CALL ExampleAddInResult::removeResultListener(const Reference<XResultListener> & aListener) throw (::com::sun::star::uno::RuntimeException) {
   myListeners.pop_back();
}


Constructors:

Code: Select all   Expand viewCollapse view
ExampleAddInResult::ExampleAddInResult(OUString aNewName)
{
   aName = aNewName;
   myListeners.reserve(10);
   nValue = 0;
}

ExampleAddInResult::ExampleAddInResult()
{
   aName = OUString();
   nValue = 0;
}


The .reserve(10) isn't terribly important, but I will mention that handling the listeners was ultimately what gave me the most difficulty.

The getResult method is also key. In the Java example, they just do aEvent.Source = this (= self in Python), but c++ is fussier, and one need to explicitly get a Reference<XInterface>. I tried several thing, but eventually lucked onto something that worked.

Code: Select all   Expand viewCollapse view
com::sun::star::sheet::ResultEvent ExampleAddInResult::getResult()
{
   com::sun::star::sheet::ResultEvent aEvent;
   OUString ou;
   Reference< XInterface > x( static_cast< lang::XTypeProvider * >( this ) );
   aEvent.Value <<= aName + OUString( RTL_CONSTASCII_USTRINGPARAM(" ") ) + ou.valueOf((sal_Int32) nValue);
   aEvent.Source = x;
   return aEvent;
}


Moving on, in the Java example the result objects are stored as (key, value) pairs in a hash table. In c++ this is an unordered_map, so in the VolatileImpl class we want (OUStringHash is in class OUString, fortunately).

Code: Select all   Expand viewCollapse view
static unordered_map<OUString,ExampleAddInResult *,OUStringHash> aResults;


and since it is static, we need to also declare it outside the definition

Code: Select all   Expand viewCollapse view
unordered_map<OUString,ExampleAddInResult *,OUStringHash> VolatileImpl::aResults;


The getCounter function itself is then

Code: Select all   Expand viewCollapse view

Reference<XVolatileResult> SAL_CALL VolatileImpl::getCounter( OUString const & aName)
      throw (RuntimeException)
{
   ExampleAddInResult *aResult;
   Reference<XVolatileResult> vResult;
   MyResultListener *aListener = new MyResultListener;
   Reference<XResultListener> xListener = static_cast< XResultListener * > ( aListener );
   
   if(aResults.count(aName) == 0) {
      aResult = new ExampleAddInResult(aName);
      aResults[aName] = aResult;
   } else {
      aResult = aResults[aName];
   }
   vResult = static_cast< XVolatileResult * > (aResult);
   return vResult;
}



It seems addResultListener is called automatically, and it is a mistake I made to try and call it explicitly, but a MyResultListener must be declared within the function.

Now what happens when we enter in a cell ("x" is an arbitrary string)

Code: Select all   Expand viewCollapse view
=GETCOUNTER("x")


The cell just displays

Code: Select all   Expand viewCollapse view
x 0


based on the ResultEvent.Value from the getResult() method, but it will increment whenever the result objects IncrementValue method is called

Code: Select all   Expand viewCollapse view
   
   void incrementValue() {
      long i;
      ++nValue;
      com::sun::star::sheet::ResultEvent aEvent = getResult();
      for(i = 0;i < myListeners.size(); i++)
         myListeners[i]->modified(aEvent);
      
    }
   


So I'm making another function to do that

Code: Select all   Expand viewCollapse view

OUString SAL_CALL VolatileImpl::IncrementCounter( OUString const & aName)
      throw (RuntimeException)
{
   
   ExampleAddInResult *aResult;

   if(aResults.count(aName) == 0) {
      return aName + OUString(RTL_CONSTASCII_USTRINGPARAM(" is null."));
   } else {
      aResult = aResults[aName];
      aResult->incrementValue();
      return aName + OUString(RTL_CONSTASCII_USTRINGPARAM(" incremented."));
   }
}


So if we put in a cell

Code: Select all   Expand viewCollapse view
=INCREMENTCOUNTER("x")


the GETCOUNTER("x") cell will increment x 1, x 2, x 3, etc. each time the INCREMENTCOUNTER is recalculated. Note the the INCREMENTCOUNTER can be on a different sheet and even in a different document.

It also works if called from somewhere else. So if we put in Basic

Code: Select all   Expand viewCollapse view
Sub Main
   IncIt("x")
End Sub


Sub IncIt(aName As String)
   Dim svc As Object
   
   svc = createUnoService("com.sun.star.sheet.FunctionAccess")
   svc.callFunction("com.volresult.my_volatile_implementation.Volatile.IncrementCounter",Array(aName))
End Sub


the GETCOUNTER("x") cell will increment whenever Main is run.

This whole mechanism is intended to update data in real time, of course, and I'm just now starting to look into that part. I'll probably go back to Python for that at first, since I think Python has facilities for reading web data that are much easier to implement than any such thing in c++.
Apache OpenOffice 4.1.1
Windows XP
User avatar
Charlie Young
Volunteer
 
Posts: 1559
Joined: Fri May 14, 2010 1:07 am

Re: Volatile Function

Postby saleem145 » Mon Feb 04, 2013 4:12 pm

Can a volatile function return a 2 dimensional array of data??

Saleem
OpenOffice 3.4.0
Mac OS X 10.5.8
saleem145
 
Posts: 130
Joined: Mon Jul 02, 2012 4:47 pm

Re: Volatile Function

Postby karolus » Mon Feb 04, 2013 4:32 pm

Hallo
saleem145 wrote:Can a volatile function return a 2 dimensional array of data??

Saleem

Yes!
AOO4, Libreoffice - 5.1 … 5.3.2.2 on Linux Mint17
User avatar
karolus
Volunteer
 
Posts: 852
Joined: Sat Jul 02, 2011 9:47 am

Re: Volatile Function

Postby Charlie Young » Mon Feb 04, 2013 7:53 pm

Just to c0nfirm this.

Modify the getResult() function from above:

Code: Select all   Expand viewCollapse view
com::sun::star::sheet::ResultEvent ExampleAddInResult::getResult()
{
   com::sun::star::sheet::ResultEvent aEvent;
   Sequence<Sequence<Any>> ResultArray(1);
   ResultArray[0] = Sequence<Any>(2);
   ResultArray[0][0] <<= aName;
   ResultArray[0][1] <<= (sal_Int32) nValue;
   aEvent.Value <<= ResultArray;
   Reference< XInterface > x( static_cast< lang::XTypeProvider * >( this ) );
   aEvent.Source = x;
   return aEvent;
}


Then enter getCounter() as an array formula (Ctrl-Shift-Enter), and the object name and counter are returned in two adjacent cells on a row.

It is worth noting that this only requires modifying the c++, and no changes need to be made to the .idl or the CalcAddin.xcu.

I'll leave it as an exercise to return the entries in a column. :D

It should be obvious how to extend this to larger arrays, but one consideration might be: is it necessary that the array always have the same dimensions? I'm guessing yes, since my experience is that array formulas are frozen in size after initial entry.
Apache OpenOffice 4.1.1
Windows XP
User avatar
Charlie Young
Volunteer
 
Posts: 1559
Joined: Fri May 14, 2010 1:07 am

Re: Volatile Function

Postby Villeroy » Mon Feb 04, 2013 8:13 pm

Charlie Young wrote:I'll leave it as an exercise to return the entries in a column. :D

=TRANSPOSE(GETCOUNTER("x"))
Please, edit this topic's initial post and add "[Solved]" to the subject line if your problem has been solved.
Ubuntu 18.04, no OpenOffice, LibreOffice 6.4
User avatar
Villeroy
Volunteer
 
Posts: 28431
Joined: Mon Oct 08, 2007 1:35 am
Location: Germany

Re: Volatile Function

Postby Charlie Young » Mon Feb 04, 2013 9:36 pm

Villeroy wrote:=TRANSPOSE(GETCOUNTER("x"))


Got me :oops: , that works.

(Of course changing the source code is also rather trivial.)
Apache OpenOffice 4.1.1
Windows XP
User avatar
Charlie Young
Volunteer
 
Posts: 1559
Joined: Fri May 14, 2010 1:07 am


Return to External Programs

Who is online

Users browsing this forum: No registered users and 2 guests