Contributing to the platform's menus and toolbars

In the example that we used to illustrate how to declare and implement an extension point, we created a plugin that adds a button to the Eclipse toolbar. Now, let's take a more careful look at how this button was added there, as well as find out how we could add elements to other parts of the Eclipse interface.

Actions versus commands

The first thing we should know is that there are two different ways of contributing to menus in the Eclipse UI: actions and commands. The action way that we used in the previous example might look simpler at first, but simplicity has a price here; as you can see, logic and interface are closely tied, which might lead to code replication if you want to add, for example, a right-click menu entry that performs the same action as the toolbar button. As you know, code replication is a project's nightmare and should be avoided at all costs. For this reason, usage of actions is discouraged in detriment of commands.

The Command framework solves this issue by completely decoupling handling from interface. It requires implementations for three extension points: org.eclipse.ui.menus, org.eclipse.ui.commands, and org.eclipse.ui.handlers. The menus' extension point is responsible for adding the interface element to the platform. The commands extension point declare commands, which are implemented by handlers.

To show how to use the commands framework, let's implement the same hello world toolbar button that we've just implemented, but now using commands instead of actions. Create a new HelloWorldCommand plugin project without using any templates to start.

org.eclipse.ui.menus

As we already discussed, this extension point is used to add user-interface contributions to Eclipse, and it replaces a number of extension points from the action framework, such as org.eclipse.ui.ActionSets, org.eclipse.ui.EditorActions, and org.eclipse.ui.popupMenus.

Add an implementation of this extension point and a menuContribution element with the following parameters:

  • locationURI: toolbar:org.eclipse.ui.workbench.file?after=additions
  • allPopups: false

LocationURI specifies where in the Eclipse UI the contribution will be added. In this case, it will appear right next to the Print button. In the next section, we'll learn how to discover locationURIs using the plug-in spy.

If the allPopups Boolean value is set to false, this menu contribution will only be added to context menus that include a marker called "additions".

org.eclipse.ui.commands

The commands extension point declares commands that will be tied to different menu entries. Add an extension to the command extension point and a command element inside it with the following properties:

  • id: com.packt.helloworldcommand.command
  • name: helloWorldCommand

org.eclipse.ui.handlers

This is where the command is actually implemented. This is done by creating a class that extends org.eclipse.core.commands.AbstractHandler. So, before adding the implementation for the handlers extension point, let's create a class called DefaultHelloWorldCommandHandler with the following code:

package com.packt.helloworldcommand;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

public class DefaultHelloWorldCommandHandler extends AbstractHandler {

    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        MessageDialog.openInformation(shell, "Hello", "Hello World!");
          return null;
    }

}

In the preceding code, the only method you have to implement is execute, which is obviously the one that will be called when the command is executed.

Now that we have the handler implemented, let's reference to it in the Extensions tab. Create an implementation for org.eclipse.ui.handlers and add a handler element containing the following properties:

  • commandId: com.packt.helloworldcommand.command
  • class: com.packt.helloworldcommand.DefaultHelloWorldCommandHandler

We have implementations for the three extension points, but note that there's nothing tying up the menu entry with the commands or handlers. This is achieved by adding a "command" element to the menuContribution element in the org.eclipse.ui.menus extension points. Create one with these parameters:

  • commandId: com.packt.helloworldcommand.command
  • label: click me

Your Extensions tab should look like the following screenshot:

org.eclipse.ui.handlers

Now let's see how our plugin behaves by running it. You should see a button with click me written on it right beside the Print button. Click on it to be greeted with yet another hello world message. If you want an icon instead of a text in the toolbar, navigate to org.eclipse.ui.menus | menuContribution | command element to edit it and change the icon parameter.

Since the whole point of the command framework is to allow adding the same command to multiple UI contributions easily, let's test this flexibility by adding an entry to the menu that pops up when the right-mouse button is pressed. All we really need is another menuContribution element in org.eclipse.ui.menus. So go ahead and create another one, full with the command child element, changing only the locationURI property to popup:org.eclipse.ui.popup.any?after=additions.

Run the plugin again, and right-click on the Package Explorer in the runtime workbench UI. You should see an entry as follows:

org.eclipse.ui.handlers

