Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Platform UI Command Design

Revision as of 14:52, 30 August 2006 by Pwebster.ca.ibm.com (Talk | contribs) (org.eclipse.ui.viewActions)

Starting point for menu and toolbar placement of commands in 3.3. Please contribute comments and suggestions in the discussion area.


Command 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

Current Framework

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.

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

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)


Framework Enhancements for 3.3

Quick list of code issues to be addressed in 3.3.

Issues to Address

Menus and toolbars point to PluginActions, but the keybindings have an ActionDelegateHandlerProxy that is disconnected from the original action. See Bug 151612 -KeyBindings- keybinding not enabled even though actions appear enabled in menus

Menus and ToolBars

Menu and toolbar placement is managed by 4 extension points, and through programmatic contributions at a number of locations: IActionBars, IViewSite, IEditorSite, EditorActionBarContributor ... more to follow

I'm not sure of an appropriate way to wrap org.eclipse.ui.IActionDelegate. It is the base class and provides 2 methods to all of the I*ActionDelegates.

public void run(IAction action);
public void selectionChanged(IAction action, ISelection selection);

run(*) is the execution method, so that is pretty straight forward. The selectionChanged(*) method is called as the workbench selection changes, often times it updates the IAction enablement ... but moving forward there is no IAction enablement. However, an IHandler can be a selection listener and update its own enablement state directly.

The current action delegate proxy, ActionDelegateHandlerProxy, creates a bogus IAction. It allows the action delegates to continue working, but it is disconnected from any state.

Of course, there is also IActionDelegate2 :-)


org.eclipse.ui.actionSets

Action Sets are visible in the main menu and coolbar. Their visibility can be updated by the user using Customize Perspective. Here is a sample actionSet distributed with Eclipse SDK.

<extension
      point="org.eclipse.ui.actionSets">
   <actionSet
         id="z.ex.editor.actionSet"
         label="Sample Action Set"
         visible="true">
      <menu
            id="sampleMenu"
            label="Sample &amp;Menu">
         <separator
               name="sampleGroup">
         </separator>
      </menu>
      <action
            class="z.ex.editor.actions.SampleAction"
            icon="icons/sample.gif"
            id="z.ex.editor.actions.SampleAction"
            label="&amp;Sample Action"
            menubarPath="sampleMenu/sampleGroup"
            toolbarPath="sampleGroup"
            tooltip="Hello, Eclipse world">
      </action>
   </actionSet>
</extension>

The <actionSet/> element defines the group of elements that can be shown or hidden. The <menu/> elements create menus and groups. The <action/> elements define individual "actions" ... they contain rendering information (label, icon), menu placement (menubarPath, toolbarPath) and behaviour (the class attribute, in this case an IWorkbenchWindowActionDelegate).

To generate the same effect using the proposed changes, you would need:

<command id="z.ex.editor.commands.SampleAction"
    name="Sample Action"
    description="Sample Action command"/>
<handler commandId="z.ex.editor.commands.SampleAction"
    class="z.ex.editor.actions.SampleAction"/>
<!-- where SampleAction now implements IHandler -->
<extension
      point="org.eclipse.ui.commandImages">
   <image
         commandId="z.ex.editor.commands.SampleAction"
         icon="icons/sample.gif">
   </image>
</extension>

And here is a possible view of the menus extension point:

<extension
      point="org.eclipse.ui.menus">
   <menu
         id="z.ex.sampleMenu"
         label="Sample Menu">
      <location
            mnemonic="M">
      </location>
   </menu>
   <group
         id="z.ex.sampleGroup"
         separatorsVisible="false">
      <location>
         <bar
               path="z.ex.sampleMenu"
               type="menu">
         </bar>
      </location>
   </group>
   <item
         commandId="z.ex.editor.commands.SampleAction"
         id="z.ex.editor.items.SampleAction">
      <location
            mnemonic="S">
         <bar
               path="z.ex.sampleMenu/z.ex.sampleGroup"
               type="menu">
         </bar>
      </location>
   </item>
</extension>

I think ...

org.eclipse.ui.editorActions

Editor Actions appear in the main menu and coolbar as long as an editor of that type is the active editor. Using the org.eclipse.ui.actionSetPartAssociation extension with an org.eclipse.ui.actionSet works similarly, except all editor actions are IEditorActionDelegates instead of IWorkbenchWindowActionDelegates.

Here is an action example adapted as an editor action:

