Volatile Function

Java, C++, C#, Delphi... - Using the UNO bridges
Post Reply
saleem145
Posts: 130
Joined: Mon Jul 02, 2012 4:47 pm

Volatile Function

Post by saleem145 »

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

Post by saleem145 »

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
User avatar
Charlie Young
Volunteer
Posts: 1559
Joined: Fri May 14, 2010 1:07 am

Re: Volatile Function

Post by Charlie Young »

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

#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

#include <cppuhelper/implbase1.hxx>
then we do

Code: Select all

typedef ::cppu::WeakImplHelper1< ::com::sun::star::sheet::XResultListener> ResultListenerHelper;
And our listener class then looks like

Code: Select all


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

typedef ::cppu::WeakImplHelper1< ::com::sun::star::sheet::XVolatileResult> VolatileResultHelper;
then

Code: Select all


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

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

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

static unordered_map<OUString,ExampleAddInResult *,OUStringHash> aResults;


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

Code: Select all

unordered_map<OUString,ExampleAddInResult *,OUStringHash> VolatileImpl::aResults;
The getCounter function itself is then

Code: Select all


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

=GETCOUNTER("x")
The cell just displays

Code: Select all

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

	
	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


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

=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

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
saleem145
Posts: 130
Joined: Mon Jul 02, 2012 4:47 pm

Re: Volatile Function

Post by saleem145 »

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

Saleem
OpenOffice 3.4.0
Mac OS X 10.5.8
User avatar
karolus
Volunteer
Posts: 1159
Joined: Sat Jul 02, 2011 9:47 am

Re: Volatile Function

Post by karolus »

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

Saleem
Yes!
AOO4, Libreoffice 6.1 on Rasbian OS (on ARM)
Libreoffice 7.4 on Debian 12 (Bookworm) (on RaspberryPI4)
Libreoffice 7.6 flatpak on Debian 12 (Bookworm) (on RaspberryPI4)
User avatar
Charlie Young
Volunteer
Posts: 1559
Joined: Fri May 14, 2010 1:07 am

Re: Volatile Function

Post by Charlie Young »

Just to c0nfirm this.

Modify the getResult() function from above:

Code: Select all

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
Villeroy
Volunteer
Posts: 31279
Joined: Mon Oct 08, 2007 1:35 am
Location: Germany

Re: Volatile Function

Post by Villeroy »

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 with LibreOffice 6.0, latest OpenOffice and LibreOffice
User avatar
Charlie Young
Volunteer
Posts: 1559
Joined: Fri May 14, 2010 1:07 am

Re: Volatile Function

Post by Charlie Young »

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
Post Reply