Clicking on it has the same result as clicking the toolbar button.

Restricting and disabling contributions

You might have noticed that the click me entry in the right-click menus is not restricted to the Package Explorer. Right-click other views, such as Java Editor or Outline, and you will see that the entry is everywhere. This is probably not the desired behavior; most of the menu contributions apply to some context only, and leaving the entry visible or enabled where they don't apply will only add clutter to the Eclipse interface.

Fortunately, there's an easy way of hiding the menu contribution. The command element under menuContribution haves an optional element called visibleWhen. As the name implies, it allows you to determine under which conditions that command will be enabled.

Let's restrict the right-click menu entry visibility to appear only in text editors. There are two ways of doing this. One is implementing the isEnabled() method in the AbstractHandler extension registered for this command (DefaultHelloWorldCommandHandler in this case). The return value of the method's execution will determine if the contribution is visible or not. The other way is by adding child elements to the visibleWhen element in order to build an expression that is to be evaluated to determine the element's visibility. We'll go for the second option in this example.

Start by adding the visibleWhen element. The checkEnabled property in the element determines which of the two methods of restricting visibility will be used. If true, isEnabled() method will be evaluated, and any child element of visibleWhen will be ignored. If false, child elements will be used. Set this property to false.

There are many types of valid child elements to the visibleWhen element. The with element is utilized to evaluate a variable. These variables are provided by the extension points and the Eclipse framework. A comprehensive list of the variables provided by Eclipse can be found at the Eclipse wiki page in this link: http://wiki.eclipse.org/Command_Core_Expressions#Variables_and_the_Command_Framework. Some examples of such variables are activeEditor, which contains the currently active editor, activeWorkbenchWindow, which contains the currently active workbench window, and activePart, which contains the currently active part. A part is a visual component within the workbench that can be either a view or an editor. Since we want our contribution to be visible in editors only, we'll evaluate the activePart variable. So go ahead and add a with element, and change it's variable value to activePart.

We can add now other elements under the with element to evaluate the activePart variable. activePart contains the instance of the the current active part in the workbench, so to verify if it is a text editor or not, we have to check which classes this object instantiates. This is done with the instanceof element. Let's add it and provide org.eclipse.ui.texteditor.AbstractTextEditor to the value parameter. For this to work, however, we must have access to the org.eclipse.ui.texteditor package, which belongs to the org.eclipse.ui.editors plug-in. Switch to the Dependencies tab and add the plugin to the dependencies list. Now switch back to the Extensions tab and you should be able to find the AbstractTextEditor class by clicking on the Browse button.

Notice that there are many other child elements to visibleWhen, such as "and" and "or", which allow you to compose different conditions. The systemTest element can be used to test system properties, such as osgi.os, that contains the operating system on which Eclipse is running (Win32, Linux, AIX, Solaris, and so on), osgi.ws, which contains the current windowing system (Win32, Motif, GTK, and so on) and osgi.arch, which contains the architecture on which the platform is running (x86, x86_64, PPC, SPARC, and so on).

The following screenshot shows how your org.eclipse.ui.menus extension should look:

Restricting and disabling contributions

Now, run your project, and try right-clicking on the runtime workbench views. Only text editors will show the click me entry.

Menu entries can also be disabled instead of hidden. Take the copy entry in a text editor as an example; it's grayed out and un-clickable when there's no text selected, although it behaves normally when there's text selected. Let's add that same behavior to our menu entry to exemplify how it's done.

Entries will be disabled when there's no handler registered for the current situation. Since we haven't conditioned our handler to anything, it's always enabled. Similar to the visibility of menuContribution, there are two ways of limiting a handler's activation. You can do it by adding the enabledWhen child element to the handler element, and using the same child elements as the ones used in visibleWhen (with, and, or, instanceof, and so on), assembling an expression that will be evaluated to determine if the handler is active or not. You can alternatively implement isEnabled(), returning true or false accordingly. Since we have already shown an example of how to do it using the child elements in the Extensions tab, let's override the isEnabled() method this time.

The following code is an example of how to override isEnabled() in order to restrict the handler to when there's code selected in a Text Editor:

@Override
    public boolean isEnabled() {
        boolean enabled = true;
        IWorkbenchPart activePart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getPartService().getActivePart();
        if(activePart instanceof IEditorPart){
                ISelection selection = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().getSelection();
                if(selection instanceof ITextSelection){
                       if (((ITextSelection) selection).getText().isEmpty()){
                                enabled = false;
                       }
                }
        }
        return enabled;
    }

The PlatformUI class is the entry point to the Eclipse user interface. We use its static method getWorkbench(), and then the getActiveWorkbenchWindow() method from the workbench object to get access to PartService, which allows us to get the currently active part in the workbench. If the activePart variable happens to be an instance of IEditorPart, we get its selection service, that gives us access to what is currently selected in the editor. If the selection happens to be an instance of ITextSelection, we verify its content. If the selection is empty, the enabled value is set to false. Note that ITextSelection belongs to the org.eclipse.jface.text plugin, so this code won't work if this plugin is not specified in the required plugins section from the Dependencies tab.

Add this code to the DefaultHelloWorldCommand class and run the project. The following screenshot shows the popup menu that you should get if you right-click your Java editor with no code selected:

Restricting and disabling contributions

The Plugin Spy feature

You might have wondered where did the locationURIs we used for our contributions came from. As we promised, we'll now show how to use the Plugin Spy, a very useful feature for Eclipse developers, to find locationURIs and much more.

This feature allows you to obtain information from the UI elements of your workbench straight from the Eclipse interface, thus making it easy to find out from which plugin does that element come, its class, the current selection's type, among other useful information. The Plugin spy has two modes: the Plugin Selection Spy and the Plugin Menu Spy. As the names imply, the Selection Spy allows you to get information regarding the currently selected element and its containing part, but the Menu Spy provides information about menu contributions.

To enable the Selection Spy, press Alt + Shift + F1. You will be presented with a pop-up window containing information similar to this one:

The Plugin Spy feature

The preceding screenshot is the output of the Plug-in Selection Spy with Java Editor as the Active Part. As you can see, it contains the class of the active editor, the plugin that provides this part, the part's identifier, and the menu contribution identifiers. In the previous example of how to disable menu entries, we have implemented the isEnabled() method using information gathered from the plugin selection spy: We know that the Java editor class is CompilationUnitEditor and the text selection is an instance of TextSelection. If we didn't have the plugin selection spy, we would have to scavenge for this information in the Java Development Tool's code or documentation.

The other Plugin Spy mode is the Plugin Menu Spy. It allows you to get info from menu entries instead of views and selections. To enable this mode, press Alt + Shift + F2. You will notice that your mouse cursor will have a different look. Let's click on in the Run toolbar button, for example, to see how the Plugin Menu Spy output looks as follows:

The Plugin Spy feature

As you can see, it gives us the active contribution item identifier, the location URI of the contribution, and the action set identifier. This field can either point to an action or a command, since the action framework, as we have discussed previously in this chapter, is deprecated but there's still a lot of code that hasn't been ported to the command framework.

The Plugin Menu Spy can also be used to get info from pop-up menus, such as right-click context menus and toolbar drop-down menus.

So now you know from where did we get the locationURI to use in the click me menu entry example. Since the Plugin Menu Spy also gives us the action or the command that actually implements the menu entry, it allows us to re-use them by adding new menu contributions that use these actions or commands when required.

To exit the Plugin Menu Spy mode, press Esc.

Creating new views

When creating an Eclipse plugin, you might need to create a whole new view instead of extending the current ones. Like almost everything in the Eclipse platform, this is achieved by implementing extension points.

The org.eclipse.ui.views is the most important extension point regarding views. There are three elements in this extension point:

  • View: Describes the view itself. It contains information regarding the view's class, category, icon, and name
  • Category: If the view you're creating doesn't belong to any of the existing categories , you can create a new one using this element. You can see all the existing views and its categories by selecting Window | Show View | Other.
  • StickyView: Use this element if you want to create a view that will be shown in all perspectives by default.