<extension
      point="org.eclipse.ui.editorActions">
   <editorContribution
         id="z.ex.editor.actions"
         targetID="z.ex.view.keybindings.editors.XMLEditor">
      <menu
            id="sampleMenu"
            label="Sample &amp;Menu">
         <separator
               name="sampleGroup">
         </separator>
      </menu>
      <action
            class="z.ex.view.keybindings.actions.SampleEditorAction"
            icon="icons/sample.gif"
            id="z.ex.view.keybindings.actions.SampleEditorAction"
            label="Sample &amp;Editor Action"
            menubarPath="sampleMenu/sampleGroup"
            toolbarPath="sampleGroup"
            tooltip="Hello, Eclipse world">
      </action>
   </editorContribution>
</extension>


The <editorContribution/> element ties the editor action to a specific editor type. Other than that, it is almost identical to org.eclipse.ui.actionSets.

Similarly, it can be adapted to commands and handlers.

<command id="z.ex.editor.commands.SampleEditorAction"
    name="Sample Editor Action"
    description="Sample Editor Action command"/>
<handler commandId="z.ex.editor.commands.SampleEditorAction"
    class="z.ex.view.keybindings.actions.SampleEditorAction"/>
<!-- where SampleEditorAction now extends AbstractHandler -->
<extension
      point="org.eclipse.ui.commandImages">
   <image
         commandId="z.ex.editor.commands.SampleEditorAction"
         icon="icons/sample.gif">
   </image>
</extension>

Assuming the actionSet had been contributed, you could place the editor action in the existing menu.

<extension
      point="org.eclipse.ui.menus">
   <item
         commandId="z.ex.editor.commands.SampleEditorAction"
         id="z.ex.editor.items.SampleEditorAction">
      <location
            mnemonic="E">
         <part
              id="z.ex.view.keybindings.editors.XMLEditor">
            <bar
                  path="z.ex.sampleMenu/z.ex.sampleGroup"
                  type="menu">
            </bar>
         </part>
      </location>
   </item>
</extension>



  • What happens in the actionSet is disabled?

org.eclipse.ui.viewActions

View actions are placed in the view menu or view toolbar, but the extension point looks almost identical to org.eclipse.ui.editorActions. The delegate for views is IViewActionDelegate.

<extension
      point="org.eclipse.ui.viewActions">
   <viewContribution
         id="z.ex.view.keybindings.viewAction"
         targetID="z.ex.view.keybindings.views.SampleView">
       <menu
            id="sampleMenu"
            label="Sample &amp;Menu">
         <separator
               name="sampleGroup">
         </separator>
      </menu>
      <action
            class="z.ex.view.keybindings.actions.SampleViewAction"
            icon="icons/sample.gif"
            id="z.ex.view.keybindings.actions.SampleViewAction"
            label="Sample &amp;View Action"
            menubarPath="sampleMenu/sampleGroup"
            toolbarPath="sampleGroup"
            tooltip="Hello, Eclipse world">
      </action>
   </viewContribution>
</extension>

Here, the Sample Menu will show up in the view menu, the dropdown from the top of the view's CTabFolder.

Similarly, it can be adapted to commands and handlers.

<command id="z.ex.editor.commands.SampleViewAction"
    name="Sample View Action"
    description="Sample View Action command"/>
<handler commandId="z.ex.editor.commands.SampleViewAction"
    class="z.ex.view.keybindings.actions.SampleViewAction"/>
<!-- where SampleViewAction now extends AbstractHandler -->
<extension
      point="org.eclipse.ui.commandImages">
   <image
         commandId="z.ex.editor.commands.SampleViewAction"
         icon="icons/sample.gif">
   </image>
</extension>

Placing the action (which is specifically a menu or button linked to a command) can be accomplished with the org.eclipse.ui.menus extension point and the <part/> element.

<extension
      point="org.eclipse.ui.menus">
   <menu
         id="z.ex.view.SampleViewMenu"
         label="Sample Menu">
      <location
            mnemonic="M">
         <part
               id="z.ex.view.keybindings.views.SampleView">
         </part>
      </location>
   </menu>
   <group
         id="z.ex.view.SampleViewGroup"
         separatorsVisible="false">
      <location>
         <bar
               path="z.ex.view.SampleViewMenu"
               type="menu">
         </bar>
      </location>
   </group>
   <item
         commandId="z.ex.editor.commands.SampleViewAction"
         id="z.ex.view.SampleViewAction">
      <location
            mnemonic="V">
         <bar
               path="z.ex.view.SampleViewMenu/z.ex.view.SampleViewGroup"
               type="menu">
         </bar>
      </location>
   </item>
</extension>


  • Is this really that complicated?

org.eclipse.ui.popupMenus

Popup menu contributions are actions contributed to the various popup menus in eclipse. They take 2 forms. A <viewerContribution/> contributes actions to a popup in a view or an editor. An <objectContribution/> contributes actions to any popup, as long as the selected object matches its enablement criteria.

