Difference between revisions of "Platform Command Framework"

From Eclipsepedia

Jump to: navigation, search
(Using an IActionDelegate to execute a command)
(Using an IActionDelegate to execute a command)
Line 150: Line 150:
 
When you start up eclipse you'll get warnings about your actions not having a class attribute.  It's not an error, and won't effect the action performance.
 
When you start up eclipse you'll get warnings about your actions not having a class attribute.  It's not an error, and won't effect the action performance.
  
 +
Now your action definition looks more like a keybinding definition.  You're specifying the command id and any parameters needed for that action.
  
  

Revision as of 08:30, 30 October 2006

Contents

Architecture Overview

http://dev.eclipse.org/viewcvs/index.cgi/~checkout~/platform-ui-home/R3_1/contributions-proposal/requestForComments_html_m41374bdb.png

Figure 1: High Level Architecture

Commands

Commands are managed by the org.eclipse.ui.commands extension point and the ICommandService.

An example of using the extension point to create a command:

<extension
      point="org.eclipse.ui.commands">
   <category
         description="Actions take at lunch time."
         id="z.ex.view.keybindings.category"
         name="Lunch">
   </category>
   <command
         categoryId="z.ex.view.keybindings.category"
         description="Go for the taco."
         id="z.ex.view.keybindings.eatTaco"
         name="Eat That Taco">
   </command>
</extension>

You can programmatically create commands as well. From within a view:

ICommandService cmdService = (ICommandService) getSite().getService(
    ICommandService.class);
Category lunch = cmdService
    .getCategory("z.ex.view.keybindings.category");
if (!lunch.isDefined()) {
  lunch.define("Lunch", "Actions take at lunch time.");
}
Command eatTaco = cmdService
    .getCommand("z.ex.view.keybindings.eatTaco");
if (!eatTaco.isDefined()) {
  eatTaco.define("Eat That Taco", "Go for the taco.", lunch);
}

Note, however, that a plugin that programmatically defines commands is responsible for cleaning them up if the plugin is ever unloaded.

Also, like IAction you can execute a command directly ... but to get the proper environment it's better to execute it through the IHandlerService. See #Handlers.

In 3.1, it's not possible to execute through the handler service, but you can provide almost the same execution environment.

Command eatTaco = cmdService
    .getCommand("z.ex.view.keybindings.eatTaco");
eatTaco.execute(new ExecutionEvent(Collections.EMPTY_MAP, null, handlerService.getCurrentState()));


Executing a command with parameters

When a Command specifies its parameters, it can also specify a parameter type and/or some valid values. For example, the showView command.

 <command
     name="%command.showView.name"
     description="%command.showView.description"
     categoryId="org.eclipse.ui.category.views"
     id="org.eclipse.ui.views.showView"
     defaultHandler="org.eclipse.ui.handlers.ShowViewHandler">
   <commandParameter
       id="org.eclipse.ui.views.showView.viewId"
       name="%command.showView.viewIdParameter"
       values="org.eclipse.ui.internal.registry.ViewParameterValues" />
 </command>

To execute this command, you need to create a ParameterizedCommand with a Parameterization (an instance of a parameter and its value).

		ICommandService commandService = ...;
		IHandlerService handlerService = ...;
		Command showView = commandService
				.getCommand("org.eclipse.ui.views.showView");
		IParameter viewIdParm = showView
				.getParameter("org.eclipse.ui.views.showView.viewId");

		// the viewId parameter provides a list of valid values ... if you
		// knew the id of the problem view, you could skip this step.
		// This method is supposed to be used in places like the keys
		// preference page, to allow the user to select values
		IParameterValues parmValues = viewIdParm.getValues();
		String viewId = null;
		Iterator i = parmValues.getParameterValues().values().iterator();
		while (i.hasNext()) {
			String id = (String) i.next();
			if (id.indexOf("ProblemView") != -1) {
				viewId = id;
				break;
			}
		}

		Parameterization parm = new Parameterization(viewIdParm, viewId);
		ParameterizedCommand parmCommand = new ParameterizedCommand(
				showView, new Parameterization[] { parm });

		handlerService.executeCommand(parmCommand, null);


This executes the showView command with the problem view id. This is done for us when declaratively specifying a keybinding.

 <key
     sequence="M2+M3+Q X"
     contextId="org.eclipse.ui.contexts.window"
     commandId="org.eclipse.ui.views.showView"
     schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
   <parameter 
       id="org.eclipse.ui.views.showView.viewId"
       value="org.eclipse.ui.views.ProblemView" />
 </key>

Using an IActionDelegate to execute a command

This is still under construction, but basically you can write a generic IActionDelegate that will execute commands for you.

