Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Difference between revisions of "Menu Contributions"
(→Search->Registry Search programmatic editor action) |
(→Dynamic Previous Searches submenu) |
||
Line 1,073: | Line 1,073: | ||
2. Move these actions up to the IWorkbenchWindow level. With the correct expression the handler activation would be correct. But once created they would live as long as the workbench. | 2. Move these actions up to the IWorkbenchWindow level. With the correct expression the handler activation would be correct. But once created they would live as long as the workbench. | ||
− | = | + | = Add a dynamic submenu to the ProblemView menu = |
− | You | + | In [[#Add ProblemView menus|Add ProblemView menus]] we added 2 dynamic menus. You then have to implement IDynamicMenu in your provided class. |
− | + | <menu id="org.eclipse.ui.views.problems.filters.menu" | |
− | + | label="%ProblemView.Filters.label" | |
− | + | mnemonic="%ProblemView.Filters.mnemonic"> | |
− | + | <dynamic class="org.eclipse.ui.views.markers.internal.FilterMenu" /> | |
− | + | </menu> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
When your menu is about to show, you should get the callback: | When your menu is about to show, you should get the callback: | ||
− | + | class FilterMenu implements IDynamicMenu { | |
− | + | public void aboutToShow(Collection menuCollection) { | |
− | + | menuCollection.clear(); | |
− | + | String[] filterIds = getCurrentFilters(); | |
− | + | for (int i = 0; i < filterIds.length; i++) { | |
− | + | String id = filterIds[i]; | |
− | + | ItemDesc item = new ItemDesc(getParameterizedCommand(id)); | |
− | + | item.setLabel(getFilterName(id)); | |
− | + | menuCollection.add(item); | |
− | + | } | |
− | + | } | |
− | + | ||
− | + | public String[] getCurrentFilters() { | |
− | + | return new String[0]; | |
− | + | } | |
− | + | ||
− | + | public ParameterizedCommand getParameterizedCommand(String filterId) { | |
− | + | // return the toggle filter state command tied to the filter id | |
− | + | return null; | |
+ | } | ||
+ | |||
+ | public String getFilterName(String id) { | ||
+ | return null; | ||
+ | } | ||
+ | } | ||
= Dynamic Previous Searches editors group = | = Dynamic Previous Searches editors group = |
Revision as of 10:39, 24 October 2006
Placement examples that try to describe the old to new way. Please contribute comments and suggestions in the discussion area or on Bug 154130 -KeyBindings- Finish re-work of commands and key bindings.
Contents
- 1 Add ProblemView menus
- 2 Add Toggle Mark Occurrences to main toolbar
- 3 Possible Java Search Menu Example
- 4 IFile object contribution
- 5 Text editor popup action
- 6 Product removes the Project menu
- 7 Widget in the main toolbar
- 8 Edit->Undo relabel action
- 9 Adding programmatic menus and handlers tied to an editor
- 10 Add a dynamic submenu to the ProblemView menu
- 11 Dynamic Previous Searches editors group
- 12 Example Matrix
Add the Problems view menus. The problems view has one toolbar action and in the view menu, 3 actions and 2 dynamic submenus. It also has a dynamic menu and another bunch of actions in its context menu.
Commands
First define commands that are specific to the view. Since these are view commands, we can specify a default handler ... we're unlikely to replace it.
<extension point="org.eclipse.ui.commands"> <category id="org.eclipse.ui.views.problems" name="%ProblemView.category.name"> </category> <command categoryId="org.eclipse.ui.views.problems" defaultHandler="org.eclipse.ui.views.markers.internal.TableSortHandler" description="%ProblemView.Sorting.description" id="org.eclipse.ui.views.problems.sorting" name="%ProblemView.Sorting.name"> </command> <-- the view preference command would probably be defined once with the other preference contributions --> <command categoryId="org.eclipse.ui.views.problems" defaultHandler="org.eclipse.ui.preferences.ViewPreferencesHandler" description="%ViewPreferences.description" id="org.eclipse.ui.preferences.viewPreferences" name="%ViewPreferences.name"> <commandParameter id="markerEnablementName" name="%ViewPreferences.markerEnablementName.name" optional="false" /> <commandParameter id="markerLimitName" name="ViewPreferences.markerLimitName.name" optional="false" /> </command> <command categoryId="org.eclipse.ui.views.problems" defaultHandler="org.eclipse.ui.views.markers.internal.FiltersHandler" description="%ProblemView.ConfigureFilters.description" id="org.eclipse.ui.views.problems.configureFilters" name="%ProblemView.ConfigureFilters.name"> </command> <command categoryId="org.eclipse.ui.views.problems" defaultHandler="org.eclipse.ui.views.markers.internal.OpenMarkerHandler" description="%ProblemView.GoTo.description" id="org.eclipse.ui.views.problems.goTo" name="%ProblemView.GoTo.name" /> </extension>
Handlers
We can also use a number of global commands, like copy, paste, delete, quick fix, and properties. For these, we just need to define our handlers. We need to add them with <activeWhen/> clauses to restrict them to being active when the view is active.
<extension point="org.eclipse.ui.handlers"> <handler commandId="org.eclipse.ui.edit.copy" class="org.eclipse.ui.views.markers.internal.CopyMarkerHandler"> <enabledWhen> <not> <count value="0" /> </not> </enabledWhen> <activeWhen> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </activeWhen> </handler> <handler commandId="org.eclipse.ui.edit.paste" class="org.eclipse.ui.views.markers.internal.PasteMarkerHandler"> <enabledWhen> <not> <count value="0" /> </not> </enabledWhen> <activeWhen> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </activeWhen> </handler> <handler commandId="org.eclipse.ui.edit.delete" class="org.eclipse.ui.views.markers.internal.RemoveMarkerHandler"> <enabledWhen> <not> <count value="0" /> </not> </enabledWhen> <activeWhen> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </activeWhen> </handler> <handler commandId="org.eclipse.ui.edit.selectAll" class="org.eclipse.ui.views.markers.internal.SelectAllMarkersHandler"> <enabledWhen> <not> <count value="0" /> </not> </enabledWhen> <activeWhen> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </activeWhen> </handler> <handler commandId="org.eclipse.jdt.ui.edit.text.java.correction.assist.proposals" class="org.eclipse.ui.views.markers.internal.ResolveMarkerHandler"> <enabledWhen> <not> <count value="0" /> </not> </enabledWhen> <activeWhen> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </activeWhen> </handler> <handler commandId="org.eclipse.ui.file.properties" class="org.eclipse.ui.views.markers.internal.ProblemPropertiesHandler"> <enabledWhen> <not> <count value="0" /> </not> </enabledWhen> <activeWhen> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </activeWhen> </handler> </extension>
Or we can programmatically activate them through the IHandlerService which we would retrieve from the ProblemView site.
IHandlerService handlerServ = (IHandlerService)getSite().getService(IHandlerService.class); copy = new CopyMarkerHandler(); handlerServ.activateHandler("org.eclipse.ui.edit.copy", copy);
Using the ProblemView site to access the IHandlerService handles the <activeWhen/> clause for us, and our programmatic handler would manage its own enablement state.
Expression Sidebar
You can see that the <activeWhen/>, <enabledWhen/>, and probably the <visibleWhen/> are likely to be replicated over and over again. A possible option is some kind of expression template markup ... either in its own extension or supported by our UI extensions that can use core expressions.
Here's an example of using expression templates in its own extension point.
<extension point="org.eclipse.core.expression.templates"> <expression id="isPartActive"> <parameter id="partId" /> <with variable="activePartId"> <equals value="$partId" /> </with> </expression> <expression id="isActionSetActive"> <parameter id="actionSetId" /> <with variable="activeContexts"> <iterator operator="or"> <equals value="$actionSetId" /> </iterator> </with> </expression> <expression id="isContextActive"> <parameter id="contextId" /> <with variable="activeContexts"> <iterator operator="or"> <equals value="$contextId" /> </iterator> </with> </expression> <expression id="isSelectionAvailable"> <not> <count value="0" /> </not> </expression> </extension>
This could be used to simplify the handler definitions:
<extension point="org.eclipse.ui.handlers"> <handler commandId="org.eclipse.ui.edit.copy" class="org.eclipse.ui.views.markers.internal.CopyMarkerHandler"> <enabledWhen> <evaluate ref="isSelectionAvailable" /> </enabledWhen> <activeWhen> <evaluate ref="isPartActive"> <parameter id="partId" value="org.eclipse.ui.views.ProblemView" /> </evaluate> </activeWhen> </handler> </extension>
If we allow recursive template definitions, that would allow you to specify the concrete expression once and then reference it throughout your view.
<extension point="org.eclipse.core.expression.templates"> <expression id="isProblemViewActive"> <evaluate ref="isPartActive"> <parameter id="partId" value="org.eclipse.ui.views.ProblemView" /> </evaluate> </expression> </extension> <extension point="org.eclipse.ui.handlers"> <handler commandId="org.eclipse.ui.edit.copy" class="org.eclipse.ui.views.markers.internal.CopyMarkerHandler"> <enabledWhen> <evaluate ref="isSelectionAvailable" /> </enabledWhen> <activeWhen> <evaluate ref="isProblemViewActive" /> </activeWhen> </handler> </extension>
This reduces the handler definition even more.
Menus
Then we would define the ProblemView menu structures. We are using 3 roots: the view menu, the view toolbar, and the view context menu. This is an example of an "in-place" menu definition. The XML hierarchy mirrors the menu hierarchy.
<extension point="org.eclipse.ui.menus"> <menuCollection location="menu://org.eclipse.ui.views.ProblemView"> <item commandId="org.eclipse.ui.views.problems.sorting" mnemonic="%ProblemView.Sorting.mnemonic" tooltip="%ProblemView.Sorting.tooltip" /> <menu id="org.eclipse.ui.views.problems.groupBy.menu" label="%ProblemView.GroupBy.label" mnemonic="%ProblemView.GroupBy.mnemonic"> <dynamic class="org.eclipse.ui.views.markers.internal.GroupByMenu" /> </menu> <separator id="group.filter" visible="true" /> <menu id="org.eclipse.ui.views.problems.filters.menu" label="%ProblemView.Filters.label" mnemonic="%ProblemView.Filters.mnemonic"> <dynamic class="org.eclipse.ui.views.markers.internal.FilterMenu" /> </menu> <item commandId="org.eclipse.ui.views.problems.configureFilters" mnemonic="%ProblemView.ConfigureFilters.mnemonic" icon="$nl$/elcl16/filter_ps.gif" tooltip="%ProblemView.ConfigureFilters.tooltip" /> <item commandId="org.eclipse.ui.preferences.viewPreferences" mnemonic="%ViewPreferences.mnemonic"> <parameter id="markerEnablementName" value="LIMIT_PROBLEMS" /> <parameter id="markerLimitName" value="PROBLEMS_LIMIT" /> </item> </menuCollection> <menuCollection location="toolbar://org.eclipse.ui.views.ProblemView"> <item commandId="org.eclipse.ui.views.problems.configureFilters" icon="$nl$/elcl16/filter_ps.gif" tooltip="%ProblemView.ConfigureFilters.tooltip" /> </menuCollection> <menuCollection location="popup://org.eclipse.ui.views.ProblemView"> <item commandId="org.eclipse.ui.views.problems.goTo" mnemonic="%ProblemView.GoTo.mnemonic" icon="$nl$/elcl16/gotoobj_tsk.gif" disabledIcon="$nl$/dlcl16/gotoobj_tsk.gif" tooltip="%ProblemView.GoTo.tooltip" /> <separator id="group.showIn" visible="true" /> <menu id="org.eclipse.ui.views.problems.showIn.menu" label="%ProblemView.ShowIn.label" mnemonic="%ProblemView.ShowIn.mnemonic"> <dynamic class="org.eclipse.ui.actions.ShowInContributions" /> </menu> <separator id="group.edit" visible="true" /> <item commandId="org.eclipse.ui.edit.copy" mnemonic="%ProblemView.copy.mnemonic" icon="$nl$/icons/full/etool16/copy_edit.gif" disabledIcon="$nl$/icons/full/dtool16/copy_edit.gif" /> <item commandId="org.eclipse.ui.edit.paste" mnemonic="%ProblemView.paste.mnemonic" icon="$nl$/icons/full/etool16/paste_edit.gif" disabledIcon="$nl$/icons/full/dtool16/paste_edit.gif" /> <item commandId="org.eclipse.ui.edit.delete" mnemonic="%ProblemView.delete.mnemonic" icon="$nl$/icons/full/etool16/delete_edit.gif" disabledIcon="$nl$/icons/full/dtool16/delete_edit.gif"> <visibleWhen> <not> <with variable="activePartId"> <equals value="org.eclipse.ui.views.ProblemView" /> </with> </not> </visibleWhen> </item> <item commandId="org.eclipse.ui.edit.selectAll" mnemonic="%ProblemView.selectAll.mnemonic" /> <separator id="group.resolve" visible="true" /> <item commandId="org.eclipse.jdt.ui.edit.text.java.correction.assist.proposals" mnemonic="%ProblemView.Resolve.mnemonic" icon="$nl$/icons/full/elcl16/smartmode_co.gif" disabledIcon="$nl$/icons/full/dlcl16/smartmode_co.gif" /> <separator id="additions" visible="true" /> <separator id="group.properties" visible="true" /> <item commandId="org.eclipse.ui.file.properties" mnemonic="%ProblemView.Properties.mnemonic" /> </menuCollection> </extension>
Some constraints on the system:
- Identifiers (id) for <menu/> and <item/> elements must be globally unique if they are specified.
- You can only reference a <menu/> or <item/> that has an id.
- If you are just placing your commands, you can leave them with only a command id. You don't have to specify an item id.
- <separator/> ids only have to be unique within that menu level.
- You can provide an <item/> label attribute. If none is provided, it will take the command name.
- In this design the item contains most of the same rendering information that <action/> did.
- <menu/> and <item/> can have <visibleWhen/> clauses. If a menu's <visibleWhen/> evaluates to false, we will never ask the items contained in that menu.
- All of the display-able attributes are translatable
Menu - JSR198
There is a JSR describing how IDEs can contribute menus. Below is a sample for 2 items:
- org.eclipse.ui.views.problems.sorting.item from menu://org.eclipse.ui.views.ProblemView
- org.eclipse.ui.views.problems.resolveMarker.item from popup://org.eclipse.ui.views.ProblemView
<menu-hook> <actions> <action id="org.eclipse.ui.views.problems.sorting.item"> <label>Sorting...</label> <mnemonic>S</mnemonic> <tooltip>Change the Sort order</tooltip> <invoke-class>org.eclipse.ui.views.problems.sorting</invoke-class> </action> <action id="org.eclipse.ui.views.problems.resolveMarker.item"> <label>Quick Fix</label> <mnemonic>Q</mnemonic> <iconpath>$nl$/icons/full/elcl16/smartmode_co.gif</iconpath> <invoke-class>org.eclipse.jdt.ui.edit.text.java.correction.assist.proposals</invoke-class> <update-class>org.eclipse.jdt.ui.edit.text.java.correction.assist.proposals</update-class> </action> </actions> <menus> <menubar id="org.eclipse.ui.views.ProblemView"> <menu id="org.eclipse.ui.views.ProblemView"> <section id="problem.view.section"> <item action-ref="org.eclipse.ui.views.problems.sorting.item" /> <menu id="org.eclipse.ui.views.problems.groupBy.menu"> <label>Group By</label> <mnemonic>G</mnemonic> </menu> </section> </menu> </menubar> <popup id="org.eclipse.ui.views.ProblemView"> <section id="group.resolve"> <item action-ref="org.eclipse.ui.views.problems.resolveMarker.item" /> </section> </popup> </menus> </menu-hook>
Some thoughts:
- the actions can only specify one icon
- the actions can't *quite* link to our commands
- the menus can't specify dynamic submenus
Menu - XUL
With Mozilla everywhere, there is the probability eclipse will include xulrunner. Menu definitions that are consistent with XUL look like:
<keyset> <key id="paste-key" modifiers="accel" key="V" /> </keyset> <menubar id="org.eclipse.ui.views.ProblemView"> <menupopup id="org.eclipse.ui.views.ProblemView"> <menuitem id="org.eclipse.ui.views.problems.sorting.item" accesskey="S" key="paste-key" label="Sorting..." oncommand="invokeCommand('org.eclipse.ui.views.problems.sorting')" /> <menu id="org.eclipse.ui.views.problems.groupBy.menu" label="Group By" accesskey="G"> <menupopup id="groupby.popup"> </menupopup> </menu> </menupopup> </menubar>
XUL supports everything as a flavour of a DOM, and javascripting can drive your buttons to perform commands. I suspect the scripting would allow you to dynamically update menus (dynamic menus) on popup, depending on what events the DOM would report to you.
Menus API
We can contribute menu definitions through the IMenuService API.
The above example can be done for the view menus:
public void createProblemsViewMenu() { IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); Collection menuCollection = new ArrayList(); // add a menu item ItemDesc sort = new ItemDesc("org.eclipse.ui.views.problems.sorting"); sort.setMnemonic("S"); sort.setTooltip("Change the Sort order"); menuCollection.add(sort); // add a dynamic submenu MenuDesc groupByMenu = new MenuDesc( "org.eclipse.ui.views.problems.groupBy.menu", "Group By"); groupByMenu.setMnemonic("B"); groupByMenu.setDynamicMenu(new GroupByDynamicMenu()); menuCollection.add(groupByMenu); menuCollection.add(new SeparatorDesc("group.filter", true)); // add a sub menu MenuDesc filtersMenu = new MenuDesc( "org.eclipse.ui.views.problems.filters.menu", "Filters"); filtersMenu.setMnemonic("F"); filtersMenu.setDynamicMenu(new FiltersDynamicMenu()); menuCollection.add(filtersMenu); // add items to the sub menu ItemDesc fitem = new ItemDesc( "org.eclipse.ui.views.problems.configureFilters"); fitem.setTooltip("Configure the filters to be applied to this view"); fitem.setMnemonic("C"); fitem.setImage(getSharedImage("elcl16/filter_ps.gif")); menuCollection.add(fitem); fitem = new ItemDesc(getProblemPreferencesCommand()); fitem.setTooltip("Open the preferences"); fitem.setMnemonic("P"); menuCollection.add(fitem); // place the contribution back into the menu service. IMenuContribution viewMenu = menuServ.contribute( "menu://org.eclipse.ui.views.ProblemView", menuCollection); // and possibly much later menuServ.remove(viewMenu); }
You add your menus and menu items to an ordered collection, then contribute them to a location (like menu://org.eclipse.ui.views.ProblemView
) and potentially a location modifier (after some id).
You can add Menus, Separators, Items, and Widgets to your collection. Once you've update your collection, contribute it back to the menu service.
If you need to reference an Item at some time you will need to create it with an id, which must be unique. For reuse you can create Items without ids, but then you will be unable to reference them.
Menus cannot be re-used in this manner, and so they have an intrinsic id value. Separators are unique within one menu level, so they also contain their id value.
Add Toggle Mark Occurrences to main toolbar
We can provide the Toggle Mark Occurrences toolbar button. It's normally contributed through an actionSet as a retargettable action, and the Java and Class File editors *EditorActionBarContributors provide the implementation ToggleMarkOccurrencesAction through IActionBars#setGlobalActionHandler(*).
Commands
First define the toggle mark occurrences command. Pretty straight forward, although it needs a "STYLE" state since it can be toggled. To allow handlers to update the label for the menu/toolbar items, we also add the "NAME" state.
<extension point="org.eclipse.ui.commands"> <command categoryId="org.eclipse.jdt.ui.category.source" description="%jdt.ui.ToggleMarkOccurrences.description" id="org.eclipse.jdt.ui.edit.text.java.toggleMarkOccurrences" name="%jdt.ui.ToggleMarkOccurrences.name"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> <state id="STYLE" class="org.eclipse.jface.commands.ToggleState:true" /> </command> </extension>
Handlers
This command doesn't have a default handler, as it only applies to specific editors that are provided. So we would provide the handler for the java editor.
<extension point="org.eclipse.ui.handlers"> <handler commandId="org.eclipse.jdt.ui.edit.text.java.toggleMarkOccurrences" class="org.eclipse.jdt.internal.ui.javaeditor.ToggleMarkOccurrencesHandler"> <activeWhen> <with variable="activePartId"> <or> <equals value="org.eclipse.jdt.ui.CompilationUnitEditor" /> <equals value="org.eclipse.jdt.ui.ClassFileEditor" /> </or> </with> </activeWhen> </handler> </extension>
We're active for both the Java editor and the Class File editor. There is also the option to programmatically install the handler.
AndExpression expr = new AndExperssion(); expr.add(new ActivePartIdExpression("org.eclipse.jdt.ui.CompilationUnitEditor")); expr.add(new ActivePartIdExpression("org.eclipse.jdt.ui.ClassFileEditor")); IHandlerService handlerServ = (IHandlerService)getSite().getWorkbenchWindow().getService(IHandlerService.class); toggleOccurrencesHandler = new ToggleMarkOccurrencesHandler(); handlerServ.activateHandler("org.eclipse.jdt.ui.edit.text.java.toggleMarkOccurrences", toggleOccurrencesHandler, expr);
Since the same handler is valid for both editors, we install it with a specific expression and don't tie the activation to the part site. But as written, the toggleOccurrencesHandler
will exist as long as the workbench window exists.
Menus
<extension point="org.eclipse.ui.menus"> <menuCollection location="toolbar:org.eclipse.ui.edit.text.actionSet.presentation?after=Presentation"> <item commandId="org.eclipse.jdt.ui.edit.text.java.toggleMarkOccurrences" tooltip="%jdt.ui.ToggleMarkOccurrences.tooltip" icon="$nl$/icons/full/etool16/mark_occurrences.gif" disabledIcon="$nl$/icons/full/dtool16/mark_occurrences.gif" helpContextId="toggle_mark_occurrences_action_context"> <visibleWhen> <with variable="activeContexts"> <iterator operator="or"> <equals value="org.eclipse.jdt.ui.text.java.actionSet.presentation" /> </iterator> </with> </visibleWhen> </item> </menuCollection> </extension>
Here we are adding ourselves to the workbench text presentation toolbar, which
has already been contributed. This item is also tied to an actionSet.
Menus API
The above XML can be done using the menus API:
public void createToggleMarkOccurrences() { IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); ItemDesc toggleItem = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.toggleMarkOccurrences"); toggleItem .setImage(getSharedImage("icons/full/etool16/mark_occurrences.gif")); toggleItem .setDisabledImage(getSharedImage("icons/full/dtool16/mark_occurrences.gif")); toggleItem.setHelpContextId("toggle_mark_occurrences_action_context"); toggleItem.setVisibleWhen(new ActiveContextExpression( "org.eclipse.jdt.ui.text.java.actionSet.presentation")); menuServ .contribute( "toolbar:org.eclipse.ui.edit.text.actionSet.presentation?after=Presentation", Collections.singletonList(toggleItem)); }
This asks for a menu root in the org.eclipse.ui.edit.text.actionSet.presentation toolbar after the Presentation id.
It's contributed with a visibleWhen clause ActiveContextExpression(JAVA_PRESENTATION_ACTIONSET_ID)
, so it will be visible when the actionSet is active.
Possible Java Search Menu Example
The java search menu items are added through a Java Search action set. They have code that enables/disables the action set depending on the active editor.
ActionSet context
For something to go in an actionSet, then we would define the actionSet context.
<extension point="org.eclipse.ui.contexts"> <context description="%JavaSearchActionSet.description" id="org.eclipse.jdt.ui.SearchActionSet" name="%JavaSearchActionSet.label" parentId="org.eclipse.ui.contexts.actionSet"> </context> </extension>
Commands
Also, a number of the items were retargetable actions that allow label updates. The current pattern would mean adding the NAME state to a lot of the commands.
<extension point="org.eclipse.ui.commands"> <command name="%ActionDefinition.readAccessInworkspace.name" description="%ActionDefinition.readAccessInWorkspace.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.workspace"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.readAccessInProject.name" description="%ActionDefinition.readAccessInProject.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.project"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.readAccessInHierarchy.name" description="%ActionDefinition.readAccessInHierarchy.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.hierarchy"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.readAccessInWorkingSet.name" description="%ActionDefinition.readAccessInWorkingSet.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.working.set"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.writeAccessInWorkspace.name" description="%ActionDefinition.writeAccessInWorkspace.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.workspace"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.writeAccessInProject.name" description="%ActionDefinition.writeAccessInProject.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.project"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.writeAccessInHierarchy.name" description="%ActionDefinition.writeAccessInHierarchy.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.hierarchy"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> <command name="%ActionDefinition.writeAccessInWorkingSet.name" description="%ActionDefinition.writeAccessInWorkingSet.description" categoryId="org.eclipse.search.ui.category.search" id="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.working.set"> <state id="NAME" class="org.eclipse.jface.menus.TextState" /> </command> </extension>
Menus
We'll assume that the Search menu is globally defined elsewhere by the org.eclipse.search plugin.
<extension point="org.eclipse.ui.menus"> <menuCollection location="menu://org.eclipse.ui.main.menu?after=navigate"> <menu label="%searchMenu.label" mnemonic="%searchMenu.mnemonic" id="org.eclipse.search.menu"> <separator name="internalDialogGroup" visible="false" /> <separator name="dialogGroup" visible="false" /> <separator name="fileSearchContextMenuActionsGroup" visible="true" /> <separator name="contextMenuActionsGroup" visible="true" /> <separator name="occurencesActionsGroup" visible="true" /> <separator name="extraSearchGroup" visible="true" /> </menu> </menuCollection> </extension>
Then the JDT plugin would contribute the menu items to search, where the menuRoot location specifies the starting point for adding the menus. For groups of actions like the Write Access or Read Access shown here, they can just be specified in order. The <visibleWhen/> clauses must be specified on the items contributed if they want to belong to the actionSet, but if the contribute items are contain in a contributed menu, it can just be specified on the <menu/> element. We also use the relative URI notation, since the menu id org.eclipse.search.menu must be unique.
<extension point="org.eclipse.ui.menus"> <menuCollection location="menu:org.eclipse.search.menu?after=dialogGroup"> <item commandId="org.eclipse.jdt.internal.ui.search.openJavaSearchPage" label="%openJavaSearchPageAction.label" mnemonic="%openJavaSearchPageAction.mnemonic" icon="$nl$/icons/full/obj16/jsearch_obj.gif" helpContextId="java_search_action_context"> <visibleWhen> <with variable="activeContexts"> <iterator operator="or"> <equals value="org.eclipse.jdt.ui.SearchActionSet" /> </iterator> </with> </visibleWhen> </item> </menuCollection> <menuCollection location="menu:org.eclipse.search.menu?after=contextMenuActionsGroup"> <menu id="readAccessSubMenu" label="%readAccessSubMenu.label" mnemonic="%readAccessSubMenu.mnemonic"> <separator name="group1" visible="false" /> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.workspace" label="%InWorkspace.label" mnemonic="%InWorkspace.mnemonic"> </item> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.project" label="%InProject.label" mnemonic="%InProject.mnemonic"> </item> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.hierarchy" label="%InHierarchy.label" mnemonic="%InHierarchy.mnemonic"> </item> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.read.access.in.working.set" label="%InWorkingSet.label" mnemonic="%InWorkingSet.mnemonic"> </item> <visibleWhen> <with variable="activeContexts"> <iterator operator="or"> <equals value="org.eclipse.jdt.ui.SearchActionSet" /> </iterator> </with> </visibleWhen> </menu> <menu id="writeAccessSubMenu" label="%writeAccessSubMenu.label" mnemonic="%writeAccessSubMenu.mnemonic"> <separator name="group1" visible="false" /> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.workspace" label="%InWorkspace.label" mnemonic="%InWorkspace.mnemonic"> </item> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.project" label="%InProject.label" mnemonic="%InProject.mnemonic"> </item> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.hierarchy" label="%InHierarchy.label" mnemonic="%InHierarchy.mnemonic"> </item> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.write.access.in.working.set" label="%InWorkingSet.label" mnemonic="%InWorkingSet.mnemonic"> </item> <visibleWhen> <with variable="activeContexts"> <iterator operator="or"> <equals value="org.eclipse.jdt.ui.SearchActionSet" /> </iterator> </with> </visibleWhen> </menu> </menuCollection> <menuCollection location="menu:org.eclipse.search.menu?after=occurencesActionsGroup"> <item commandId="org.eclipse.jdt.ui.edit.text.java.search.occurrences.in.file.quickMenu" label="%occurrencesSubMenu.label"> <dynamic class="org.eclipse.jdt.internal.ui.actions.OccurrencesSearchMenuAction" /> <visibleWhen> <with variable="activeContexts"> <iterator operator="or"> <equals value="org.eclipse.jdt.ui.SearchActionSet" /> </iterator> </with> </visibleWhen> </item> </menuCollection> </extension>
Currently, the java search menus are in the Java Search actionSet, that is dynamically enabled/disabled. This could also be done by specifying a visibleWhen like:
<visibleWhen> <with variable="activeEditorId"> <or> <equals value="org.eclipse.jdt.ui.CompilationUnitEditor" /> <equals value="org.eclipse.jdt.ui.ClassFileEditor" /> </or> </with> </visibleWhen>
This would make the visible if either the Java or Class File editor was the active editor, and they would disappear otherwise.
Menus API
The API can be used to contribute to the main menu bar:
public void addSearchMenu() { IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); MenuDesc searchMenu = new MenuDesc("org.eclipse.search.menu", "Search"); searchMenu.setMnemonic("a"); searchMenu.add(new SeparatorDesc("internalDialogGroup", false)); searchMenu.add(new SeparatorDesc("dialogGroup", false)); searchMenu.add(new SeparatorDesc("fileSearchContextMenuActionsGroup", true)); searchMenu.add(new SeparatorDesc("contextMenuActionsGroup", true)); searchMenu.add(new SeparatorDesc("occurencesActionsGroup", true)); searchMenu.add(new SeparatorDesc("extraSearchGroup", true)); menuServ.contribute("menu://org.eclipse.ui.main.menu?after=navigate", Collections.singletonList(searchMenu)); }
It's just a menu inserted at the menu root location.
Then another plugin can contribute to the search menu:
public void addToSearchMenu() { IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); ItemDesc searchItem = new ItemDesc( "org.eclipse.jdt.internal.ui.search.openJavaSearchPage"); searchItem.setMnemonic("J"); searchItem.setImage(getSharedImage("icons/full/obj16/jsearch_obj.gif")); searchItem.setHelpContextId("java_search_action_context"); ActiveContextExpression activeSearchActionSet = new ActiveContextExpression( "org.eclipse.jdt.ui.SearchActionSet"); searchItem.setVisibleWhen(activeSearchActionSet); menuServ.contribute("menu:org.eclipse.search.menu?after=dialogGroup", Collections.singletonList(searchItem)); Collection menuCollection = new ArrayList(); MenuDesc readMenu = new MenuDesc("readAccessSubMenu", "Read Access"); readMenu.setMnemonic("R"); readMenu.setVisibleWhen(activeSearchActionSet); menuCollection.add(readMenu); readMenu.add(new SeparatorDesc("group1", false)); ItemDesc item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.read.access.in.workspace"); item.setMnemonic("W"); readMenu.add(item); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.read.access.in.project"); item.setMnemonic("P"); readMenu.add(item); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.read.access.in.hierarchy"); item.setMnemonic("H"); readMenu.add(item); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.read.access.in.working.set"); item.setMnemonic("S"); readMenu.add(item); MenuDesc writeMenu = new MenuDesc("readAccessSubMenu", "Read Access"); writeMenu.setMnemonic("R"); writeMenu.setVisibleWhen(activeSearchActionSet); menuCollection.add(writeMenu); writeMenu.add(new SeparatorDesc("group1", false)); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.write.access.in.workspace"); item.setMnemonic("W"); writeMenu.add(item); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.write.access.in.project"); item.setMnemonic("P"); writeMenu.add(item); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.write.access.in.hierarchy"); item.setMnemonic("H"); writeMenu.add(item); item = new ItemDesc( "org.eclipse.jdt.ui.edit.text.java.search.write.access.in.working.set"); item.setMnemonic("S"); writeMenu.add(item); menuServ.contribute( "menu:org.eclipse.search.menu?after=contextMenuActionsGroup", menuCollection); }
When done, contribute the built menu back to the menu service.
IFile object contribution
We also have to provide object contributions (which in the past were scoped by objectClass).
Menus
There will be a reserved popup ID, "org.eclipse.ui.menus.popup.any" that will allow contributions to any popup menu.
<extension point="org.eclipse.ui.menus"> <menuCollection location="popup://org.eclipse.ui.menus.popup.any?after=additions"> <item commandId="org.eclipse.ui.examples.wiki.post" mnemonic="%WikiExample.post.mnemonic" icon="$nl$/icons/full/elcl16/post_wiki.gif"> <visibleWhen> <with variable="selection"> <adapt type="org.eclipse.core.resources.IFile" /> </with> </visibleWhen> </item> <item commandId="org.eclipse.ui.examples.wiki.load" mnemonic="%WikiExample.load.mnemonic" icon="$nl$/icons/full/elcl16/load_wiki.gif"> <visibleWhen> <adapt type="org.eclipse.core.resources.IFile" /> </visibleWhen> </item> </menuCollection> </extension>
It's probably that the default variable for core expression evaluations would be selection, so you wouldn't need the <with/> clause like in the second item above. There would probably also be a short-hand to tie the visibility to an active handler. Maybe <visibleWhen handler="true"/>
Menus API
So programmatically it is similar to all other menu contributions.
public void addIFileContribution() { IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); Expression ifileExpression = new AdaptSelectionExpression( "org.eclipse.core.resources.IFile"); Collection menuCollection = new ArrayList(); ItemDesc wikiItem = new ItemDesc("org.eclipse.ui.examples.wiki.post"); wikiItem.setMnemonic("P"); wikiItem.setImage(getSharedImage("icons/full/elcl16/post_wiki.gif")); wikiItem.setVisibleWhen(ifileExpression); menuCollection.add(wikiItem); wikiItem = new ItemDesc("org.eclipse.ui.examples.wiki.load"); wikiItem.setMnemonic("L"); wikiItem.setImage(getSharedImage("icons/full/elcl16/load_wiki.gif")); wikiItem.setVisibleWhen(ifileExpression); menuCollection.add(wikiItem); menuServ.contribute( "popup://org.eclipse.ui.menus.popup.any?after=additions", menuCollection); }
The location of org.eclipse.ui.menus.popup.any specifies any context menu, and the expression ties it to a specific objectClass. Using the new expression syntax you can make your conditions more complex.
You can set your visibleWhen expression on each item as you create it.
Text editor popup action
Popups can be targetted at any registered context menu, or at all of them. This is the Scramble Text command to be added the the standard text editor.
Commands
First define the command and its handler.
<extension point="org.eclipse.ui.commands"> <command id="org.eclipse.ui.examples.menus.scramble.text" defaultHandler="org.eclipse.ui.examples.menus.internal.ScrambleTextHandler" name="%ScrambleText.name" description="%ScrambleText.description" /> </extension>
Menus
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"> <menuCollection location="popup://#EditorContext?after=additions"> <item commandId="org.eclipse.ui.examples.menus.scramble.text" mnemonic="%ScrambleText.mnemonic" icon="$nl$/icons/full/eobj16/scramble.gif" /> </menuCollection> </extension>
Menus API
Programmatically do this, you would have to go through the IMenuService.
public void addTextMenuContribution() { IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); ItemDesc item = new ItemDesc( "org.eclipse.ui.examples.menus.scramble.text"); item.setMnemonic("c"); item.setImage(getSharedImage("icons/full/eobj16/scramble.gif")); menuServ.contribute("popup://#EditorContext?after=additions", Collections.singletonList(item)); }
An RCP product wishes to remove the Project menu. It should be possible to override the visibility of menu contributions.
public void addOverride() { // the RCP app would already have its product key Object productKey = null; IMenuService menuServ = (IMenuService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getService(IMenuService.class); menuServ.addOverride(productKey, "menu:project", new OverrideAdapter() { public Boolean getVisible() { 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.
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 null
the next level of visibility is evaluated. The null
case is to keep it consistent with other overrides.
The override service is ID based. For items which haven't specified their ID, the override will be applied to the commandId (which is required on every item).
Widget in the main toolbar
You can use the extension point to contribute a control to the toolbar. You use the <widget/> element instead of the <item/> element.
<extension point="org.eclipse.ui.menus"> <menuCollection location="toolbar:org.eclipse.search.toolbar"> <widget id="org.eclipse.ui.examples.menus.searchBar" class="org.eclipse.ui.examples.menus.internal.SearchBar"> <visibleWhen> <with variable="activeContexts"> <iterator operator="or"> <equals value="org.eclipse.jdt.ui.SearchActionSet" /> </iterator> </with> </visibleWhen> </widget> </menuCollection> </extension>
The widget class must implement org.eclipse.ui.menus.IWorkbenchWidget, and provide fill(Composite parent)
for a toolbar.
I'm not sure how far to go with IWorkbenchWidget. We already use this interface for adding controls to the trim, and there are open bug requests about adding arbitrary controls to the toolbars.
Will menus take arbitrary controls?
Edit->Undo relabel action
Like the Undo action, sometimes menu items would want to allow their label to be updated. This is currently handled through the Command objects and the handlers. IMenuStateIds and INamedHandleStateIds define some states that we currently support.
After the item has been placed in a menu, the NAME state can be used by the handler to update the label.
<extension point="org.eclipse.ui.commands"> <extension point="org.eclipse.ui.menus"> <menuCollection location="menu:edit?after=undo.ext"> <item commandId="org.eclipse.ui.examples.menus.targettedUndo" mnemonic="%TargettedUndo.mnemonic" /> </menuCollection> </extension>
As a handler becomes active and implement IObjectWithState (for example, derives from AbstractHandlerWithState) it is notified about any states that the handler's command contains. The states can be updated at that time.
class FileDeleteUndoHandler extends AbstractHandlerWithState { public final void handleStateChange(final State state, final Object oldValue) { if (INamedHandleStateIds.NAME.equals(state.getId()) && oldValue == null) { state.setValue("Undo File Delete"); } } public final Object execute(final ExecutionEvent event) { // undo a file delete using EFS ... very cool return null; } }
Programmatically we have to take the editor action handlers into account.
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.
Our service hierarchy has 2 levels, IWorkbenchWindow and IPartSite. Handlers registered with IWorkbenchWindow are active as long as the window is active, and are disposed when the window is disposed. Handlers registered with IPartSite are active and available as long as that specific part is active and available. When the part is closed, the handlers are disposed. The menus contributed through the IPartSite would also potentially be disposed when the part is closed, although not necessarily.
Two possibilities are:
1. Provide a service locator for the in-between case. Tied to a part type. The service locator expression makes the activation correct and the service locator disposes of them correctly. Basically, we would make IActionBars returned from each IPartSite a service locator as well.
2. Move these actions up to the IWorkbenchWindow level. With the correct expression the handler activation would be correct. But once created they would live as long as the workbench.
In Add ProblemView menus we added 2 dynamic menus. You then have to implement IDynamicMenu in your provided class.
<menu id="org.eclipse.ui.views.problems.filters.menu" label="%ProblemView.Filters.label" mnemonic="%ProblemView.Filters.mnemonic"> <dynamic class="org.eclipse.ui.views.markers.internal.FilterMenu" /> </menu>
When your menu is about to show, you should get the callback:
class FilterMenu implements IDynamicMenu { public void aboutToShow(Collection menuCollection) { menuCollection.clear(); String[] filterIds = getCurrentFilters(); for (int i = 0; i < filterIds.length; i++) { String id = filterIds[i]; ItemDesc item = new ItemDesc(getParameterizedCommand(id)); item.setLabel(getFilterName(id)); menuCollection.add(item); } } public String[] getCurrentFilters() { return new String[0]; } public ParameterizedCommand getParameterizedCommand(String filterId) { // return the toggle filter state command tied to the filter id return null; } public String getFilterName(String id) { return null; } }
Dynamic Previous Searches editors group
You can have a group dynamically insert elements as a menu is shown.
<extension point="org.eclipse.ui.menus"> <group groupId="com.example.registry.search.editors.menu" separatorsVisible="true"> <dynamic class="com.example.registry.search.RecentSearchEditors"/> <location type="menu"> <menu id="file"/> <order after="mru"/> </location> </group> </extension>
When the File menu is about to show, you will be called with your group IMenuCollection (not the entire File menu). But basically, it looks the same as the dynamic menu case.
public class RecentSearchEditors implements IDynamicMenu { public void aboutToShow(IMenuCollection menu) { menu.clear(); String[] editorIds = getSearchVew().getPreviousOpenEditors(); Command openCmd = commandServ.getCommand("com.example.registry.search.openEditor"); SLocation fileLocation = new SLocation(SBar.MENU, "file"); for (int i=0; i<editorIds.length && i<4; i++) { SItem item = menuServ.getItem(new SLocation(SBar.MENU, "file/searchOpenEditor" + i)); item.undefine(); ParameterizedCommand parmOpenCmd = ....; // openCmd + the editorsId parameter item.define(parmOpenCmd, fileLocation); menu.add(item); } } public SearchViewPart getSearchView() { return ....; } }
Example Matrix
Example | Location | visible when | enabled when | defined by | placed by | handled by | comments |
---|---|---|---|---|---|---|---|
Add ProblemView menus | view menu | always | always | ViewPart & IActionBars | ViewPart & IActionBars | SampleViewAction |