Here is an example of each:

<extension
      point="org.eclipse.ui.popupMenus">
   <objectContribution
         id="z.ex.popup.objectContribution"
         objectClass="org.eclipse.ui.handlers.IHandlerActivation">
      <action
            class="z.ex.view.keybindings.actions.SampleContributionAction"
            icon="icons/sample.gif"
            id="z.ex.view.keybindings.actions.SampleContributionAction"
            label="Sample &Contribution Action"
            menubarPath="additions"
            tooltip="Hello, Eclipse world">
      </action>
   </objectContribution>
   <viewerContribution
         id="z.ex.popup.viewerContribution"
         targetID="z.ex.view.keybindings.editors.XMLEditor">
      <action
            class="z.ex.view.keybindings.actions.SampleViewerAction"
            icon="icons/sample.gif"
            id="z.ex.view.keybindings.actions.SampleViewerAction"
            label="Sample V&iewer Action"
            menubarPath="#Ruler"
            tooltip="Hello, Eclipse world">
      </action>
   </viewerContribution>
</extension>


Each of the actions can be turned into commands/handlers as was done for view actions or editor actions.

This would make use of the popup specifier. Visibility can be specified, or simply tied to handler enablement using <visibleWhen checkEnabled="true"/>.

<extension
      point="org.eclipse.ui.menus">
   <item
         commandId="z.ex.view.commands.SampleContributionAction"
         id="z.ex.objectContribution">
      <location
            mnemonic="C">
         <popup
               path="additions">
         </popup>
      </location>
      <visibleWhen>
          <with
               variable="selection">
            <adapt
                  type="org.eclipse.ui.handlers.IHandlerActivation">
            </adapt>
         </with>
      </visibleWhen>
   </item>
   <item
         commandId="z.ex.view.commands.SampleViewerAction"
         id="z.ex.viewerContribution">
      <location
            mnemonic="i">
         <part
               id="z.ex.view.keybindings.editors.XMLEditor">
            <popup
                  path="#Ruler">
            </popup>
         </part>
      </location>
   </item>
</extension>


While the <with/> expression is used to select a specific variable from the evaluation context, it is possible for a default variable to be supplied so that with is not necessary. In this case, the default variable would probably be "selection".

Programmatic Contributions and Delegates

Contributing menus through IActionBars, EditorActionBarContributor, IWorkbenchPartSite#registerContextMenu(*), etc


I*ActionDelegate

Each of the IActionDelegates has a slightly different initialization interface. With each execution the IHandler is provided an ExecutionEvent that contains the application context, which will allow the handler to retrieve the information of interest.

Creating an equivalent IHandler for IWorkbenchWindowActionDelegate that has access to the window is straightforward. ex:


public class SampleAction extends AbstractHandler {
	public Object execute(ExecutionEvent event) throws ExecutionException {
		IWorkbenchWindow window = null;
		ISelection selection = null;

		Object appContextObj = event.getApplicationContext();
		if (appContextObj instanceof IEvaluationContext) {
			IEvaluationContext appContext = (IEvaluationContext) appContextObj;
			window = (IWorkbenchWindow) appContext
					.getVariable(ISources.ACTIVE_WORKBENCH_WINDOW_NAME);
			selection = (ISelection) appContext
					.getVariable(ISources.ACTIVE_CONTEXT_NAME);
		}
		if (window != null) {
			MessageDialog.openInformation(window.getShell(), "Editor Plug-in",
					"Hello, Eclipse world");
		}
		if (selection instanceof IStructuredSelection) {
			if (((IStructuredSelection) selection).size() > 1) {
				setEnabled(false);
			} else {
				setEnabled(true);
			}
		}
		return null;
	}

	private boolean enabled = true;

