Context menu is one that appears when we right-click on a sheet. In this part we are going to add an entry to context menu and assign action to it.
In order to register a new entry in LO context menu, we need access to Frame
object of the current window. For this we are going to make a AsyncJob
component that gets executed via a normal addon menu in the menubar. Since we already know how to implement AsyncJob
component, we here focus on how we use this component to register a new context menu entry.
A context menu entry can be added by implementing com::sun::star::ui::XContextMenuInterceptor
interface which has a single method :
::com::sun::star::ui::ContextMenuInterceptorAction SAL_CALL notifyContextMenuExecute(
const ::com::sun::star::ui::ContextMenuExecuteEvent& rEvent )
throw ( ::com::sun::star::uno::RuntimeException );
and then we register this implementation using XContextMenuInterception
object's registerContextMenuInterceptor()
method inside executeAsync()
of our AsyncJob
component implementation RegisterInterceptorJobImpl
. Let us now focus on implementing com::sun::star::ui::XContextMenuInterceptor
// interceptor.hxx
// XContextMenuInterceptor implementer class
class ContextMenuInterceptorImpl : public cppu::WeakImplHelper1< ::com::sun::star::ui::XContextMenuInterceptor >
{
public:
ContextMenuInterceptorImpl()
{
printf("DEBUG>>> Created ContextMenuInterceptorImpl object : %p\n", this); fflush(stdout);
}
virtual ::com::sun::star::ui::ContextMenuInterceptorAction SAL_CALL notifyContextMenuExecute(
const ::com::sun::star::ui::ContextMenuExecuteEvent& rEvent )
throw ( ::com::sun::star::uno::RuntimeException );
};
// interceptor.cxx
ContextMenuInterceptorAction SAL_CALL ContextMenuInterceptorImpl::notifyContextMenuExecute(
const ContextMenuExecuteEvent& rEvent )
throw ( RuntimeException )
{
printf("DEBUG>>> Inside notifyContextMenuExecute : this = %p\n", this); fflush(stdout);
try {
Reference< XIndexContainer > xContextMenu = rEvent.ActionTriggerContainer;
if ( !xContextMenu.is() )
{
logError("DEBUG>>> notifyContextMenuExecute : bad rEvent.ActionTriggerContainer\n");
return ContextMenuInterceptorAction_IGNORED;
}
Reference< XMultiServiceFactory > xMenuElementFactory( xContextMenu, UNO_QUERY );
if ( !xMenuElementFactory.is() )
{
logError("DEBUG>>> notifyContextMenuExecute : bad xMenuElementFactory\n");
return ContextMenuInterceptorAction_IGNORED;
}
Reference< XPropertySet > xSeparator( xMenuElementFactory->createInstance( "com.sun.star.ui.ActionTriggerSeparator" ), UNO_QUERY );
if ( !xSeparator.is() )
{
logError("DEBUG>>> notifyContextMenuExecute : cannot create xSeparator\n");
return ContextMenuInterceptorAction_IGNORED;
}
xSeparator->setPropertyValue( "SeparatorType", makeAny( ActionTriggerSeparatorType::LINE ) );
Reference< XPropertySet > xMenuEntry( xMenuElementFactory->createInstance( "com.sun.star.ui.ActionTrigger" ), UNO_QUERY );
if ( !xMenuEntry.is() )
{
logError("DEBUG>>> notifyContextMenuExecute : cannot create xMenuEntry\n");
return ContextMenuInterceptorAction_IGNORED;
}
xMenuEntry->setPropertyValue( "Text", makeAny(OUString("Increment cell(s)")));
xMenuEntry->setPropertyValue( "CommandURL", makeAny( OUString("vnd.sun.star.job:event=onIncrementClick") ) );
sal_Int32 nCount = xContextMenu->getCount();
sal_Int32 nIdx = nCount;
xContextMenu->insertByIndex( nIdx++, makeAny( xSeparator ) );
xContextMenu->insertByIndex( nIdx++, makeAny( xMenuEntry ) );
return ContextMenuInterceptorAction_CONTINUE_MODIFIED;
}
catch ( Exception& e )
{
fprintf(stderr, "DEBUG>>> notifyContextMenuExecute : caught UNO exception: %s\n",
OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US ).getStr());
fflush(stderr);
}
return ContextMenuInterceptorAction_IGNORED;
}
The method notifyContextMenuExecute()
gets a struct object typed ContextMenuExecuteEvent
. This struct contains the following fields :
There are two types of menu entries :
In the above code we create each of these menu entries using ContextMenuExecuteEvent::ActionTriggerContainer
's XMultiServiceFactory
method createInstance()
.
com.sun.star.ui.ActionTrigger
menu entry has the following properties :
ProtocolHandler
or AsyncJob
or Job
component to handle this URL. In this case we just reuse our AsyncJob
component RegisterInterceptorJobImpl
. The URL we use here is vnd.sun.star.job:event=onIncrementClick
com.sun.star.ui.ActionTriggerSeparator
menu entry has one optional property : SeparatorType
which can take any of the values :
Next after creating these new menu entries we insert them to the ActionTriggerContainer
member of ContextMenuExecuteEvent
object we received using the method insertByIndex()
.
Finally the method notifyContextMenuExecute()
needs to return one of the following possibilities ( of type com.sun.star.ui.ContextMenuInterceptorAction
) :
com.sun.star.ui.XContextMenuInterceptor
would then be notified.com.sun.star.ui.XContextMenuInterceptor
.We want our XContextMenuInterceptor
implementation ContextMenuInterceptorImpl
to be instantiated only once per LO lifetime. We could do this by making it a singleton, but in this case we make use of C++'s static
variable to make it a singleton manually. For this we write a private method getInterceptor
in the AsyncJob implementation class. This method accepts the current XFrame
object and a reference to an integer that indicates the number of times this method was called for the current XFrame
object. This method has a static variable to hold on to uno reference to XContextMenuInterceptor
we implemented and makes sure we instantiate it only once.
Reference< XContextMenuInterceptor >& RegisterInterceptorJobImpl::getInterceptor( const Reference<XFrame>& xFrame, sal_Int32& rCalls )
{
static Reference< XContextMenuInterceptor > xInterceptor;
static std::map<std::uintptr_t, sal_Int32> aFrame2Calls;
if ( !xInterceptor.is() )
xInterceptor = Reference< XContextMenuInterceptor >( (cppu::OWeakObject*) new ContextMenuInterceptorImpl(), UNO_QUERY );
std::uintptr_t nFramePtrVal = reinterpret_cast<std::uintptr_t>( xFrame.get() );
auto aItr = aFrame2Calls.find( nFramePtrVal );
if ( aItr != aFrame2Calls.end() )
rCalls = ++(aItr->second);
else
{
aFrame2Calls[ nFramePtrVal ] = 1;
rCalls = 1;
}
return xInterceptor;
}
Next we handle the events onEnableInterceptClick1
( event produced by the normal menu entry to enable the context menu ) and onIncrementClick
( event produced by the context menu ) in executeAsync()
of our AsyncJob.
if ( aEventName.equalsAscii("onEnableInterceptClick1") )
{
sal_Int32 nNumCalls = 0;
Reference< XContextMenuInterceptor > xInterceptor = getInterceptor( xFrame, nNumCalls );
if ( nNumCalls > 1 )
{
printf("DEBUG>>> Interceptor is already enabled\n");
fflush(stdout);
}
else
{
Reference< XController > xController = xFrame->getController();
if ( xController.is() )
{
Reference< XContextMenuInterception > xContextMenuInterception( xController, UNO_QUERY );
if ( xContextMenuInterception.is() )
{
xContextMenuInterception->registerContextMenuInterceptor( xInterceptor );
printf("DEBUG>>> Registered ContextMenuInterceptorImpl to current frame controller.\n"); fflush(stdout);
}
}
}
} else if ( aEventName.equalsAscii("onIncrementClick") ) {
printf("DEBUG>>> Got request from DISPATCH envType and event name onIncrementClick, going to call IncrementCellValue().\n"); fflush(stdout);
IncrementMarkedCellValues( xFrame );
}
IncrementMarkedCellValues()
is executed as an action to user selecting our context menu entry. It imcrements all numerical cells by 1 under current selection.
The whole project can be downloaded, built and installed by doing :
$ git clone https://github.com/niocs/CtxtMenuInterceptor.git
$ cd CtxtMenuInterceptor
$ make
$ $LOROOT/instdir/program/unopkg add /home/$username/libreoffice5.3_sdk/LINUXexample.out/bin/CtxtMenuInterceptor.oxt
To remove the extension, do :
$ $LOROOT/instdir/program/unopkg remove CtxtMenuInterceptor.oxt
Click on the menu Add-On example > Enable context menu
. Now you should see something like below in the terminal.
DEBUG>>> Created RegisterInterceptorJobImpl object : 0x7ff86e8382e8
DEBUG>>> Called executeAsync() : this = 0x7ff86e8382e8
DEBUG>>> xFrame = 0x7ff88134ec78
DEBUG>>> Created ContextMenuInterceptorImpl object : 0x7ff86e831c10
DEBUG>>> Registered ContextMenuInterceptorImpl to current frame controller.
If you click again on the same menu, you should see the following in terminal indicating that the object ContextMenuInterceptorImpl
is instantiated only once.
DEBUG>>> Created RegisterInterceptorJobImpl object : 0x7ff86e8383d8
DEBUG>>> Called executeAsync() : this = 0x7ff86e8383d8
DEBUG>>> xFrame = 0x7ff88134ec78
DEBUG>>> Interceptor is already enabled
Now populate few cells with numbers and place cursor on them or select them and right click. You should see the menu entry Increment cell(s)
and a menu-separator just above that. When you click it the cells you selected with numbers should increment by 1. The following would be displayed in terminal on doing that :
DEBUG>>> Inside notifyContextMenuExecute : this = 0x7ff86e831c10
DEBUG>>> Created RegisterInterceptorJobImpl object : 0x7ff880089570
DEBUG>>> Called executeAsync() : this = 0x7ff880089570
DEBUG>>> xFrame = 0x7ff88134ec78
DEBUG>>> Got request from DISPATCH envType and event name onIncrementClick, going to call IncrementCellValue().
If you clicked Increment cell(s)
context menu with non-numeric data in current/selected cell(s), then those cells will not be modified. The terminal would say something like :
DEBUG>>> Got request from DISPATCH envType and event name onIncrementClick, going to call IncrementCellValue().
DEBUG>>> IncrementCellRange : cell type is not numeric, skipping.
Download the extension as :
$ git clone https://github.com/niocs/CtxtMenuInterceptor.git
$ cd CtxtMenuInterceptor
In CtxtMenuInterceptor
directory you will see a file called CtxtMenuInterceptor.oxt
. This is the prebuilt extension. Install this extension using
$ unopkg add /path/to/CtxtMenuInterceptor.oxt
To remove this extension do :
$ unopkg remove CtxtMenuInterceptor.oxt
Open calc and click on the menu Add-On example > Enable context menu
. This should enable our context menu entry from now on. Now populate few cells with numbers and place cursor on them or select them and right click. You should see the menu entry Increment cell(s)
and a menu-separator just above that. When you click it, the cells you selected with numbers should increment by 1.