For example, in the above section we saw the showView command takes one parameter, the view id. What if you wanted to call the command from an actionSet?

You can specify the action using the <class/> element instead of the class attribute ... you'll get a couple of warnings, ignore them.

        <action
              id="org.eclipse.ui.examples.actions.showOutlineView"
              label="Show View:Outline"
              menubarPath="org.eclipse.ui.examples.actions.showViewMenu/additions"
              style="push">
           <class class="org.eclipse.ui.examples.actions.actions.GenericCommandAction">
              <parameter name="commandId" 
                         value="org.eclipse.ui.views.showView"/>
              <parameter name="org.eclipse.ui.views.showView.viewId" 
                         value="org.eclipse.ui.views.ContentOutline"/>
           </class>
        </action>
        <action
              id="org.eclipse.ui.examples.actions.showBookmarkView"
              label="Show View:Bookmark"
              menubarPath="org.eclipse.ui.examples.actions.showViewMenu/additions"
              style="push">
           <class class="org.eclipse.ui.examples.actions.actions.GenericCommandAction">
              <parameter name="commandId" 
                         value="org.eclipse.ui.views.showView"/>
              <parameter name="org.eclipse.ui.views.showView.viewId" 
                         value="org.eclipse.ui.views.BookmarkView"/>
           </class>
        </action>

For commands without parameters, you can use the class attribute short from:

class="org.eclipse.ui.examples.actions.actions.GenericCommandAction:commandId"

When you start up eclipse you'll get warnings about your actions not having a class attribute. It's not an error, and won't effect the action performance.

Now your action definition looks more like a keybinding definition. You're specifying the command id and any parameters needed for that action.


Generic Command Action Delegate

We'll need a more robust implementation, but in 3.2 your action delegate needs to look something like the class below (maybe I can get an example plugin together, at the very least the GenericCommandAction should be able to be used in all 4 action extension points).

