Framework/Tutorial/Context Menu Interception
TDF LibreOffice Document Liberation Project Community Blogs Weblate Nextcloud Redmine Ask LibreOffice Donate
This tutorial will give you a detailed step-by-step insight into a very flexible way to add a context menu interceptor into OpenOffice.org 2.x/3.0. A Context menu interceptor must be implemented via UNO supporting the com.sun.star.ui.XContextMenuInterceptor interface. This tutorial will be split into small chapters which describe different aspects of context menu interception. Everybody is invited to participate. May be someone wants to translate the extension to a different language or wants to have more information about a specific topic. You can set a link to this page, if you think that this page adds valuable information.
The reader of this tutorial should know the following
* Programming with UNO * C++/Java knowledge (Using C++/Java with the SDK) * How to create an extension with the "OpenOffice.org SDK"
A context menu is displayed when an object is right clicked. Typically, a context menu has context dependent functions to manipulate the selected object, such as cut, copy and paste. Developers can intercept context menus before they are displayed to cancel the execution of a context menu, add, delete, or modify the menu by replacing context menu entries or complete sub menus. It is possible to provide new customized context menus.
Context menu interception is implemented by the observer pattern. This pattern defines a one-to-many dependency between objects, so that when an object changes state, all its dependents are notified. The implementation supports more than one interceptor which are chained in a queue. The root access point for intercepting context menus is a com.sun.star.frame.Controller object. The controller implements the interface com.sun.star.ui.XContextMenuInterception to support context menu interception. A context menu interceptor must support the com.sun.star.ui.XContextMenuInterceptor interface. It's used by the interception implementation to notify an interceptor whenever a context menu is going to be shown.
UNO interfaces, services, structs and constants
com::sun::star::ui::XContextMenuInterceptor
module com { module sun { module star { module ui {
//=============================================================================
/** This interface enables the object to be registered as an interceptor
to change context menus or prevent them from being executed.
*/
published interface XContextMenuInterceptor : ::com::sun::star::uno::XInterface
{
//-------------------------------------------------------------------------
/** notifies the interceptor about the request to execute a context menu.
The interceptor has to decide whether the menu should be executed with
or without being modified or may ignore the call.
@param aEvent
provides information about the current context menu and other
related data like mouse position, window and selection.
@return
action which determines how the interception implementation should
proceed.
*/
ContextMenuInterceptorAction notifyContextMenuExecute([in] ContextMenuExecuteEvent aEvent);
};
}; }; }; };
com::sun::star::ui::XContextMenuInterception
module com { module sun { module star { module ui {
//=============================================================================
/** This interface enables the object to get interceptors registered that
change context menu or prevent them from being executed.
*/
published interface XContextMenuInterception : ::com::sun::star::uno::XInterface
{
//-------------------------------------------------------------------------
/** registers am XContextMenuInterceptor, which will become the first
interceptor in the chain of registered interceptors.
@param Interceptor
an interceptor which should be added at the start of the
interceptor chain.
*/
void registerContextMenuInterceptor([in] XContextMenuInterceptor Interceptor);
//-------------------------------------------------------------------------
/** removes an XContextMenuInterceptor which was previously registered
using XContextMenuInterception::registerContextMenuInterceptor.
The order of removals is arbitrary. It's not necessary to remove the
last registered interceptor first.
@param Interceptor
an interceptor which should be removed from the interceptor
chain.
*/
void releaseContextMenuInterceptor([in] XContextMenuInterceptor Interceptor);
};
}; }; }; };
com::sun::star::ui::ContextMenuInterceptorAction
module com { module sun { module star { module ui {
//=============================================================================
/** This interface enables the object to get interceptors registered that
change context menu or prevent them from being executed.
*/
published enum ContextMenuInterceptorAction
{
/** the XContextMenuInterceptor has ignored the call. The next registered
interceptor should be notified.
*/
IGNORED,
/** the context menu must not be executed. The next registered interceptor
should not be notified.
*/
CANCELLED,
/** the menu has been modified and should be executed without notifying the
next registered interceptor.
*/
EXECUTE_MODIFIED,
/** the menu has been modified and the next registered interceptor should
be notified.
*/
CONTINUE_MODIFIED
};
}; }; }; };
com::sun::star::ui::ContextMenuExecuteEvent
module com { module sun { module star { module ui {
//=============================================================================
/** This interface enables the object to get interceptors registered that
change context menu or prevent them from being executed.
*/
published struct ContextMenuExecuteEvent
{
/** contains the window where the context menu has been requested,
*/
::com::sun::star::awt::XWindow SourceWindow;
/** contains the position where the context menu should be displayed.
*/
::com::sun::star::awt::Point ExecutePosition;
/** enables access to the menu content. The implementing object
has to support service com::sun::star::ui::ActionTriggerContainer
*/
::com::sun::star::container::XIndexContainer ActionTriggerContainer;
/** provides the current selection inside the source window.
*/
::com::sun::star::view::XSelectionSupplier Selection;
};
}; }; }; };
Register and Remove an Interceptor
The <idl>com.sun.star.ui.XContextMenuInterception</idl> interface enables the developer to register and remove an interceptor. When an interceptor is registered, it is notified whenever a context menu is about to be executed. Registering an interceptor adds it to the front of the interceptor chain, so that it is called first. The order of removals is arbitrary. It is not necessary to remove the interceptor that registered last.
Implementing the notification part
A context menu interceptor implements the <idl>com.sun.star.ui.XContextMenuInterceptor</idl> interface. This interface has one function that is called by the responsible controller whenever a context menu is about to be executed.
ContextMenuInterceptorAction notifyContextMenuExecute ( [in] ContextMenuExecuteEvent aEvent)
The <idl>com.sun.star.ui.ContextMenuExecuteEvent</idl> is a struct that holds all the important information for an interceptor.
Members of <idl>com.sun.star.ui.ContextMenuExecuteEvent</idl> | |
---|---|
<idlm>com.sun.star.ui.ContextMenuExecuteEvent:ExecutePosition</idlm> | <idl>com.sun.star.awt.Point</idl>. Contains the position the context menu will be executed. |
<idlm>com.sun.star.ui.ContextMenuExecuteEvent:SourceWindow</idlm> | <idl>com.sun.star.awt.XWindow</idl>. Contains the window where the context menu has been requested. |
<idlm>com.sun.star.ui.ContextMenuExecuteEvent:ActionTriggerContainer</idlm> | <idl>com.sun.star.container.XIndexContainer</idl>. The structure of the intercepted context menu. The member implements the <idl>com.sun.star.ui.ActionTriggerContainer</idl> service. |
<idlm>com.sun.star.ui.ContextMenuExecuteEvent:Selection</idlm> | <idl>com.sun.star.view.XSelectionSupplier</idl>. Provides the current selection inside the source window. |
The ActionTriggerContainer member is an indexed container of context menu entries, where each menu entry is a property set. It implements the <idl>com.sun.star.ui.ActionTriggerContainer</idl> service. The interface <idl>com.sun.star.container.XIndexContainer</idl> directly accesses the intercepted context menu structure through methods to access, insert, remove and replace menu entries.
All elements inside an ActionTriggerContainer
member support the <idl>com.sun.star.beans.XPropertySet</idl> interface to get and set property values. There are two different types of menu entries which support specific set of properties. The following tables describe all properties, their type and purpose:
It would be possible to retrieve the type of a certain entry via checking the supported properties. An easier and more flexible way to check it is just to use the supported service name of the entry. The following code provides two small wrapper methods which detect if an entry inside a ActionTriggerContainer
is a menu entry or a separator.
It is essential to determine the type of each menu entry be querying it for the interface <idl>com.sun.star.lang.XServiceInfo</idl> and calling
// A helper class to determine the menu element type
public class MenuElement
{
static public boolean IsMenuEntry( com.sun.star.beans.XPropertySet xMenuElement ) {
com.sun.star.lang.XServiceInfo xServiceInfo =
(com.sun.star.lang.XServiceInfo)UnoRuntime.queryInterface(
com.sun.star.lang.XServiceInfo.class, xMenuElement );
return xServiceInfo.supportsService( "com.sun.star.ui.ActionTrigger" );
}
static public boolean IsMenuSeparator( com.sun.star.beans.XPropertySet xMenuElement ) { com.sun.star.lang.XServiceInfo xServiceInfo =
(com.sun.star.lang.XServiceInfo)UnoRuntime.queryInterface(
com.sun.star.lang.XServiceInfo.class, xMenuElement );
return xServiceInfo.supportsService( "com.sun.star.ui.ActionTriggerSeparator" );
}
}
Simple Menu Entry
Service Name = "com.sun.star.ui.ActionTrigger"
Properties of <idl>com.sun.star.ui.ActionTrigger</idl> | |
---|---|
<idlm>com.sun.star.ui.ActionTrigger:Text</idlm> | string. Contains the text of the label of the menu entry. |
<idlm>com.sun.star.ui.ActionTrigger:CommandURL</idlm> | string. Contains the command URL that defines which function will be executed if the menu entry is selected by the user. |
<idlm>com.sun.star.ui.ActionTrigger:HelpURL</idlm> | string. This optional property contains a help URL that points to the help text. |
<idlm>com.sun.star.ui.ActionTrigger:Image</idlm> | <idl>com.sun.star.awt.XBitmap</idl>. This property contains an image that is shown left of the menu label. The use is optional so that no image is used if this member is not initialized. |
<idlm>com.sun.star.ui.ActionTrigger:SubContainer</idlm> | <idl>com.sun.star.container.XIndexContainer</idl>. This property contains an optional sub menu. |
Separator
Service name = "com.sun.star.ui.ActionTriggerSeparator"
Property of <idl>com.sun.star.ui.ActionTriggerSeparator</idl> | |
---|---|
<idlm>com.sun.star.ui.ActionTriggerSeparator:SeparatorType</idlm> | <idl>com.sun.star.ui.ActionTriggerSeparatorType</idl>. Specifies a certain type of a separator. Currently the following types are possible:
|
Sub Menu
Service name = "com.sun.star.ui.ActionTriggerContainer"
Only the top-level container is directly accessible. A sub menu is accessible via the SubContainer
property of a normal menu entry. This is necessary as sub menus ae normally have a label which is retrieved from the menu entry property set.
Manipulate a Menu
It is possible to accomplish certain tasks without implementing code in a context menu interceptor, such as preventing a context menu from being activated. Normally, a context menu is manipulate to provide additional functions to the user. Seldom to remove a function.
As previously discussed, the context menu structure is queried through the ActionTriggerContainer
member that is part of the <idl>com.sun.star.ui.ContextMenuExecuteEvent</idl> structure. The <idl>com.sun.star.ui.ActionTriggerContainer</idl> service has an additional interface <idl>com.sun.star.lang.XMultiServiceFactory</idl> which supports the creation of
- <idl>com.sun.star.ui.ActionTriggerContainer</idl>
- <idl>com.sun.star.ActionTrigger</idl>
- <idl>com.sun.star.ui.ActionTriggerSeparator</idl>
These objects can be used to extend a context menu adding them to a ActionTriggerContainer
.
The <idl>com.sun.star.lang.XMultiServiceFactory</idl> implementation of the ActionTriggerContainer
implementation supports the following strings:
String | Object |
---|---|
"com.sun.star.ui.ActionTrigger" | Creates a normal menu entry. |
"com.sun.star.ui.ActionTriggerContainer" | Creates an empty sub menu1 . |
"com.sun.star.ui.ActionTriggerSeparator" | Creates an unspecified separator2 . |
1 A sub menu cannot exist by itself. It has to be inserted into a <idl>com.sun.star.ui.ActionTrigger</idl> !
2 The separator has no special type. It is the responsibility of the concrete implementation to render an unspecified separator.
Conclude the Interception
Every interceptor that is called directs the framework implementation how it should continue after the notification returns. The enumeration <idl>com.sun.star.ui.ContextMenuInterceptorAction</idl> defines the possible return values. You can find the values in the following table.
Values of <idl>com.sun.star.ui.ContextMenuInterceptorAction</idl> | |
---|---|
IGNORED
|
Called object has ignored the call. The next registered <idl>com.sun.star.ui.XContextMenuInterceptor</idl> should be notified. |
CANCELLED
|
The context menu must not be executed. No remaining interceptor will be called. |
EXECUTE_MODIFIED
|
The context menu has been modified and should be executed without notifying the next registered <idl>com.sun.star.ui.XContextMenuInterceptor</idl>. |
CONTINUE_MODIFIED
|
The context menu was modified by the called object. The next registered <idl>com.sun.star.ui.XContextMenuInterceptor</idl> should be notified. |
Sample code
The following example shows a context menu interceptor that adds a a sub menu to a menu that has been intercepted at a controller, where this com.sun.star.ui.XContextMenuInterceptor has been registered. This sub menu is inserted into the context menu at the topmost position. It provides help functions to the user that are reachable through the menu Help.
import com.sun.star.ui.*;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.XIndexContainer;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.Exception;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.lang.IllegalArgumentException;
public class ContextMenuInterceptor implements XContextMenuInterceptor {
public ContextMenuInterceptorAction notifyContextMenuExecute(
com.sun.star.ui.ContextMenuExecuteEvent aEvent ) throws RuntimeException {
try {
// Retrieve context menu container and query for service factory to
// create sub menus, menu entries and separators
com.sun.star.container.XIndexContainer xContextMenu = aEvent.ActionTriggerContainer;
com.sun.star.lang.XMultiServiceFactory xMenuElementFactory =
(com.sun.star.lang.XMultiServiceFactory)UnoRuntime.queryInterface(
com.sun.star.lang.XMultiServiceFactory.class, xContextMenu );
if ( xMenuElementFactory != null ) {
// create root menu entry for sub menu and sub menu
com.sun.star.beans.XPropertySet xRootMenuEntry =
(XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance ( "com.sun.star.ui.ActionTrigger" ));
// create a line separator for our new help sub menu
com.sun.star.beans.XPropertySet xSeparator =
(com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance( "com.sun.star.ui.ActionTriggerSeparator" ) );
Short aSeparatorType = new Short( ActionTriggerSeparatorType.LINE );
xSeparator.setPropertyValue( "SeparatorType", (Object)aSeparatorType );
// query sub menu for index container to get access
com.sun.star.container.XIndexContainer xSubMenuContainer =
(com.sun.star.container.XIndexContainer)UnoRuntime.queryInterface(
com.sun.star.container.XIndexContainer.class,
xMenuElementFactory.createInstance(
"com.sun.star.ui.ActionTriggerContainer" ));
// intialize root menu entry "Help"
xRootMenuEntry.setPropertyValue( "Text", new String( "Help" ));
xRootMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5410" ));
xRootMenuEntry.setPropertyValue( "HelpURL", new String( "5410" ));
xRootMenuEntry.setPropertyValue( "SubContainer", (Object)xSubMenuContainer );
// create menu entries for the new sub menu
// intialize help/content menu entry
// entry "Content"
XPropertySet xMenuEntry = (XPropertySet)UnoRuntime.queryInterface(
XPropertySet.class, xMenuElementFactory.createInstance (
"com.sun.star.ui.ActionTrigger" ));
xMenuEntry.setPropertyValue( "Text", new String( "Content" ));
xMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5401" ));
xMenuEntry.setPropertyValue( "HelpURL", new String( "5401" ));
// insert menu entry to sub menu
xSubMenuContainer.insertByIndex ( 0, (Object)xMenuEntry );
// intialize help/help agent
// entry "Help Agent"
xMenuEntry = (com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance (
"com.sun.star.ui.ActionTrigger" ));
xMenuEntry.setPropertyValue( "Text", new String( "Help Agent" ));
xMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5962" ));
xMenuEntry.setPropertyValue( "HelpURL", new String( "5962" ));
// insert menu entry to sub menu
xSubMenuContainer.insertByIndex( 1, (Object)xMenuEntry );
// intialize help/tips
// entry "Tips"
xMenuEntry = (com.sun.star.beans.XPropertySet)UnoRuntime.queryInterface(
com.sun.star.beans.XPropertySet.class,
xMenuElementFactory.createInstance(
"com.sun.star.ui.ActionTrigger" ));
xMenuEntry.setPropertyValue( "Text", new String( "Tips" ));
xMenuEntry.setPropertyValue( "CommandURL", new String( "slot:5404" ));
xMenuEntry.setPropertyValue( "HelpURL", new String( "5404" ));
// insert menu entry to sub menu
xSubMenuContainer.insertByIndex ( 2, (Object)xMenuEntry );
// add separator into the given context menu
xContextMenu.insertByIndex ( 0, (Object)xSeparator );
// add new sub menu into the given context menu
xContextMenu.insertByIndex ( 0, (Object)xRootMenuEntry );
// The controller should execute the modified context menu and stop notifying other
// interceptors.
return com.sun.star.ui.ContextMenuInterceptorAction.EXECUTE_MODIFIED ;
}
}
catch ( com.sun.star.beans.UnknownPropertyException ex ) {
// do something useful
// we used a unknown property
}
catch ( com.sun.star.lang.IndexOutOfBoundsException ex ) {
// do something useful
// we used an invalid index for accessing a container
}
catch ( com.sun.star.uno.Exception ex ) {
// something strange has happend!
}
catch ( java.lang.Throwable ex ) {
// catch java exceptions - do something useful
}
return com.sun.star.ui.ContextMenuInterceptorAction.IGNORED;
}
}