	private void setEnabled(boolean b) {
		if (enabled != b) {
			enabled = b;
			HandlerEvent handlerEvent = new HandlerEvent(this, true, false);
			fireHandlerChanged(handlerEvent);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.commands.AbstractHandler#isEnabled()
	 */
	public boolean isEnabled() {
		return enabled;
	}
}

Also note that IHandlers are not handed an IAction, but the IHandler can return its own isEnabled() state directly.

At the moment, a wrapper for an existing I*ActionDelegate is the ActionDelegateHandlerProxy.

Similarly, an IEditorActionDelegate equivalent (same applies to IViewActionDelegate, except it would use the active part) can access the active editor:

	public Object execute(ExecutionEvent event) throws ExecutionException {
		IEditorPart activeEditor = null;
		ISelection selection = null;
		Object appContextObj = event.getApplicationContext();
		if (appContextObj instanceof IEvaluationContext) {
			IEvaluationContext appContext = (IEvaluationContext) appContextObj;
			activeEditor = (IEditorPart) appContext
					.getVariable(ISources.ACTIVE_EDITOR_NAME);
			selection = (ISelection) appContext
					.getVariable(ISources.ACTIVE_CONTEXT_NAME);
		}
		// ... execute the event.
		return null;
	}


IObjectActionDelegate

For the behaviour part of an IObjectActionDelegate, it can simply get the active part on execution similar to the IEditorActionDelegate with appContext.getVariable(ISources.ACTIVE_PART_NAME).

Handlers are the implementation of behaviour, and so there is no one to one equivalent of IObjectActionDelegate#setActivePart(IAction action, IWorkbenchPart targetPart). The menu item as displayed in the popup menu can declare visibleWhen in its XML to help control its visibility, but will not be notified just before it is about to show.

Historical Information

The Contribution proposal started a couple of releases ago with the original RFC Contribution RFC

There are discussions in a number of places:


Requirements

  1. Provide a single concept for contributing to the workbench. Right now, there are two distinct ontologies: actions and contribution items; and commands and handlers.
  2. Support the addition and removal of plug-ins.
  3. Separate model and behaviour from visual presentation. Adhere more closely to the Model-View-Controller pattern. Model and user interface separation.
  4. Extensibility. Every group of items in the user interface (e.g., menu, tool bar, etc.) should be extensible – both in structure and content.
  5. Universal keyboard shortcuts. A user should be able to add a keyboard shortcut to any item that appears in the user interface (e.g., menu item, tool item, menu, etc.).
  6. Separation of structure and content. The structure of the menus (e.g., groups) should be defined independently from the items.
  7. No implicit declarations of structure or content. Everything should be explicit.
  8. Fine-grained control over visibility.
  9. More intelligent updating of elements within the user interface. Support for lazy updating for elements that are not showing within the user interface. This lazy updating should be handled automatically – without the elements needing to understand whether they are showing.
  10. Improved control over menu definition and item ordering. This will affect the “Search” and “Run” menus.
  11. The selection should be capable of overriding the behaviour of a user action. For example, if a Java element is selected in the Resource Navigator, a rename should be a refactoring rename.
  12. Address the difficulty in determining the keyboard shortcuts to show for context menu items.
  13. Support dynamic entries in top-level menus. For example, the recently opened files in the “File” menu should be possible using only public API.
  14. There should be an easy way to define the default behaviour in response to a user action (i.e., default handler for a command).
  15. Provide localized control of the model, view and controller elements talked about in this proposal. This includes such concepts as automatic addition/removal as parts are become active/inactive, and automatic removal as parts are destroyed.
  16. Allow the same user interface element to be placed in multiple locations. Reduce duplication in the syntax, and try to reduce memory usage.
  17. Provide facilities for finding and triggering elements within the user interface. This is intended to provide better support for the welcome facilities, cheat sheets, macros and scripting.
  18. JFace must not lose functionality. Everything that can be accomplished with JFace must still be possible in JFace, even if the API changes radically. Similarly, everything that can be accomplished with the workbench must still be possible in the workbench.
  19. Contribute all of the workbench and IDE model, view and controller elements using the API from this proposal. Everything that the workbench and IDE can do should be possible for third-party plug-ins as well.
  20. Contributing arbitrary controls (e.g., combo boxes) to Eclipse, where appropriate.


Rational

The Eclipse Platform has always provided a mechanism for contributing items to the menus and tool bars in Eclipse. This mechanism has – up until now – been based on instances of IAction.

Actions suffered from a few key deficiencies. First of all, the interaction with the application model (e.g., the handling of the run method) was tightly coupled with its presentation elements (e.g., icon, label, etc.). Also, there was no easy way to provide user-configurable keyboard shortcuts. Actions were not initially designed with a way to identify two actions as sharing the same semantic behaviour. To further confuse matters, there were action delegates. Action delegates were not actions, but could handle action behaviour in some circumstances.

Actions were defined in XML using several extension points. This XML syntax had several problems. First of all, there were too many extension points, which made the syntax hard to learn and caused maintenance problems. Features added to one extension point, would have to be copied into other extension points. Ultimately, what ended up happening is that for any given feature, it was possible that only a subset of the extension points would actually support it (e.g., dynamic menus). Partly due to this and partly due to the tight coupling mentioned above, this lead to an overly verbose syntax containing duplicate XML elements. If an action was required in a view menu and in a context menu, then the XML would need to be copied and contributed to two different extension points. This also led to multiple instances of the action in memory.

Aside from these main points, there are handful of other significant problems we hope to address – either directly or indirectly. These include dynamic menus, ordering of contribution items, performance problems, and better macro and instrumentation support.

Back to the top