Framework/Tutorial/Context Menu Interception

From Apache OpenOffice Wiki
Jump to: navigation, search

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.

General abstract of the context menu interception concept

Context menu interception architecture

Context Menu Interception Architecture.png

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;
};
 
}; }; }; };

How to implement a context menu interceptor

Register and Remove an Interceptor

The com.sun.star.ui.XContextMenuInterception 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 com.sun.star.ui.XContextMenuInterceptor 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 com.sun.star.ui.ContextMenuExecuteEvent is a struct that holds all the important information for an interceptor.

Members of com.sun.star.ui.ContextMenuExecuteEvent
ExecutePosition com.sun.star.awt.Point. Contains the position the context menu will be executed.
SourceWindow com.sun.star.awt.XWindow. Contains the window where the context menu has been requested.
ActionTriggerContainer com.sun.star.container.XIndexContainer. The structure of the intercepted context menu. The member implements the com.sun.star.ui.ActionTriggerContainer service.
Selection com.sun.star.view.XSelectionSupplier. Provides the current selection inside the source window.

Querying the menu structure

The ActionTriggerContainer member is an indexed container of context menu entries, where each menu entry is a property set. It implements the com.sun.star.ui.ActionTriggerContainer service. The interface com.sun.star.container.XIndexContainer 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 com.sun.star.beans.XPropertySet 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 com.sun.star.lang.XServiceInfo 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 com.sun.star.ui.ActionTrigger
Text string. Contains the text of the label of the menu entry.
CommandURL string. Contains the command URL that defines which function will be executed if the menu entry is selected by the user.
HelpURL string. This optional property contains a help URL that points to the help text.
Image com.sun.star.awt.XBitmap. 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.
SubContainer com.sun.star.container.XIndexContainer. This property contains an optional sub menu.
Separator

Service name = "com.sun.star.ui.ActionTriggerSeparator"

Property of com.sun.star.ui.ActionTriggerSeparator
SeparatorType com.sun.star.ui.ActionTriggerSeparatorType. Specifies a certain type of a separator. Currently the following types are possible:
  • const int LINE = 0
  • const int SPACE = 1
  • const int LINEBREAK = 2
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 com.sun.star.ui.ContextMenuExecuteEvent structure. The com.sun.star.ui.ActionTriggerContainer service has an additional interface com.sun.star.lang.XMultiServiceFactory which supports the creation of - com.sun.star.ui.ActionTriggerContainer - com.sun.star.ActionTrigger - com.sun.star.ui.ActionTriggerSeparator

These objects can be used to extend a context menu adding them to a ActionTriggerContainer.

The com.sun.star.lang.XMultiServiceFactory 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 com.sun.star.ui.ActionTrigger !

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 com.sun.star.ui.ContextMenuInterceptorAction defines the possible return values. You can find the values in the following table.

Values of com.sun.star.ui.ContextMenuInterceptorAction
IGNORED Called object has ignored the call. The next registered com.sun.star.ui.XContextMenuInterceptor 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 com.sun.star.ui.XContextMenuInterceptor.
CONTINUE_MODIFIED The context menu was modified by the called object. The next registered com.sun.star.ui.XContextMenuInterceptor should be notified.
Documentation caution.png Using some of the return values will break up the interceptor chain and therefore changes from additional interceptors won't be included into the context menu. You can break user expectation if you use a wrong value!

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;
  }
}
Personal tools