public class GenericCommandAction implements IWorkbenchWindowActionDelegate,
        IExecutableExtension {

    private static final String PARM_COMMAND_ID = "commandId";

    private String commandId = null;

    private Map parameterMap = null;

    private ParameterizedCommand parameterizedCommand = null;

    private IHandlerService handlerService;

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
     */
    public void dispose() {
        handlerService = null;
        parameterizedCommand = null;
        parameterMap = null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
     */
    public void init(IWorkbenchWindow window) {
        handlerService = (IHandlerService) window
                .getService(IHandlerService.class);
        if (parameterMap != null) {
            ICommandService commandService = (ICommandService) window
                    .getService(ICommandService.class);
            createCommand(commandService);
        }
    }

    private void createCommand(ICommandService commandService) {
        String id = (String) parameterMap.get(PARM_COMMAND_ID);
        if (id == null) {
            return;
        }
        if (parameterMap.size() == 1) {
            commandId = id;
            return;
        }
        try {
            Command cmd = commandService.getCommand(id);
            if (!cmd.isDefined()) {
                // command not defined? no problem ...
                return;
            }
            ArrayList parameters = new ArrayList();
            Iterator i = parameterMap.keySet().iterator();
            while (i.hasNext()) {
                String parmName = (String) i.next();
                if (PARM_COMMAND_ID.equals(parmName)) {
                    continue;
                }
                IParameter parm = cmd.getParameter(parmName);
                if (parm == null) {
                    // asking for a bogus parameter? No problem
                    return;
                }
                parameters.add(new Parameterization(parm, (String) parameterMap
                        .get(parmName)));
            }
            parameterizedCommand = new ParameterizedCommand(cmd,
                    (Parameterization[]) parameters
                            .toArray(new Parameterization[parameters.size()]));
        } catch (NotDefinedException e) {
            // command is bogus? No problem, we'll do nothing.
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
     */
    public void run(IAction action) {
        try {
            if (commandId != null) {
                handlerService.executeCommand(commandId, null);
            } else if (parameterizedCommand != null) {
                handlerService.executeCommand(parameterizedCommand, null);
            }
            // else there is no command for this delegate
        } catch (Exception e) {
            // exceptions reduced for brevity
            e.printStackTrace();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction,
     *      org.eclipse.jface.viewers.ISelection)
     */
    public void selectionChanged(IAction action, ISelection selection) {
        // we don't care, handlers get their selection from the
        // ExecutionEvent application context
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement,
     *      java.lang.String, java.lang.Object)
     */
    public void setInitializationData(IConfigurationElement config,
            String propertyName, Object data) throws CoreException {
        // save the data until our init(*) call, where we can get
        // the services.
        if (data instanceof String) {
            commandId = (String) data;
        } else if (data instanceof Map) {
            parameterMap = (Map) data;
        }
    }

}

Handlers

Handlers are managed by the org.eclipse.ui.handlers extension point and the IHandlerService. Many Handlers can register for a command. At any give time, either 0 or 1 handlers will be active for the command. A handler's active state and enabled state can be controlled declaratively.

<extension
      point="org.eclipse.ui.handlers">
   <handler
         class="z.ex.view.keybindings.handlers.TacoHandler"
         commandId="z.ex.view.keybindings.eatTaco">
      <activeWhen>
         <with variable="activeContexts">
            <iterate operator="or">
               <equals value="z.ex.view.keybindings.contexts.taco"/>
            </iterate>
         </with>
      </activeWhen>
   </handler>
</extension>

Here the handler is checking the activeContexts variable (See org.eclipse.ui.ISources) and if the "taco" context is active, the handler is active.

The handler itself, TacoHandler, must implement IHandler but would usually be derived from the abstract base class org.eclipse.core.commands.AbstractHandler.

You can create and activate a handler programmatically:

IHandlerService handlerService = (IHandlerService) getSite()
    .getService(IHandlerService.class);
IHandler handler = new AbstractHandler() {
  public Object execute(ExecutionEvent event)
          throws ExecutionException {
    System.out.println("Eat that Taco");
    return null;
  }
};
handlerService
    .activateHandler("z.ex.view.keybindings.eatTaco", handler);


Apparently, to run commands we should be calling the IHandlerService, not the Command object execute method itself.

handlerService.executeCommand("z.ex.view.keybindings.eatTaco", null);

KeyBindings

KeyBindings are managed by the org.eclipse.ui.bindings extension point and the IBindingService. Keybindings cannot be updated programmatically.

<extension
      point="org.eclipse.ui.bindings">
   <key
         commandId="z.ex.view.keybindings.eatTaco"
         contextId="z.ex.view.keybindings.contexts.taco"
         schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
         sequence="CTRL+3">
   </key>
</extension>

A key binding is active when the context is active.

Contexts

Contexts are managed by the org.eclipse.ui.contexts extension point and the IContextService.

Most contexts are created by the extension point, and activated programmatically when appropriate. But you can create contexts programmatically as well. The active contexts usually form a tree, although in the case of keybindings this tree is narrowed down to a branch.

<extension
      point="org.eclipse.ui.contexts">
   <context
         description="To allow the consumption of Tacos"
         id="z.ex.view.keybindings.contexts.taco"
         name="Mexican Food"
         parentId="org.eclipse.ui.contexts.window">
   </context>
</extension>

For a context that was attached to a view, it would normally be activated in the view's createPartControl(*) method.

IContextService contextService = (IContextService) getSite()
  .getService(IContextService.class);
IContextActivation contextActivation = contextService.activateContext("z.ex.view.keybindings.contexts.taco");

You can only de-activate a context that you are responsible for activating.

Programmatically, you can create contexts:

Context tacos = contextService
    .getContext("z.ex.view.keybindings.contexts.taco");
if (!tacos.isDefined()) {
  tacos.define("Mexican Food", "To allow the consumption of Tacos",
      "org.eclipse.ui.contexts.window");
}

Note, however, that a plugin that programmatically defines contexts is responsible for cleaning them up if the plugin is ever unloaded.

Tracing Option

There are a couple of reasons why keybindings and commands might not work.

  1. Keybindings are in a context that is not active
  2. There is a keybinding conflict
  3. No handler is currently active for the command
  4. There is a handler conflict

To help track down the problem, you can run with debug tracing options. For example:

org.eclipse.ui/debug=true
org.eclipse.ui/trace/keyBindings=true
org.eclipse.ui/trace/keyBindings.verbose=true
org.eclipse.ui/trace/sources=true
org.eclipse.ui/trace/handlers=true
org.eclipse.ui/trace/handlers.verbose=true
#org.eclipse.ui/trace/handlers.verbose.commandId=org.eclipse.ui.edit.copy
org.eclipse.ui/trace/handlers.verbose.commandId=org.eclipse.jdt.ui.navigate.open.type
org.eclipse.ui/trace/contexts=true
org.eclipse.ui/trace/contexts.verbose=true


I put these options in a debug.options file and run eclipse using:

bash$ eclipse -debug debug.options -data /opt/local/pw_workspace >debug.log 2>&1

This logs the debug output to the debug.log file. This works on windows as well:

C:\development> eclipse32\eclipse.exe -debug debug.options -data workspaces\pw_workspace >debug.log 2>&1

handlers.verbose.commandId allows you to track the information about a specific command that isn't working. org.eclipse.jdt.ui.navigate.open.type is the open type dialog (normally CTRL+SHIFT+T) and org.eclipse.ui.edit.copy (commented out) is COPY (normally CTRL+C)