In Part5 we saw how to add menu and toolbar entries in Calc. In this part we see how the command URLs emitted by these new toolbar buttons and menu entries can be handled according to our needs. On clicking the "Insert Date"/"Insert Time" button/menu, we insert current date/time to cell A1.
One way to make our component handle any commandURL, is to implement com.sun.star.frame.ProtocolHandler
service. ProtocolHandler
service supports the interface com.sun.star.frame.XDispatchProvider
mandatorily and optionally support the interface com.sun.star.lang.XInitialization
.
The interface XDispatchProvider
supports two methods :
XDispatch queryDispatch( [in] ::com::sun::star::util::URL URL,
[in] string TargetFrameName,
[in] long SearchFlags )
sequence< XDispatch > queryDispatches( [in] sequence< DispatchDescriptor > Requests )
Our component implementing ProtocolHandler
service will be asked for its agreement to execute a given URL by a call to interface methods queryDispatch()
. It has to parse and validate the incoming URL. If the URL is valid and the protocol handler is able to handle it, it should return a dispatch object, thus indicating that it accepts the request.
The returned object must implement the interface com.sun.star.frame.XDispatch
which has the following methods :
void dispatch(
[in] com::sun::star::util::URL URL,
[in] sequence<com::sun::star::beans::PropertyValue> Arguments);
void addStatusListener(
[in] XStatusListener Control,
[in] com::sun::star::util::URL URL);
void removeStatusListener(
[in] XStatusListener Control,
[in] com::sun::star::util::URL URL);
If our ProtocolHandler component supports XInitialization
interface, its method void initialize([in] sequence< any > aArguments)
will be passed with a com.sun.star.frame.Frame
object. This Frame
object implements com.sun.star.frame.XFrame
interface which gives us access to the controller from which we can access the document model where we can manipulate the document.
Following diagram shows how to get the controller and document model from XFrame interface.
Note that our ProtocolHandler Component will be instantiated when LO starts for every UI element whose URL we support.
Lets start with declaring our ProtocolHandler component implementation class Addon
in addon.hxx
#define IMPLEMENTATION_NAME "inco.niocs.test.protocolhandler"
class Addon : public cppu::WeakImplHelper3
<
com::sun::star::frame::XDispatchProvider,
com::sun::star::lang::XInitialization,
com::sun::star::lang::XServiceInfo
>
{
private:
::com::sun::star::uno::Reference< ::com::sun::star::uno::XComponentContext > mxContext;
::com::sun::star::uno::Reference< ::com::sun::star::frame::XFrame > mxFrame;
public:
Addon( const ::com::sun::star::uno::Reference< ::com::sun::star::uno::XComponentContext > &rxContext)
: mxContext( rxContext )
{
printf("DEBUG>>> Created Addon object : %p\n", this); fflush(stdout);
}
// XDispatchProvider
virtual ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch >
SAL_CALL queryDispatch( const ::com::sun::star::util::URL& aURL,
const ::rtl::OUString& sTargetFrameName, sal_Int32 nSearchFlags )
throw( ::com::sun::star::uno::RuntimeException );
virtual ::com::sun::star::uno::Sequence < ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch > >
SAL_CALL queryDispatches(
const ::com::sun::star::uno::Sequence < ::com::sun::star::frame::DispatchDescriptor >& seqDescriptor )
throw( ::com::sun::star::uno::RuntimeException );
// XInitialization
virtual void SAL_CALL initialize( const ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Any >& aArguments )
throw (::com::sun::star::uno::Exception, ::com::sun::star::uno::RuntimeException);
// XServiceInfo
virtual ::rtl::OUString SAL_CALL getImplementationName()
throw (::com::sun::star::uno::RuntimeException);
virtual sal_Bool SAL_CALL supportsService( const ::rtl::OUString& ServiceName )
throw (::com::sun::star::uno::RuntimeException);
virtual ::com::sun::star::uno::Sequence< ::rtl::OUString > SAL_CALL getSupportedServiceNames()
throw (::com::sun::star::uno::RuntimeException);
};
// Standard service operations functions needed for every component
::rtl::OUString Addon_getImplementationName()
throw ( ::com::sun::star::uno::RuntimeException );
sal_Bool SAL_CALL Addon_supportsService( const ::rtl::OUString& ServiceName )
throw ( ::com::sun::star::uno::RuntimeException );
::com::sun::star::uno::Sequence< ::rtl::OUString > SAL_CALL Addon_getSupportedServiceNames()
throw ( ::com::sun::star::uno::RuntimeException );
::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface >
SAL_CALL Addon_createInstance( const ::com::sun::star::uno::Reference< ::com::sun::star::uno::XComponentContext > & rContext)
throw ( ::com::sun::star::uno::Exception );
Next we define the XDispatch
implementing class DateTimeWriterDispatchImpl
in addon.hxx
.
class DateTimeWriterDispatchImpl : public cppu::WeakImplHelper1<com::sun::star::frame::XDispatch>
{
private:
::com::sun::star::uno::Reference< ::com::sun::star::frame::XFrame > mxFrame;
public:
DateTimeWriterDispatchImpl( const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XFrame > &rxFrame )
: mxFrame( rxFrame )
{
printf("DEBUG>>> Created DateTimeWriterDispatchImpl object : %p\n", this); fflush(stdout);
}
// XDispatch
virtual void SAL_CALL dispatch( const ::com::sun::star::util::URL& aURL,
const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue >& lArgs )
throw (::com::sun::star::uno::RuntimeException);
virtual void SAL_CALL addStatusListener( const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XStatusListener >& xControl,
const ::com::sun::star::util::URL& aURL ) throw (::com::sun::star::uno::RuntimeException);
virtual void SAL_CALL removeStatusListener( const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XStatusListener >& xControl,
const ::com::sun::star::util::URL& aURL ) throw (::com::sun::star::uno::RuntimeException);
};
We can now implement the Addon
class methods in addons.cxx
// This is the service name an Add-On has to implement
#define SERVICE_NAME "com.sun.star.frame.ProtocolHandler"
// ProtocolHandler implementation "Addon" class methods
void SAL_CALL Addon::initialize( const Sequence< Any >& aArguments ) throw ( Exception, RuntimeException)
{
Reference < XFrame > xFrame;
if ( aArguments.getLength() )
{
aArguments[0] >>= xFrame;
mxFrame = xFrame;
}
}
Reference< XDispatch > SAL_CALL Addon::queryDispatch( const URL& aURL, const ::rtl::OUString& sTargetFrameName, sal_Int32 nSearchFlags )
throw( RuntimeException )
{
Reference < XDispatch > xRet;
if ( aURL.Protocol.equalsAscii("inco.niocs.test.protocolhandler:") )
{
printf("DEBUG>>> Addon::queryDispatch() called. this = %p, command = %s\n", this,
OUStringToOString( aURL.Path, RTL_TEXTENCODING_ASCII_US ).getStr()); fflush(stdout);
if ( aURL.Path.equalsAscii( "InsertDate" ) )
xRet = new DateTimeWriterDispatchImpl( mxFrame );
else if ( aURL.Path.equalsAscii( "InsertTime" ) )
xRet = xRet = new DateTimeWriterDispatchImpl( mxFrame );
}
return xRet;
}
// This is a method which can query multiple URLs at once.
Sequence < Reference< XDispatch > > SAL_CALL Addon::queryDispatches( const Sequence < DispatchDescriptor >& seqDescripts )
throw( RuntimeException )
{
sal_Int32 nCount = seqDescripts.getLength();
Sequence < Reference < XDispatch > > lDispatcher( nCount );
for( sal_Int32 i=0; i<nCount; ++i )
lDispatcher[i] = queryDispatch( seqDescripts[i].FeatureURL, seqDescripts[i].FrameName, seqDescripts[i].SearchFlags );
return lDispatcher;
}
// Helper functions for the implementation of UNO component interfaces.
OUString Addon_getImplementationName()
throw (RuntimeException)
{
return OUString ( IMPLEMENTATION_NAME );
}
Sequence< ::rtl::OUString > SAL_CALL Addon_getSupportedServiceNames()
throw (RuntimeException)
{
Sequence < ::rtl::OUString > aRet(1);
::rtl::OUString* pArray = aRet.getArray();
pArray[0] = OUString ( SERVICE_NAME );
return aRet;
}
Reference< XInterface > SAL_CALL Addon_createInstance( const Reference< XComponentContext > & rContext)
throw( Exception )
{
return (cppu::OWeakObject*) new Addon( rContext );
}
// Implementation of the recommended/mandatory interfaces of a UNO component.
// XServiceInfo
::rtl::OUString SAL_CALL Addon::getImplementationName()
throw (RuntimeException)
{
return Addon_getImplementationName();
}
sal_Bool SAL_CALL Addon::supportsService( const ::rtl::OUString& rServiceName )
throw (RuntimeException)
{
return cppu::supportsService(this, rServiceName);
}
Sequence< ::rtl::OUString > SAL_CALL Addon::getSupportedServiceNames( )
throw (RuntimeException)
{
return Addon_getSupportedServiceNames();
}
Now we need to implement the XDispatch
implementing class DateTimeWriterDispatchImpl
in addon.cxx
with helper functions to write date/time to cell A1
// XDispatch implementer class "DateTimeWriterDispatchImpl" methods
void SAL_CALL DateTimeWriterDispatchImpl::dispatch( const URL& aURL, const Sequence < PropertyValue >& lArgs ) throw (RuntimeException)
{
if ( aURL.Protocol.equalsAscii("inco.niocs.test.protocolhandler:") )
{
printf("DEBUG>>> DateTimeWriterDispatchImpl::dispatch() called. this = %p, command = %s\n", this,
OUStringToOString( aURL.Path, RTL_TEXTENCODING_ASCII_US ).getStr()); fflush(stdout);
if ( aURL.Path.equalsAscii( "InsertDate" ) )
{
WriteCurrDate( mxFrame );
}
else if ( aURL.Path.equalsAscii( "InsertTime" ) )
{
WriteCurrTime( mxFrame );
}
}
}
void SAL_CALL DateTimeWriterDispatchImpl::addStatusListener( const Reference< XStatusListener >& xControl, const URL& aURL ) throw (RuntimeException)
{
}
void SAL_CALL DateTimeWriterDispatchImpl::removeStatusListener( const Reference< XStatusListener >& xControl, const URL& aURL ) throw (RuntimeException)
{
}
// Helper functions to write current date/time to sheet
// Local function to write a string to cell A1
void WriteStringToCell( Reference< XFrame > &rxFrame, OUString aStr )
{
if ( !rxFrame.is() )
return;
Reference< XController > xCtrl = rxFrame->getController();
if ( !xCtrl.is() )
return;
Reference< XModel > xModel = xCtrl->getModel();
if ( !xModel.is() )
return;
Reference< XSpreadsheetDocument > xSpreadsheetDocument( xModel, UNO_QUERY );
if ( !xSpreadsheetDocument.is() )
return;
Reference< XSpreadsheets > xSpreadsheets = xSpreadsheetDocument->getSheets();
if ( !xSpreadsheets->hasByName("Sheet1") )
return;
Any aSheet = xSpreadsheets->getByName("Sheet1");
Reference< XSpreadsheet > xSpreadsheet( aSheet, UNO_QUERY );
Reference< XCell > xCell = xSpreadsheet->getCellByPosition(0, 0);
xCell->setFormula(aStr);
printf("DEBUG>>> Wrote \"%s\" to Cell A1\n",
OUStringToOString( aStr, RTL_TEXTENCODING_ASCII_US ).getStr()); fflush(stdout);
}
void GetCurrDateTime( oslDateTime* pDateTime )
{
if ( !pDateTime )
return;
TimeValue aTimeval;
osl_getSystemTime( &aTimeval );
osl_getDateTimeFromTimeValue( &aTimeval, pDateTime );
}
// Local function to write Date to cell A1
void WriteCurrDate( Reference< XFrame > &rxFrame )
{
char buf[12];
oslDateTime aDateTime;
GetCurrDateTime( &aDateTime );
sprintf(buf, "%04d/%02d/%02d", aDateTime.Year, aDateTime.Month, aDateTime.Day);
WriteStringToCell( rxFrame, OUString::createFromAscii(buf) );
}
// Local function to write Time to cell A1
void WriteCurrTime( Reference< XFrame > &rxFrame )
{
char buf[10];
oslDateTime aDateTime;
GetCurrDateTime( &aDateTime );
sprintf(buf, "%02d%02d%02d", aDateTime.Hours, aDateTime.Minutes, aDateTime.Seconds);
WriteStringToCell( rxFrame, OUString::createFromAscii(buf) );
}
The final step is to write component operation function to register our component in component.cxx
. Since we have only one component to register, we use a different approach than in Part4.
/**
* This function is called to get service factories for an implementation.
*
* @param pImplName name of implementation
* @param pServiceManager a service manager, need for component creation
* @param pRegistryKey the registry key for this component, need for persistent data
* @return a component factory
*/
extern "C" SAL_DLLPUBLIC_EXPORT void * SAL_CALL component_getFactory(const sal_Char * pImplName, void * /*pServiceManager*/, void * pRegistryKey)
{
void * pRet = 0;
if (rtl_str_compare( pImplName, IMPLEMENTATION_NAME ) == 0)
{
Reference< XSingleComponentFactory > xFactory( createSingleComponentFactory(
Addon_createInstance,
OUString( IMPLEMENTATION_NAME ),
Addon_getSupportedServiceNames() ) );
if (xFactory.is())
{
xFactory->acquire();
pRet = xFactory.get();
}
}
return pRet;
}
extern "C" SAL_DLLPUBLIC_EXPORT void SAL_CALL
component_getImplementationEnvironment(
char const ** ppEnvTypeName, uno_Environment **)
{
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
Please remember to remove any previous extensions from Part5 using $LOROOT/instdir/program/unopkg remove
The whole project can be downloaded and built be doing :
$ git clone https://github.com/niocs/ProtocolHandlerExtension.git
$ cd ProtocolHandlerExtension
$ make
$ $LOROOT/instdir/program/unopkg add /home/$username/libreoffice5.3_sdk/LINUXexample.out/bin/ProtocolHandlerExtension.oxt
To remove the extension, do :
$ $LOROOT/instdir/program/unopkg remove ProtocolHandlerExtension.oxt
On opening Calc you should notice the top level menu called Add-On example
with menu entries Insert Date
and Insert Time
. Add some text to any cell and try clicking on the new menu items and see if the text gets bold/italicized. The two new toolbar icons and also should be visible. On clicking Insert Date
/Insert Time
, the current date/time(UTC) will be inserted to cell A1. You should also see the debug statements printed in the terminal as below when LO starts and you click the newly added UI elements.
## Just after Calc Loads.
DEBUG>>> Created Addon object : 0x2bfebd0
DEBUG>>> Addon::queryDispatch() called. this = 0x2bfebd0, command = InsertDate
DEBUG>>> Created DateTimeWriterDispatchImpl object : 0x2bfdeb0
DEBUG>>> Created Addon object : 0x2bff5d0
DEBUG>>> Addon::queryDispatch() called. this = 0x2bff5d0, command = InsertTime
DEBUG>>> Created DateTimeWriterDispatchImpl object : 0x2bff6e0
DEBUG>>> Created Addon object : 0x2ff5740
DEBUG>>> Addon::queryDispatch() called. this = 0x2ff5740, command = InsertDate
DEBUG>>> Created DateTimeWriterDispatchImpl object : 0x2ffbb50
DEBUG>>> Created Addon object : 0x2ff5860
DEBUG>>> Addon::queryDispatch() called. this = 0x2ff5860, command = InsertTime
DEBUG>>> Created DateTimeWriterDispatchImpl object : 0x2ff59a0
### After you click the UI elements :
DEBUG>>> DateTimeWriterDispatchImpl::dispatch() called. this = 0x2ffbb50, command = InsertDate
DEBUG>>> Wrote "2016/10/26" to Cell A1
DEBUG>>> DateTimeWriterDispatchImpl::dispatch() called. this = 0x2ff59a0, command = InsertTime
DEBUG>>> Wrote "053312" to Cell A1
DEBUG>>> DateTimeWriterDispatchImpl::dispatch() called. this = 0x2bfdeb0, command = InsertDate
DEBUG>>> Wrote "2016/10/26" to Cell A1
DEBUG>>> DateTimeWriterDispatchImpl::dispatch() called. this = 0x2bff6e0, command = InsertTime
DEBUG>>> Wrote "053316" to Cell A1
Please remember to remove any previous extension from Part5 using unopkg remove
.
Download the extension as :
$ git clone https://github.com/niocs/ProtocolHandlerExtension.git
$ cd ProtocolHandlerExtension
In ProtocolHandlerExtension
directory you will see a file called ProtocolHandlerExtension.oxt
. This is the prebuilt extension. Install this extension using
$ unopkg add /path/to/ProtocolHandlerExtension.oxt
To remove this extension do :
$ unopkg remove ProtocolHandlerExtension.oxt
On opening calc you should see the top level menu called Add-On example
with menu entries Insert Date
and Insert Time
. The two new toolbar icons and also should be visible. On clicking Insert Date
/Insert Time
, the current date/time(UTC) will be inserted to cell A1.