Framework/Tutorial/Context Menu Interception

    From The Document Foundation Wiki

    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 <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.
    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 <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:
    • 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 <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.
    Warning.svg

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

    Apache Feather Logo.svg

    This work is licensed under the Apache License version 2.0