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.
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.
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:
toolbar:org.eclipse.ui.workbench.file?after=additions
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".
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:
com.packt.helloworldcommand.command
helloWorldCommand
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:
com.packt.helloworldcommand.command
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:
com.packt.helloworldcommand.command
click me
Your Extensions tab should look like the following screenshot:
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:
Clicking on it has the same result as clicking the toolbar button.
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:
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:
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 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:
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.
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:
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: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.
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
.
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.
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:
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:
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.