The class required by the view element must be one that implements the org.eclipse.ui.part.IViewPart interface. It's more recommended, however, to extend the ViewPart abstract class, which is a base implementation of IViewPart that makes the task of implementing IViewPart easier. It leaves two methods to be implemented:

  • createPartControl(Composite parent): This is the method that's called when the view is created. According to the method comments of IViewPart, the following actions must be taken in implementations:
    • Create one or more controls within the parent
    • Set the parent layout as needed
    • Register any global actions with the site's IActionBars
    • Register any context menus with the site
    • Register a selection provider with the site, to make it available to the workbench's ISelectionService (optional)
  • setFocus(): This method is called when focus is set in your view. Code in this method should focus one of the elements inside the view.

Basically, every SWT element presented in Chapter 5, SWT, and Chapter 6, More SWT, can be used to compose a view, as well as your own custom composites.

Saving the view's current state

It's important for most of the views to be able to save its state so that it can be retrieved when Eclipse is restarted. There is a saveState(IMemento memento) method in IViewPart that must be implemented in order to achieve that. If you take a look at ViewPart's implementation, though, you will be disappointed:

    public void saveState(IMemento memento) {
        // do nothing
    }

If you want your view to be capable of saving its own state, you have to override this method. Let's see how to do this.

We can see that the method receives a IMemento object as a parameter. This is the object we have to update in order to save the view's state. From the IMemento's JavaDoc, mementos are objects to which you can assign a "mapping of arbitrary string keys to primitive values, and by allowing mementos to have other mementos as children (arranged into a tree)". This mapping is created by calling the put<type>(String key, <type> value) methods, such as putFloat, putInteger, putString, and so on. Child mementos are created with the createChild method.

Once you have accordingly implemented the saveState method, Eclipse guarantees that the information stored in the memento object will be available across different Eclipse sessions, using an xml-based storage format under the hood. However, we still have to tell Eclipse how to use this information to recompose the view's state. This is done by overriding the init(IViewSite site, IMemento memento) method, since the ViewPart implementation simply ignores the memento object and delegates to init(IViewSite site). As a general rule, information from the memento is only saved in the current object, leaving the task of assigning the values back to the SWT elements to the createPartControl method. This happens because the platform calls init before createPartConrol.

Adding context help to your view

The easiest way of getting help in Eclipse is through the Dynamic Help, which can be activated in Help | Dynamic Help. This opens a Help view which contains basic information and usage guidance for the currently focused view. Each view provides the help content, and the Eclipse platform takes care of assembling the help view.

To provide help content, plugins must implement the org.eclipse.help.contexts extension point. The implementation must provide a contexts element, which points to a Context Help file in the XML format. Instead of creating it from scratch and editing manually, you can use the Context Help editor. To do so, create the XML file by selecting File | New | Other, and then browsing to User Assistance | Context Help. Provide the filename and location in the next page, and click on Finish. You can add topics and commands to the context. Topics point to HTML files that will be rendered in the Dynamic Help view.

Example of a new view

To illustrate what we have seen about creating a new view so far, let's create a very simple one containing just a label and a text field. Let's begin by extending ViewPart:

package sampleview.views;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.ViewPart;

public class SampleView extends ViewPart {

    public static final String ID = "sampleview.views.SampleView";

    private Label label;
    private Text text;
    private IMemento memento;

    public SampleView() {
    }

    public void createPartControl(Composite parent) {
            parent.setLayout(new GridLayout(2, false));
                label = new Label(parent, SWT.NONE);
                label.setText("Write down something here:");

                text = new Text(parent, SWT.BORDER);
                text.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
                if(memento != null){
                      if(memento.getString("content") != null){
                       text.setText(memento.getString("content"));
                       }
                }
    }

    public void setFocus() {
            text.setFocus();
    }

    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        this.memento = memento;
        init(site);
    }

    @Override
    public void saveState(IMemento memento) {
        memento.putString("content", text.getText());
    }
}

Now that we have the IViewPart implementation, let's modify plugin.xml in order to register the view in the Eclipse platform. The following screenshot shows how our Extensions tab of the plugin manifest editor looks like after registering the view:

Example of a new view

Now run the project. Open the view that we just created by selecting Window | Show View | Other or by using the Ctrl + 3 shortcut and typing the view's name. Our view should look like this:

Example of a new view

Try writing something in the text field and restarting the runtime workbench. Note that you must close the Eclipse instance instead of clicking the Terminate button (red stop button) in your development environment. The Terminate button simply kills the instance, giving no time for the platform to save your view's state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset