Jump to: navigation, search

Difference between revisions of "Platform UI Command Design"

(Menu Proposal 2 UseCases)
m
 
(43 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Starting point for menu and toolbar placement of commands in 3.3.  Please contribute comments and suggestions in the discussion area or on [https://bugs.eclipse.org/bugs/show_bug.cgi?id=154130 Bug 154130 -KeyBindings- Finish re-work of commands and key bindings].
+
{{Platform UI}}
 
+
Starting point for menu and toolbar placement of commands in 3.3.  Please contribute comments and suggestions in the discussion area or on [https://bugs.eclipse.org/bugs/show_bug.cgi?id=154130 Bug 154130 -KeyBindings- Finish re-work of commands and key bindings]. Here is a page with concrete example cases: [[Menu Item Placement Examples]]
  
  
Line 55: Line 55:
 
  .getVariable(ISources.ACTIVE_WORKBENCH_WINDOW_NAME);
 
  .getVariable(ISources.ACTIVE_WORKBENCH_WINDOW_NAME);
 
  selection = (ISelection) appContext
 
  selection = (ISelection) appContext
  .getVariable(ISources.ACTIVE_CONTEXT_NAME);
+
  .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
 
  }
 
  }
 
  if (window != null) {
 
  if (window != null) {
Line 61: Line 61:
 
  "Hello, Eclipse world");
 
  "Hello, Eclipse world");
 
  }
 
  }
  if (selection instanceof IStructuredSelection) {
+
  return null;
if (((IStructuredSelection) selection).size() > 1) {
+
}
setEnabled(false);
+
  } else {
+
}
setEnabled(true);
+
 
  }
+
 
 +
At the moment, a wrapper for an existing <b>I*ActionDelegate</b> 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_CURRENT_SELECTION_NAME);
 
  }
 
  }
 +
// ... execute the event.
 
  return null;
 
  return null;
 
  }
 
  }
   
+
 
 +
 
 +
Also note that IHandlers are not handed an IAction, but the IHandler can return its own <b>isEnabled()</b> state directly. For Handlers that want to programmatically report their enablement change, they must remember to fire an event.
 +
 
 +
public class SampleEnabledHandler extends AbstractHandler {
 
  private boolean enabled = true;
 
  private boolean enabled = true;
 
   
 
   
Line 89: Line 108:
 
  return enabled;
 
  return enabled;
 
  }
 
  }
  }
+
   
 
+
Also note that IHandlers are not handed an IAction, but the IHandler can return its own <b>isEnabled()</b> state directly.
+
 
+
At the moment, a wrapper for an existing <b>I*ActionDelegate</b> 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 {
 
  public Object execute(ExecutionEvent event) throws ExecutionException {
 
  IEditorPart activeEditor = null;
 
  IEditorPart activeEditor = null;
Line 106: Line 118:
 
  .getVariable(ISources.ACTIVE_EDITOR_NAME);
 
  .getVariable(ISources.ACTIVE_EDITOR_NAME);
 
  selection = (ISelection) appContext
 
  selection = (ISelection) appContext
  .getVariable(ISources.ACTIVE_CONTEXT_NAME);
+
  .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
 
  }
 
  }
 
  // ... execute the event.
 
  // ... execute the event.
 
  return null;
 
  return null;
 
  }
 
  }
 
+
}
 
+
  
 
=== IObjectActionDelegate ===
 
=== IObjectActionDelegate ===
Line 126: Line 137:
  
 
These are the editor specific IActionBars.  They add some functionality, but most specifically the ability to add two IEditorActionBarContributors.  The editor action bar contributor and the extension action bar contributor (which is an ExternalContributor provided by the EditorActionBuilder).
 
These are the editor specific IActionBars.  They add some functionality, but most specifically the ability to add two IEditorActionBarContributors.  The editor action bar contributor and the extension action bar contributor (which is an ExternalContributor provided by the EditorActionBuilder).
 +
 +
One of the advantages of EditorActionBars is the lifecycle of the actions that are added.  They're created when the first editor of that type is loaded and exist until the last editor of that type is closed.
  
 
=== PluginActionBuilder subclasses ===
 
=== PluginActionBuilder subclasses ===
Line 361: Line 374:
 
We need a relatively straight forward model to represent our menu and toolbar structures.  But JFace already provides decent coverage for that using MenuManager, ToolBarManager, and IContributionItems.  As part of providing our rendering, we would be changing from ActionContributionItems that are currently used to some kind of CommandContributionItem.
 
