6.3. Object ActionsSuppose you want to make it easy for the user to add files and folders to the Favorites view. Object contributions are ideal for this because they appear in context menus only when the selection in the current view or editor contains an object compatible with that action (see Figure 6-8). In this manner, an object contribution is available to the user when he or she needs the action, yet not intrusive when the action does not apply. Figure 6-8. Object action.6.3.1. Defining an object-based actionAs in Section 6.2.1, Defining a workbench window menu, on page 209 and subsequent sections, use the Extensions page of the plug-in manifest editor to create the new object contribution. Click on the Add button to add an org.eclipse.ui.popupMenus extension, then add an objectContribution with the following attributes:
Next, add an action to the new objectContribution with the following attribute values, which are very similar to the action attributes covered in Section 6.2.3, Defining a menu item and toolbar button, on page 212.
Multiple actions appear in reverse order Buried in the org.eclipse.ui.popupMenu extension point documentation is the following nugget of information: "If two or more actions are contributed to a menu by a single extension, the actions will appear in the reverse order of how they are listed in the plugin.xml file. This behavior is admittedly unintuitive. However, it was discovered after the Eclipse Platform API was frozen. Changing the behavior now would break every plug-in that relies on the existing behavior." Other available action attributes not used in this example include:
6.3.2. Action filtering and enablementIn keeping with the lazy loading plug-in theme, Eclipse provides multiple declarative mechanisms for filtering actions based on the context, and enabling visible actions only when appropriate. Because they are declared in the plug-in manifest, these mechanisms have the advantage that they do not require the plug-in to be loaded for Eclipse to use them. 6.3.2.1. Basic filtering and enablementIn Section 6.3.1, Defining an object-based action, on page 224, the nameFilter and objectClass attributes are examples of filters, while the enablesFor attribute determines when an action will be enabled. When the context menu is activated, if a selection does not contain objects with names that match the wildcard nameFilter or are not of a type specified by the objectClass attribute, none of the actions defined in that object contribution will appear in the context menu. In addition, the enablesFor attribute uses the syntax in Table 6-1 to define exactly how many objects need to be selected for a particular action to be enabled: The techniques listed in this table represent those most commonly used for limiting visibility and the enablement of actions; occasionally, a more refined approach is needed. The visibility and filter elements provide an additional means to limit an action's visibility, while the selection and enablement elements provide a more flexible way to specify when an action is enabled. Still further refinement of action enablement can be provided by using the selection Changed() method in the action delegate, as discussed in Section 6.2.6, Creating an action delegate, on page 216. 6.3.2.2. The visibility elementThe visibility element provides an alternate and more powerful way to specify when an object contribution's actions will be available to the user as compared with the object contribution's nameFilter and objectClass. For example, an alternate way to specify filtering for the object contribution just described would be: <objectContribution ... <visibility> <objectClass name="org.eclipse.core.resources.IResource"/> </visibility> ...the other stuff here... </objectContribution> If the action is to be visible only for resources that are not read-only, then the visibility object contribution might look like this: <objectContribution ...> <visibility> <and> <objectClass name="org.eclipse.core.resources.IResource"/> <objectState name="readOnly" value="false"/> </and> </visibility> ... the other stuff here ... </objectContribution> As part of the <visibility> element declaration, you can use nested <and>, <or>, and <not> elements for logical expressions, plus the following Boolean expressions.
6.3.2.3. The filter elementThe filter element is a simpler form of the objectState element discussed previously. For example, if the object contribution was to be available for any file that is not read-only, then the object contribution could be expressed like this: <objectContribution ...>
<filter name="readOnly" value="false"/>
... the other stuff here ...
</objectContribution>
As with the objectState element, the filter element uses the IActionFilter interface to determine whether an object in the selection matches the criteria. Every selected object must either implement or adapt to the IActionFilter interface (more on adapters in Chapter 20, Advanced Topics) and implement the appropriate behavior in the testAttribute() method to test the specified name/value pair against the state of the specified object. For resources, Eclipse provides the following built-in state comparisons as listed in the org.eclipse.ui.IResourceActionFilter class:
6.3.2.4. The selection elementThe selection element is a technique for enabling an individual action based on its name and type, similar to the way that the nameFilter and objectClass attributes determine whether all actions in an object contribution are visible. For example, an alternate form for the object contribution using the selection element would be: <objectContribution objectClass="java.lang.Object" id="com.qualityeclipse.favorites.popupMenu"> <action label="Add to Favorites" tooltip="Add the selected resource(s) to the Favorites view" class="com.qualityeclipse.favorites.actions. AddToFavoritesActionDelegate" menubarPath="additions" enablesFor="+" id="com.qualityeclipse.favorites.addToFavorites"> <selection class="org.eclipse.core.resources.IResource" name="*.java"/> </action> </objectContribution> With this declaration, the object contribution's actions would always be visible, but the Add to Favorites action would only be enabled if the selection contained only implementers of IResource that matched the name filter *.java. 6.3.2.5. The enablement elementThe enablement element is a more powerful alternative to the selection element, supporting the same complex conditional logic expressions and comparisons as the visibility element (see Section 6.3.2.2, The visibility element, on page 228). For example, an alternate object contribution declaration to the one outlined in the previous section, but that produces the same behavior would be: <objectContribution objectClass="java.lang.Object" id="com.qualityeclipse.favorites.popupMenu"> <action label="Add to Favorites" tooltip="Add the selected resource(s) to the Favorites view" class="com.qualityeclipse.favorites.actions. AddToFavoritesActionDelegate" menubarPath="additions" enablesFor="+" id="com.qualityeclipse.favorites.addToFavorites"> <enablement> <and> <objectClass name="org.eclipse.core.resources.IResource"/> <objectState name="name" value="*.java"/> </and> </enablement> </action> </objectContribution> 6.3.2.6. Content-sensitive object contributionsThere is a new mechanism for filtering actions based on resource content. This filtering is specified in the plug-in manifest (does not load your plug-in) and determines whether an action should be visible or enabled by inspecting a file's content. For example, the Run Ant... command is associated with resources named build.xml, but no others; what if your Ant script is located in a file called export.xml? This new mechanism can determine whether the Run Ant... command should be visible based on the first XML tag or DTD specified in the file. In this case, the org.eclipse.ant.core plug-in defines a new antBuildFile content type: <extension point="org.eclipse.core.runtime.contentTypes"> <content-type id="antBuildFile" name="%antBuildFileContentType.name" base-type="org.eclipse.core.runtime.xml" file-names="build.xml" file-extensions="macrodef,ent,xml" priority="normal"> <describer class="org.eclipse.ant.internal.core. contentDescriber.AntBuildfileContentDescriber"> </describer> </content-type> </extension> The preceding declaration associates the antBuildFile content type with the AntBuildfileContentDescriber class, which determines whether XML content is Ant content. The antBuildFile content type can then be used to specify action visibility and enablement, editor association, and more. For more about declaring and using your own content types, see the following:
6.3.3. IObjectActionDelegateGetting back to the Favorites plug-in, the next task is to create an action delegate that implements the IObjectActionDelegate interface, which performs the operation behind the new Add to Favorites menu item. Create a new AddToFavoritesActionDelegate class as described next. Since the Favorites view is not fully functional, the action displays a message rather than adding the selected items to the view (see Section 7.3.1, Model actions, on page 283 for more implementation details). Start by selecting the action defined in Section 6.3.1, Defining an object-based action, on page 224 and then clicking on the class: label to the left of the class field. This opens the Java Attribute Editor dialog for creating a new Java class. Fill in the package and class name fields as necessary and be sure to add IObjectActionDelegate as the interface to implement, then click Finish to generate the new class. Next, add a new field and modify the setActivePart() method to cache the view or editor in which the action appears: private IWorkbenchPart targetPart; public void setActivePart(IAction action, IWorkbenchPart part) { this.targetPart = part; } Finally, modify the run() method to open a message dialog indicating that this action was successfully executed. As mentioned before, this action delegate will be fleshed out in Section 7.3.1, Model actions, on page 283. public void run(IAction action) { MessageDialog.openInformation( targetPart.getSite().getShell(), "Add to Favorites", "Triggered the " + getClass().getName() + " action"); } 6.3.4. Creating an object-based submenuMenus can be contributed to a context menu in a manner similar to adding actions. If three or more similar actions are contributed, then think about placing those actions in a submenu rather than in the context menu itself. The Favorites plug-in only adds one action to the context menu, but let's place the action in a submenu rather than in the context menu itself. To create the Favorites menu, right-click on the com.qualityeclipse.- favorites.popupMenu object contribution in the Extensions page of the plug-in manifest editor, and select New > menu. Enter the following values for this new menu:
Next, add a groupMarker to the menu with the name "content" and a separator with the name "additions" (see Section 6.2.2, Groups in a menu, on page 212). Finally, modify the Add to Favorites action's attributes as follows so that the action will now be part of the new Favorites submenu:
6.3.5. Manually testing the new actionWhen the Favorites Runtime Workbench configuration is launched (see Section 2.6, Debugging the Product, on page 88), any context menu activated on a workbench resource will contain the Favorites menu with the Add submenu item. Selecting this submenu item displays a message box notifying you that the action was indeed triggered correctly. The Favorites action is only displayed when one or more objects are selected due to enablesFor="+" in the declaration shown in Section 6.3.2.5, The enablement element, on page 231. This means that when you test the Favorites menu, you must have at least one project created in your runtime workbench and select at least one resource in the Navigator view when you activate the context menu. If you right click without selecting anything, you will only see an abbreviated context menu that does not have the Favorites menu. 6.3.6. Adding a test for the new actionThe last task is to create an automated test that triggers the action and validates the result. Because this operation displays a message rather than adding a resource to the Favorites view, the code that validates the results of this test will have to wait until the next chapter (see Section 7.6, Testing, on page 314), where the Favorites view will be more fully developed. For now, create the following new test case in the Favorites test project and then modify the Favorites test suite to include this new test (see Section 6.2.8, Adding a test for the new action, on page 220). You can begin by creating a new AddToFavoritesTest class that extends AbstractFavoritesTest and adds it to the Favorites test suite. package com.qualityeclipse.favorites.test; import ... public class AddToFavoritesTest extends AbstractFavoritesTest { public AddToFavoritesTest(String name) { super(name); } Next add a field and override the setUp() method to create a temporary project called "TestProj" for the duration of this test. The tearDown() method deletes this temporary project when the test is complete. To get these changes to properly compile, edit the Favorites test project's plug-in manifest and add the org.eclipse.core.resources plug-in to the Required Plug-ins list (see Figure 2-10 on page 73). protected IProject project; protected void setUp() throws Exception { super.setUp(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); project = root.getProject("TestProj"); project.create(null); project.open(null); } protected void tearDown() throws Exception { super.tearDown(); // Wait for a bit for the system to catch up // so that the delete operation does not collide // with any background tasks. delay(3000); waitForJobs(); project.delete(true, true, null); } Finally, add the method that exercises the new menu item for adding objects to the Favorites view. public void testAddToFavorites() throws CoreException { // Show the resource navigator and select the project. IViewPart navigator = PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getActivePage().showView( "org.eclipse.ui.views.ResourceNavigator"); StructuredSelection selection = new StructuredSelection(project); ((ISetSelectionTarget) navigator).selectReveal(selection); // Execute the action. final IObjectActionDelegate delegate = new AddToFavoritesActionDelegate(); IAction action = new Action("Test Add to Favorites") { public void run() { delegate.run(this); } }; delegate.setActivePart(action, navigator); delegate.selectionChanged(action, selection); action.run(); // Add code here at a later time to verify that the // Add to Favorites action correctly added the // appropriate values to the Favorites view. } |