We need a relatively straight forward model to represent our menu and toolbar structures.  But JFace already provides decent coverage for that using MenuManager, ToolBarManager, and IContributionItems.  As part of providing our rendering, we would be changing from ActionContributionItems that are currently used to some kind of CommandContributionItem.
  
IActions would still be used (mostly), but really our IAction would be the focal point of the rendering data model.
+
IActions would still be used (mostly), but really our IAction would be the focal point of the rendering data model. For example, we'll provide a <code>CommandBridgeAction</code>.  Knowledge of the command will allow the <code>CommandBridgeAction</code> to provide label, description, icons (from the ICommandImageService).  Its <code>runWithEvent(*)</code> method will simply be <code>handlerService.executeCommand("commandId", event)</code>.
 +
 
 +
As an alternative option, we could skip IActions and just use the CommandContributionItem.  Why build an IAction as the intermediary when the CommandContributionItem can fetch things like the label, icon, etc for us.
 +
 
  
 
We would still have to deal with [[#Issue 104 - Dynamic menus need a good story|Issue 104]] and improve dynamic menu creation.
 
We would still have to deal with [[#Issue 104 - Dynamic menus need a good story|Issue 104]] and improve dynamic menu creation.
Line 481: Line 497:
 
#Overrides
 
#Overrides
 
#Visibility expression like <visibleWhen/> or the Expression used in IMenuService#contribute(*)
 
#Visibility expression like <visibleWhen/> or the Expression used in IMenuService#contribute(*)
#Programmatic visibility property*
+
#Programmatic visibility property <sup>1</sup>
  
 
If the check at a level returns false, the item won't be visible.  If it returns true the next level of visibility is evaluated.
 
If the check at a level returns false, the item won't be visible.  If it returns true the next level of visibility is evaluated.
  
<nowiki>*</nowiki> - I'm not sure if a programmatic level of visibility is warranted.  The counter example is enablement, which can be changed through Overrides, the <enabledWhen/> expression on the handler, or programmatically from the handler isEnabled() method.
+
:<sup>[1]</sup> I'm not sure if a programmatic level of visibility is warranted.  The counter example is enablement, which can be changed through Overrides, the <enabledWhen/> expression on the handler, or programmatically from the handler isEnabled() method.
  
==== Action Set 01 ====
+
==== Menu Placement Locations ====
  
Provide a basic action set.  Assume you have 2 commands defined, <code>com.example.registry.search.find</code> (Registry Find) and <code>com.example.registry.search.replace</code> (Registry Replace), and you want to add them to the main Search menu at <code>org.eclipse.search.menu/dialogGroup</code>.  Your commands already have their images defined in the <code>org.eclipse.ui.commandImages</code> extension point.
+
We'll support 7 <b>root</b> types to start with:
 +
#SBar.MENU
 +
#: The main menu.
 +
#SBar.TOOLBAR
 +
#: The main toolbar.
 +
#SBar.VIEW_MENU
 +
#: A view menu. The first path segment will be the view id.
 +
#SBar.VIEW_TOOLBAR
 +
#: A view toolbar.  The first path segment will be the view id.
 +
#SBar.CONTEXT_MENU
 +
#: A context menu.  The first path segment will be the context menu id, or <code>org.eclipse.ui.menus.context.any</code> to apply to all context menus
 +
#SBar.TRIM
 +
#: Contribute a piece of trimThis is already implemented
 +
#SBar.STATUS
 +
#: Contribute information to the status line manager ... I'm not sure about this one, is it no longer necessary since they can contribute trim?
  
Define the action set:
+
For describing menu locations as strings, we have a couple of options.
 
+
<context
+
      description="The collection of registry search actions"
+
      id="com.example.registry.search.actionSet"
+
      name="Registry Search Action Set"
+
      parentId="org.eclipse.ui.contexts.actionSet">
+
</context>
+
 
+
And here is a possible view of the menus extension point:
+
 
+
<extension
+
      point="org.eclipse.ui.menus">
+
    <item
+
          commandId="com.example.registry.search.find"
+
          mnemonic="d"
+
          id="com.example.registry.search.find.menu">
+
      <location>
+
          <menu id="org.eclipse.search.menu">
+
            <group id="dialogGroup"/>
+
          </menu>
+
      </location>
+
      <visibleWhen>
+
          <with variable="activeContexts">
+
            <iterator operator="or">
+
                <equals value="com.example.registry.search.actionSet"/>
+
            </iterator>
+
          </with>
+
      </visibleWhen>
+
    </item>
+
    <item
+
          commandId="com.example.registry.search.replace"
+
          mnemonic="p"
+
          id="com.example.registry.search.replace.menu">
+
      <location>
+
          <menu id="org.eclipse.search.menu">
+
            <group id="dialogGroup"/>
+
          </menu>
+
          <order after="com.example.registry.search.find.menu"/>
+
      </location>
+
      <visibleWhen>
+
          <with variable="activeContexts">
+
            <iterator operator="or">
+
                <equals value="com.example.registry.search.actionSet"/>
+
            </iterator>
+
          </with>
+
      </visibleWhen>
+
    </item>
+
</extension>
+
 
+
Hmmmm, so to programmatically do this, you would have to go through the IMenuService.
+
 
+
 
+
ICommandService commandServ = (ICommandService) getSite().getWindow().getService(ICommandService.class);
+
IMenuService menuServ = (IMenuService) getSite().getWindow().getService(IMenuService.class);
+
+
SMenu searchMenu = menuServ.getMenu("org.eclipse.search.menu");
+
SGroup dialogGroup = searchMenu.getGroup("dialogGroup");
+
+
Command findCmd = commandServ.getCommand("com.example.registry.search.find");
+
SLocation findLocation = new SLocation(dialogGroup);
+
SItem findItem = menuServ.getItem("com.example.registry.search.find.menu");
+
findItem.define(findCmd, findLocation, "d");
+
menuServ.contribute(findItem, new ActionSetExpression("com.example.registry.search.actionSet"));
+
+
Command replaceCmd = commandServ.getCommand("com.example.registry.search.replace");
+
SLocation replaceLocation = new SLocation(dialogGroup);
+
replaceLocation.setOrder(new SOrder(SOrder.AFTER, "com.example.registry.search.find.menu"));
+
SItem replaceItem = menuServ.getItem("com.example.registry.search.replace.menu");
+
replaceItem.define(replaceCmd, replaceLocation, "p");
+
menuServ.contribute(replaceItem, new ActionSetExpression("com.example.registry.search.actionSet"));
+
 
+
==== Editor Actions 01 ====
+
 
+
This should basically be the same as [[#Action_Set_01]] with a different visible when clause.
+
 
+
      <visibleWhen>
+
          <with variable="activeEditorId">
+
            <equals value="com.example.registry.RegistryEditor"/>
+
          </with>
+
      </visibleWhen>
+
 
+
So programmatically, that just translates into a different visible when expression:
+
 
+
menuServ.contribute(findItem, new ActiveEditorIdExpression("com.example.registry.RegistryEditor"));
+
 
+
The menu items must be uniquely identified by their id.  Since they only have one location, they can only have one activation in the service at a time.
+
 
+
==== View Actions 01 ====
+
 
+
View actions have to specify their menus as they are self contained.
+
 
+
<command id="z.ex.editor.commands.SampleViewAction"
+
    name="Sample View Action"
+
    description="Sample View Action command"/>
+
 
+
<extension
+
      point="org.eclipse.ui.commandImages">
+
    <image commandId="z.ex.editor.commands.SampleViewAction"
+
          icon="icons/sample.gif"/>
+
</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.
+
 
+
<extension
+
      point="org.eclipse.ui.menus">
+
    <menu
+
          id="z.ex.view.SampleViewMenu"
+
          mnemonic="M"
+
          label="Sample Menu">
+
      <location partId="z.ex.view.keybindings.views.SampleView"/>
+
    </menu>
+
    <group
+
          groupId="z.ex.view.SampleViewGroup"
+
          separatorsVisible="false">
+
      <location>
+
          <menu id="z.ex.view.SampleViewMenu"/>
+
      </location>
+
    </group>
+
    <item
+
          commandId="z.ex.editor.commands.SampleViewAction"
+
          mnemonic="V"
+
          id="z.ex.view.SampleViewAction">
+
      <location>
+
          <menu id="z.ex.view.SampleViewMenu">
+
            <group id="z.ex.view.SampleViewGroup"/>
+
          </menu>
+
      </location>
+
    </item>
+
</extension>
+
 
+
 
+
 
+
Hmmmm, so to programmatically do this, you would have to go through the IMenuService.
+
 
+
 
+
ICommandService commandServ = (ICommandService) getSite().getWindow().getService(ICommandService.class);
+
IMenuService menuServ = (IMenuService) getSite().getWindow().getService(IMenuService.class);
+
+
SMenu viewMenu = menuServ.getMenu("z.ex.view.SampleViewMenu");
+
viewMenu.define("Sample Menu", new SLocation(SLocation.PART_ID,
+
        "z.ex.view.keybindings.views.SampleView"), "M");
+
SGroup viewGroup = viewMenu.getGroup("z.ex.view.SampleViewGroup");
+
viewGroup.define(false);
+
+
Command viewCmd = commandServ.getCommand("z.ex.editor.commands.SampleViewAction");
+
SLocation viewLocation = new SLocation(viewGroup);
+
SItem viewItem = menuServ.getItem("z.ex.view.SampleViewAction.menu");
+
viewItem.define(viewCmd, viewLocation, "V");
+
menuServ.contribute(viewItem);
+
 
+
 
+
==== Popup Actions 01 ====
+
 
+
Popups can be targetted at any registered context menu, or at all of them
+
 
+
<command id="z.ex.view.commands.SampleContributionAction"
+
    name="Sample Context Action"
+
    description="Sample Context Action command"/>
+
 
+
<extension
+
      point="org.eclipse.ui.commandImages">
+
    <image commandId="z.ex.view.commands.SampleContributionAction"
+
          icon="icons/sample.gif"/>
+
</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.
+
 
+
<extension
+
      point="org.eclipse.ui.menus">
+
    <item
+
          commandId="z.ex.view.commands.SampleContributionAction"
+
          mnemonic="C"
+
          id="z.ex.objectContribution.menu">
+
      <location>
+
          <menu id="#EditorContext">
+
            <group id="z.ex.editor.contextGroup"/>
+
          </menu>
+
      </location>
+
      <visibleWhen>
+
          <with variable="selection">
+
            <adapt type="org.eclipse.ui.handlers.IHandlerActivation"/>
+
        </with>
+
      </visibleWhen>
+
    </item>
+
</extension>
+
 
+
 
+
It's probably that the default variable for core expression evaluations would be <b>selection</b>, so you wouldn't need the <with/> clause.  There would probably also be a short-hand to tie the visibility to an active handler.  Maybe <visibleWhen handler="true"/>
+
 
+
Hmmmm, so to programmatically do this, you would have to go through the IMenuService.
+
 
+
 
+
SMenu editorMenu = menuServ.getMenu("#EditorContext");
+
SGroup contextGroup = editorMenu.getGroup("z.ex.editor.contextGroup");
+
+
Command contextCmd = commandServ.getCommand("z.ex.view.commands.SampleContributionAction");
+
SLocation contextLocation = new SLocation(contextGroup);
+
SItem contextItem = menuServ.getItem("z.ex.objectContribution.menu");
+
contextItem.define(contextCmd, contextLocation, "C");
+
menuServ.contribute(contextItem, new SelectionAdaptExpression("org.eclipse.ui.handlers.IHandlerActivation"));
+
 
+
 
+
==== Popup Actions 02 ====
+
 
+
There will be a reserved popup ID, "org.eclipse.ui.menu.any" that will allow contributions to any popup menu.
+
 
+
SMenu anyPopup = menuServ.getMenu("org.eclipse.ui.menu.any");
+
SGroup additions = editorMenu.getGroup("additions");
+
+
Command contextCmd = commandServ.getCommand("z.ex.view.commands.SampleContributionAction");
+
SLocation contextLocation = new SLocation(additions);
+
SItem contextItem = menuServ.getItem("z.ex.objectContribution.menu");
+
contextItem.define(contextCmd, contextLocation, "C");
+
menuServ.contribute(contextItem, new SelectionAdaptExpression("org.eclipse.core.resources.IFile"));
+
  
The contribution should look exactly the same as a normal contribution.
+
<b>Option 1: URI</b>
  
 +
All of our paths can be generalized to a URI that looks like <code>type://id/path</code>.
  
==== Override Actions 01 ====
+
*The main File menu extention group marker: <code>menu://org.eclipse.ui.menu.main/file/file.ext</code>
 +
*The save toolbar: <code>toolbar://org.eclipse.ui.toolbar.main/save.group</code>
 +
*The resource navigator menu additions group marker: <code>menu://org.eclipse.ui.views.ResourceNavigator/additions</code>
 +
*The resource navigator toolbar: <code>toolbar://org.eclipse.ui.views.ResourceNavigator</code>
 +
*The text editor context menu additions group marker: <code>popup://#EditorContext/additions</code>
 +
*An object contribution additions group marker: <code>popup://org.eclipse.ui.menu.any/additions</code>
  
It should be possible to override the visibility of menu contributions.
+
<b>Option 2: A rooted Path</b>
  
IMenuService menuService = (IMenuService) PlatformUI.getWorkbench().getService(IMenuService.class);
+
Just use paths of the form <code>/type/id/path</code>They'll be IPath elements within eclipse.
  SItem findItem = menuServ.getItem("com.example.registry.search.find.menu");
+
menuService.addOverride(findItem, new OverrideAdapter() {
+
  public Boolean isVisible() {
+
    return Boolean.FALSE;
+
  }
+
});
+
  
The idea is to provide this ability at the product level. For example, an RCP app should be able to hide any menu items that it doesn't want but picked up through the inclusion of a plugin.
+
*The main File menu extention group marker: <code>/menu/org.eclipse.ui.menu.main/file/file.ext</code>
 +
*The save toolbar: <code>/toolbar/org.eclipse.ui.toolbar.main/save.group</code>
 +
*The resource navigator menu additions group marker: <code>/menu/org.eclipse.ui.views.ResourceNavigator/additions</code>
 +
*The resource navigator toolbar: <code>/toolbar/org.eclipse.ui.views.ResourceNavigator</code>
 +
*The text editor context menu additions group marker: <code>/popup/#EditorContext/additions</code>
 +
*An object contribution additions group marker: <code>/popup/org.eclipse.ui.menu.any/additions
  
That implies that it might not be part of the general IMenuService interface.  Or (taking a page from the IExtensionRegistry) it might use a token that's available from the WorkbenchWindowAdvisor so that products can use the interface, or even expose the ability to their users.
 
  
If it returns <code>null</code> the next level of visibility is evaluated.  The <code>null</code> case is to keep it consistent with other overrides.
+
The usecases are being moved to [[Menu Item Placement Examples]]
  
 
== Menu Proposal 3 ==
 
== Menu Proposal 3 ==
Line 784: Line 602:
  
 
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.
 
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.
 +
[[Category:Platform UI]]

Latest revision as of 12:54, 13 September 2007

Starting point for menu and toolbar placement of commands in 3.3. Please contribute comments and suggestions in the discussion area or on Bug 154130 -KeyBindings- Finish re-work of commands and key bindings. Here is a page with concrete example cases: Menu Item Placement Examples


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

See Platform_Command_Framework

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

Managing menus through the suggested org.eclipse.ui.menus extension maps one menu item to one org.eclipse.core.commands.ParameterizedCommand. This contains both the command and appropriate parameters needed to execute it.


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_CURRENT_SELECTION_NAME);
		}
		if (window != null) {
			MessageDialog.openInformation(window.getShell(), "Editor Plug-in",
					"Hello, Eclipse world");
		}
		return null;
	}

}


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_CURRENT_SELECTION_NAME);
		}
		// ... execute the event.
		return null;
	}


Also note that IHandlers are not handed an IAction, but the IHandler can return its own isEnabled() state directly. For Handlers that want to programmatically report their enablement change, they must remember to fire an event.

public class SampleEnabledHandler extends AbstractHandler {
	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;
	}

	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_CURRENT_SELECTION_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.

EditorActionBars

These are the editor specific IActionBars. They add some functionality, but most specifically the ability to add two IEditorActionBarContributors. The editor action bar contributor and the extension action bar contributor (which is an ExternalContributor provided by the EditorActionBuilder).

One of the advantages of EditorActionBars is the lifecycle of the actions that are added. They're created when the first editor of that type is loaded and exist until the last editor of that type is closed.

PluginActionBuilder subclasses

PluginActionBuilder is the base class for reading the 4 current action extension points. The subclasses are:

  • PluginActionSetBuilder - for org.eclipse.ui.actionSets, it creates an ActionSetContribution and the ActionDescriptors with ActionDescriptor.T_WORKBENCH or ActionDescriptor.T_WORKBENCH_PULLDOWN. It fills in each PluginActionSet provided by the ActionSetRegistry. Each PluginActionSet provides the IActionBars.
  • EditorActionBuilder - for org.eclipse.ui.editorActions, it creates an EditorContribution and ActionDescriptors with ActionDescriptor.T_EDITOR. The EditorContributions are added to an ExternalContributor. These are added to the EditorActionBars.
  • ViewActionBuilder - for org.eclipse.ui.viewActions, it creates a BasicContribution and the ActionDescriptors with ActionDescriptor.T_VIEW. The BasicContributions are added to the IViewSite IActionBars.
  • ViewerActionBuilder - this is part of the org.eclipse.ui.popupMenus extension point. This is used after IPartSite#registerContextMenu(*) creates a PopupMenuExtender. It creates a ViewerContribution and stores ActionDescriptors with ActionDescriptor.T_EDITOR or ActionDescriptor.T_VIEW. It's contributed to the popup menu by PopupMenuExtender#menuAboutToShow(*) - viewerContributions are called "static" contributions.
  • ObjectActionContributor - also for org.eclipse.ui.popupMenus, it stores the ActionDescriptions of type ActionDescriptor.T_POPUP in an ObjectContribution. This goes back to the ObjectActionContributorManager, which is called into from PopupMenuExtender#menuAboutToShow(*).


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

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.

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.

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>

Framework Enhancements for 3.3

Quick list of code issues to be addressed in 3.3.

Issues to Address

Issue 101 - PluginActions disconnected from Handlers

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

Does this become an issue if we replace our menuing abstraction?

Issue 102 - Commands implementation of label changing

Commands are an abstraction of behaviour, an have a lot in common with RetargetableActions. Certain kinds of RetargetableActions provide the ability to switch the labels, like switching Undo to Redo. This can be accomodated by adding text state to the command:

<extension
      point="org.eclipse.ui.commands">
   <command
         description="A test case for a command with state"
         id="org.eclipse.ui.tests.commandWithState"
         name="Command Wtih State">
      <state
            class="org.eclipse.jface.menus.TextState"
            id="NAME"/>
   </command>
</extension>

Currently, command states that have meaning are in org.eclipse.jface.menus.IMenuStateIds and org.eclipse.core.commands.INamedHandleStateIds.

The NAME state can be updated, and also supports state change notification. Our menu and/or toolbar rendering would have to listen for changes to properties and states associated with commands?

States are also the proposed way to handle information like toggled state (for check boxes or toggle buttons) and radio state (for a group of radio buttons).


Issue 103 - Menu items map to ParameterizedCommands

Just like a keybinding, each menu item or toolbar button would map to a ParameterizedCommand. If there was no parameters involved, then just containing the command id would be acceptable. For example, if you were creating menu items for Show Console View and Show Problems View each menu item would map to the ParameterizedCommand.

IHandlerService#executeCommand(String commandId, Event event) can execute a normal command with a selection event, and IHandlerSerivce#executeCommand(ParameterizedCommand command, Event event) does the same with a parameterized command.

Issue 104 - Dynamic menus need a good story

Dynamic menus need to work correctly for menus and menu items. They also need to create the drop-downs for toolbar items.

If the top part of a dynamic menu is declarative, then it can be provided without loading the plugin. Then a callback could be specified.

It seems right now that dynamic menus are mostly used to populate pull-down actions.

The appropriate IAction returns an IMenuCreator. This means that it must be instanitated inside the IAction.

Menu Proposal 1

Replace the entire thing with something really simple (haha). This should most closely resemble the design described in the original RFC in Historical Information Section.

Menu placements would be controlled by a tree of data.

  • File (menu)
    • New (menu)
      • (group: new.wizard)
      • New Wizard (item)
    • Open File... (item)
    • (group: file.close)
    • Close (item)
    • Close All (item)
  • Edit (menu)
    • Undo (item)
    • Redo (item)

All nodes in the tree would have visibility and enablement state. Enablement for the menu node would be tied to its Command.

The each menu item would be able to specify some order constraints.

Dynamic menus would have a simpler form. They would also be included in the declarative extension point (as much as possible) See Issue 104.

There would be a product level visibility (similar to the way Customize Perspective works for ActionSets). Would we also provide product level ability to customize other prospective menu item attributes? For example, a product level way to override menu order?


This would mean an out-and-out replacement of most of the internal classes to do with PluginActions and the ActionSetRegistry. For RCP support, we would continue to allow MenuManager and contribution items to be added, but those structures would need to be used to populate the tree structures. This is hard :-)


The new system would have to manage the interaction between 4 properties: Enabled, Active, Visible, Showing. Enabled is the state of the command (from the active handler), Active is the state of the handler, Visible is the state of the menu item, and Showing (and internal state) describes if the menu item can currently be seen by the user. Showing is a new concept designed to help with lazy loading and lazy creation of sub-menus. It's not in the current menu behaviour.

We would have to provide a visibility engine of somekind that would update the menu item structure visibility as state changed.

From an menu visibility/enablement point of view, Enabled is the state that matters. If no handler is active, Enabled will be false. If a handler is active, the Enabled will be the handler Enabled state.

Menu Proposal 2

We need a relatively straight forward model to represent our menu and toolbar structures. But JFace already provides decent coverage for that using MenuManager, ToolBarManager, and IContributionItems. As part of providing our rendering, we would be changing from ActionContributionItems that are currently used to some kind of CommandContributionItem.

IActions would still be used (mostly), but really our IAction would be the focal point of the rendering data model. For example, we'll provide a CommandBridgeAction. Knowledge of the command will allow the CommandBridgeAction to provide label, description, icons (from the ICommandImageService). Its runWithEvent(*) method will simply be handlerService.executeCommand("commandId", event).

As an alternative option, we could skip IActions and just use the CommandContributionItem. Why build an IAction as the intermediary when the CommandContributionItem can fetch things like the label, icon, etc for us.


We would still have to deal with Issue 104 and improve dynamic menu creation.

Perhaps IContributionManagerOverrides could be enhanced and used as the product override mechanism. There would have to be some API to update and list the product level list of overrides.

We would get rid of the PluginAction and ActionSet code.

We would need to provide a "visibility" engine to update the visibility of contribution items or IActions as the sources changed.

This still needs a good story for replacing EditorActionBarContributor and the IActionBar stuff ... providing the same functionality in an alternate place. I don't think providing the functionality is too much of a problem, but finding the correct place for it will require more thought.


Proposal "A" (Eric's World...;-):

The basic idea is to provide a single menu/action/command(+binding)/handler 'best practices' path. We can do this by re-using the existing extension points; tweaking as necessary to provide complete functionality and deprecating EP's that are no longer necessary.

  1. Move the current 'ActionSet.action' and 'ActionSet.menu' out from under the ActionSet extension point and make them their own EPs. This allows the definition of actions and menu structure independent of any particular action set and/or each other.
  2. Standardize on core expressions for vis and enablement states. It turns out that this mechanism is already capable of emulating many behaviours that can currently be accessed through the use of specialized EPs.
  3. Allow for the definition of the IWorkbenchWindowPulldownDelegate2 for any sub-menu to handle the dynamic menus case.
  4. Extend the 'menuBarPath' & 'tooBarPath' to allow for new conventions specific to siting an action anywhere within the workbench. This should allow us to deprecate View/Editor Actions -and- ObjectContributions.
  • EditorContributions:
  • "editor: <editorId>[/groupPath]"
  • ViewContributions:
  • "view-context:<viewId>[/groupPath]" - Site it in the given view's context menu
  • "view-chevron:<viewId>[/groupPath]" - Site it in the given view's chevron menu [in the given group]
  • "any context menu" - Replacement for the current ObjectContribution mechanism. The 'visibleWhen' core expression can determine whether the selection context is appropriate...


Proposal "A" - item 3 - Dynamic Menu interface

The menu extension can declaratively specify that the menu item is a dynamic menu, and provide a dynamic menu callback class that implements IDynamicMenu. When the menu item is about to show, the callback class will be handed an IMenuCollection, which contains a modifiable list of menu elements. As an example:

public interface IDynamicMenu {
	/**
	 * Called just before the given menu is about to show. This allows the
	 * implementor of this interface to modify the list of menu elements before
	 * the menu is actually shown.
	 * 
	 * @param menu
	 *            The menu that is about to show. This value is never
	 *            null.
	 */ 
	public void aboutToShow(IMenuCollection menu); 
}

And the IMenuCollection allows the modification of the menu that's about to show. Assume that MenuElement is the new flavour of IContributionItem/IContributionManager

public interface IMenuCollection {
	/**
	 * Appends a menu element to the end of the collection.
	 * 
	 * @param element
	 *            The element to append. Must not be null, and
	 *            must be of the appropriate type for the type of collection.
	 */
	public void add(MenuElement element);

	/**
	 * Adds a menu element at the given index.
	 * 
	 * @param index
	 *            The index at which to insert.
	 * @param element
	 *            The element to append. Must not be null, and
	 *            must be of the appropriate type for the type of collection.
	 */
	public void add(int index, MenuElement element);

	/**
	 * Removes all elements from the collection.
	 */
	public void clear();

	/**
	 * Gets the element at a given index.
	 * 
	 * @param index
	 *            The index at which to retrieve the element.
	 * @return The element at the index.
	 */
	public MenuElement get(int index);

	/**
	 * Removes the element at a given index.
	 * 
	 * @param index
	 *            The index at which to remove the element.
	 * @return The element that has been removed.
	 */
	public MenuElement remove(int index);

	/**
	 * Removes the given menu element, if it exists.
	 * 
	 * @param element
	 *            The element to remove.
	 * @return true if the object was removed; false if it could not be found.
	 */
	public boolean remove(MenuElement element);

	/**
	 * Returns the number of elements in the collection.
	 * 
	 * @return The size of the collection.
	 */
	public int size();
}

This is a change from the IMenuCreator interface, which directly references an SWT Control or SWT Menu.

Menu Proposal 2 UseCases

Visibility controls if the user can see the menu item in the menubar or toolbar or context menu. There are 3 levels of visibility that are checked in order:

  1. Overrides
  2. Visibility expression like <visibleWhen/> or the Expression used in IMenuService#contribute(*)
  3. Programmatic visibility property 1

If the check at a level returns false, the item won't be visible. If it returns true the next level of visibility is evaluated.

[1] I'm not sure if a programmatic level of visibility is warranted. The counter example is enablement, which can be changed through Overrides, the <enabledWhen/> expression on the handler, or programmatically from the handler isEnabled() method.

Menu Placement Locations

We'll support 7 root types to start with:

  1. SBar.MENU
    The main menu.
  2. SBar.TOOLBAR
    The main toolbar.
  3. SBar.VIEW_MENU
    A view menu. The first path segment will be the view id.
  4. SBar.VIEW_TOOLBAR
    A view toolbar. The first path segment will be the view id.
  5. SBar.CONTEXT_MENU
    A context menu. The first path segment will be the context menu id, or org.eclipse.ui.menus.context.any to apply to all context menus
  6. SBar.TRIM
    Contribute a piece of trim. This is already implemented
  7. SBar.STATUS
    Contribute information to the status line manager ... I'm not sure about this one, is it no longer necessary since they can contribute trim?

For describing menu locations as strings, we have a couple of options.

Option 1: URI

All of our paths can be generalized to a URI that looks like type://id/path.

  • The main File menu extention group marker: menu://org.eclipse.ui.menu.main/file/file.ext
  • The save toolbar: toolbar://org.eclipse.ui.toolbar.main/save.group
  • The resource navigator menu additions group marker: menu://org.eclipse.ui.views.ResourceNavigator/additions
  • The resource navigator toolbar: toolbar://org.eclipse.ui.views.ResourceNavigator
  • The text editor context menu additions group marker: popup://#EditorContext/additions
  • An object contribution additions group marker: popup://org.eclipse.ui.menu.any/additions

Option 2: A rooted Path

Just use paths of the form /type/id/path. They'll be IPath elements within eclipse.

  • The main File menu extention group marker: /menu/org.eclipse.ui.menu.main/file/file.ext
  • The save toolbar: /toolbar/org.eclipse.ui.toolbar.main/save.group
  • The resource navigator menu additions group marker: /menu/org.eclipse.ui.views.ResourceNavigator/additions
  • The resource navigator toolbar: /toolbar/org.eclipse.ui.views.ResourceNavigator
  • The text editor context menu additions group marker: /popup/#EditorContext/additions
  • An object contribution additions group marker: /popup/org.eclipse.ui.menu.any/additions


The usecases are being moved to Menu Item Placement Examples

Menu Proposal 3

In this proposal we don't seek to address most of what the RFC is talking about. Leave dynamic menus alone. The current extension points are fine, they just need to be backed by a single implementation.

It would involve cleaning up some of the workbench code, and wiring up the underlying framework like in Issue 101.

We would also go through all of the programmatic code in the workbench and make sure we're creating IActions backed by commands.

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:


Original 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.