Java Programming the Eclipse Workbench

Programming the Eclipse Workbench

What makes Eclipse applications most easily recognizable is the
Workbench. It is the foundation of applications such as the Eclipse IDE
— the main window contains menus, toolbars, and a variety of smaller
windows, some tiled, some stacked behind one another. What makes each
Workbench application unique is what these windows are and what
functionality they provide.

In the previous installment of the Eclipse series an Eclipse
Workbench "view" was used as the host of a JFace and SWT components,
but I did not go into much detail about its structure or function. In
this article, the Eclipse Workbench will be explored in more detail.
Specifically, the Workbench "parts" — the views
and editors that make up most Workbench applications — will be reviewed.

Introduction

When you run a typical Eclipse Workbench application, such as your
Eclipse IDE, you get the familiar application window with menu and
toolbars at the top, status bar at the bottom, and one or more views in
between. Many of these views allow you to manipulate the information
they present, and perhaps edit it in specialized editors. While some
tasks may be implemented as step-by-step wizards, the telltale sign of a
workbench application is the ability to view and manipulate information
in a variety of ways.

In this article, a simple editor is developed that lets you edit
lists of strings stored in plain-text files. A JFace viewer is used to
display the model. Menus are provided to allow for the addition of new entries
into the list and deletion of selected entries. Also provided is support
for undoing, redoing, and saving of these modifications.

To compile and run the example, you will need
Eclipse SDK 3.1, available at http://www.eclipse.org/downloads.
Some familiarity with the Plug-in Development Environment, JFace, and
SWT is required.

Eclipse Workbench

From a user’s point of view, the Workbench is a top-level application
window (or several of them, in fact) with menus, toolbars, views, and
editors. While this is by no means a requirement, many users have
actually come to expect certain menus and views to be available in
Workbench applications; for example, there is usually a File menu that
allows for creation of new and opening of existing files, an Edit menu
with editing actions such as Cut, Copy, and Paste, as well as a Window
menu for customizing some aspects of the user interface.

From a developer’s perspective, the Workbench serves as the
foundation for applications that contribute their views, editors,
wizards, and other components as plug-ins. You can look at the Workbench
as the infrastructure that you can use to build a certain class of
desktop applications. In fact, Eclipse started out as an IDE workbench
suitable for developing integrated development tools, but over the years
it evolved into a generic platform for Rich Client Applications (RCP).
The central architectural element of these applications is precisely the
Workbench (also called the Generic Workbench to differentiate it from
the IDE).

Programmatically, the Workbench is represented by interface IWorkbench,
which can be obtained as a singleton instance by calling PlatformUI.getWorkbench().
The Workbench provides a variety of services, most of which we won’t
address here. It also provides access to its visual components, starting
with its windows; while there may be more than one window (represented
by interface IWorkbenchWindow) in a Workbench application,
at most one can be active at any given time. Each window consists of
multiple pages (IWorkbenchPage; although having more than
one page is rare), of which only one may be active at any given time.

At this point you may be tempted to think that switching
perspectives is equivalent to flipping the window’s pages. This,
however, is not the case. Programmatically, a perspective just defines
the page layout (which is, by the way, fully customizable by the user).
When you switch perspectives, you actually apply a different layout (and
a corresponding set of views) to the same page.

Finally, each workbench page consists of several “parts” — editors
or views (and again, only one may be active at a time).

Each of these interfaces provides access to components and services
pertinent to their level within the hierarchy. For instance, IWorkbenchPage
allows you to show views, open editors, and manipulate the current
perspective, while IWorkenchWindow lets you work with its
pages, and IWorbench with windows and other Workbench-wide
services.

This hierarchy stops at IWorkbenchPart, because this is
where customization comes to play: each view or editor implements its
own user interface and provides its own services.

Several views are considered “standard” — Properties and
Content Outline in the Generic Workbench, Problems, Tasks, Bookmarks,
Resource Navigator, and some others, in the IDE. Many of these expose
public APIs that you can peruse in your own solutions.

Workbench Parts


Fig. 1: Eclipse Workbench Parts and Action Bars.

While editors and views perform different functions within the
Workbench, they have much in common — they are "parts" of the
Workbench, and as such they must follow a certain protocol common to all
Workbench parts. This protocol is embodied in interface IWorkbenchPart,
which all Workbench parts must implement.

While developing your own views or editors, you will notice
that instead of directly implementing the corresponding interface, you
are asked to extend an abstract base class (ViewPart for
views and EditorPart for editors, respectively). This
approach is actually quite common throughout Eclipse. It gives the API
providers the flexibility to amend and evolve the API (i.e., the
interface) without inadvertently breaking backward compatibility. For
example, if they decide to add a new method to the interface, then all
existing implementations would break, unless updated. If, however, the
implementations extend an abstract class, then that class can provide
the default implementation of this method, without breaking any existing
subclasses.

In the previous article’s example, the SuperView’s createPartControl(Composite)
method was implemented in order to construct its user interface components. The
method’s argument were used as the parent Composite for the viewer. In fact,
Workbench parts are essentially user interface widgets displayed and
managed by the Workbench in a specific way. Thus, this method is called
by the Workbench at the appropriate time within the part’s lifecycle in
order to create its visual representation. If the part allocates any
platform resources during construction, it must dispose them in its dispose()
method.

Workbench parts are often dynamic in nature and may need to
communicate any relevant state changes to the Workbench. For this
reason, each Workbench part supports a property change notification
protocol — interested parties may add themselves as IPropertyListeners,
which the part then notifies when one of its properties changes. Just
what these properties may be depends on the part type; one common
property shared by both views and editors is their title.

The Workbench supplies each of its parts with instance-specific
services by means of a “site” (IWorkbenchPartSite) — the
primary interface between the part and the Workbench. The way in which
views and editors are supplied with it differs, but what they do have in
common is that both are initialized with a site provided by the
Workbench before their user interfaces may be constructed (views
actually get a view-specific subtype of the site, as do the editors).

Action Bars

When exploring JFace, you learned that user-invokable functionality is
encapsulated in Actions, which are visually represented by
Contributions. In the Workbench, the components that expose
contributions are called Action Bars. They include menus, toolbars,
coolbars (a younger cousin of toolbar), and the status line. There’s
also the notion of “global action handlers”, which give workbench parts
the means to provide their own implementation of certain common actions
(such as Cut, Copy, Paste, etc.) registered globally by the Workbench.
Both views and editors have access to their respective action bars,
though they differ in usage.

Views vs. Editors

As a user, you can probably tell the difference between views and
editors with relative ease. For instance, views are usually there by
default as part of the perspective, while editors are opened from one of
the views. Views are usually stacked around the window’s sides, while
editors take up a more central location, and there is usually only one
instance of the view shown at a time, but potentially multiple instances
of the same editor type. Finally, each view (or a stack of them) gets
its own action bars (toolbar and menu bar), while editors use the main
application menu and toolbar to make contributions (you can see the
action bars changing as you activate different views or editor types).

You’ve already seen that from the programming perspective, views and
editors have a lot in common — both are Workbench parts. However, the
first thing you will notice is that views are initialized differently
than editors. For one, overriding a ViewPart‘s init
method is entirely optional, while the editor’s init method must be
implemented. That is because views are left to figure out on their own
what information to display (e.g., some sort of singleton data source,
such as the Workspace, of Workbench-wide Tasks, etc.), while the editors
get their input from the client code that requests their opening; for
instance, double-clicking a file in the Resource Navigator will open the
appropriate editor with that file as its input. Thus, editors must
accept their input as part of their initialization.

Secondly, editors have a “dirty” state, which is when they may need
to be saved. Saving an editor involves storing its content in the
provided input in such a way that it can be re-opened next time around.
On the other hand, if a view wants to remember any state across its
invocations, it should store them in an IMemento, which is
a simple, hierarchical key/value pair data structure. The Workbench
stores this memento at an internal location and passes it back to the
view during its subsequent initialization.

Lastly, editors and views manage their action bars differently. While
a view gets its own instance of IActionBars (available
through its site) to which it can contribute actions directly, editors
share a single instance of action bars per editor type. This is meant to
make the management of contributions easier, given that each editor
instance may attempt to contribute the same set of actions. Instead,
editors use a helper class called the “action bar contributor”
(implementing IEditorActionBarContributor), which is
declared as part of the editor’s extension. When the first instance of
the editor is created, its action bar contributor is instantiated and
used for any subsequent editor instances. The job of this class is to
contribute actions on the editor’s behalf, and updated them to reflect
the currently active editor (the contributor is notified whenever the
active editor changes).

Building the List Editor

You’ve already developed a view in the previous article, though it
did not involve action contributions. To get a better feel for how
action contributions work, as well as how editors differ from views, you
are going to create a simple editor with several actions contributed by
its action bar contributor.

You start by creating a plug-in project, much like we did last time.
You specify that the plug-in will make contributions to the UI. This
gives the correct plug-in dependencies (org.eclipse.ui,
to be precise).

In the Plug-in Manifest Editor, declare an org.eclipse.ui.editors
extension like this: Click the Extensions tab, then the “Add…” button
in the “All Extensions” section and select “org.eclipse.ui.editors”.
Click “Finish”. In the editor, right-click the extension and choose “New
-> Editor”. Fill out the new element’s “Extension Element Details”
section with your editor’s ID, name, path to an icon (located somewhere
in your plug-in), and extensions (as in “file extensions” — specify
“list” as the only recognized extension). Two remaining properties that
are required in order to proceed are “class” and “contributorClass”. Those
haven’t been created yet, but you can use this editor to help get
started: Clicking the “class” label (it looks like a web link) brings up
a “New Class” dialog, pre-populated with the correct superclass (org.eclipse.ui.part.EditorPart).
All you need to do is specify the name and click “Finish” to create your
editor class.

The first method to implement is init. Before it does
anything else, the editor needs to determine if it can handle the input
provided to it. In our case, the editor requires that the input
implement IPathEditorInput; if it doesn’t, a PartInitException
must be thrown. You proceed by loading the editor’s input into the object
model, which is ultimately what the editor uses (we will take a closer
look at it shortly). Lastly, you set the editor’s site, input (the
protocol requires that we retain it), part name (you set it to the
input’s name, which will usually be the filename), and a user-friendly
tooltip (which contains the full path of the input file).

Next, we implement the two familiar methods — createPartControl
and setFocus. In createPartControl, you instantiate a JFace
ListViewer, which will be sufficient for the purpose of
displaying a simple list of strings. Specify that it support
multi-item selection, which lets the user select and delete multiple
items at the same time. Then configure the viewer with a default
label provider and sorter; however, there isn’t any existing content
provider that “understands” our object model, so you have to implement
your own.

Editor’s Object Model

While the editor’s input is very simple — a list of unique strings
— and could be represented by one of Java’s collection classes, a feature
is required that these classes don’t provide — you want to be
notified whenever the model changes (e.g., elements are added or
removed). Thus, create a simple wrapper equipped with listener/event
mechanism, which will allow any interested party to register themselves
as a listener and be notified (via a custom event) whenever changes are
made to the underlying list.

You would be correct in concluding that you could just as
easily maintain an instance of ArrayList internal to the editor, provide
access to it only via editor’s public methods, and in those methods
track any changes to the list. However, models accompanied by a
listener/event notification mechanism are more flexible (e.g., may be
used outside of an editor context) and their use is wide-spread
throughout Eclipse.

Now, the content provider can be implemented, which both converts the
object model into a list of string elements (suitable for displaying in
the ListViewer) and updates the viewer whenever changes are detected in
the model (this part is accomplished by registering the content provider
as the model’s listener whenever the input changes).

Finishing the Minimal Implementation

A few methods in the editor class remain to be implemented. For
simplicity, the “Save As” functionality is not going to be supported, and
thus false from method isSaveAsAllowed will always be returned.
Saving back to the original input, however, will be supported, and
thus the method doSave(IProgressMonitor) will be implemented.

Its body is rather trivial — write the model’s elements into a flat text file,
each on its own line. However, one important protocol that you need to
support here is the editor’s “dirty” state; recall that an editor is in
this state when it contains unpersisted data that will be lost when the
application closes. Our editor implementation is required to maintain
this state (and provide it via method isDirty), and notify
any interested listeners (via their IPropertyListener
interface) whenever this state changes. When a save is successful, the
editor should no longer be dirty; thus, we should set our internal flag
to false and fire a property change event (with ID PROP_DIRTY)
to indicate the change. The doSave method may only be
called when the editor is in fact dirty, and so we don’t have to check
if the state did in fact change.

Contributing Editor Actions

To implement the action bar contributor for the editor, you must go
back to the Plug-in Manifest Editor and in the extension’s details
section, click the “contributorClass” label (which looks like a
hyperlink). This class instantiates all actions in its constructor and
contributes them to their respective action bars: the Add action is
appended to the top-level Edit menu (since it already has similar Add
actions), and the Delete, Undo, and Redo actions is set as “global
action” handles. What this means is that whenever this contributor is
active, it will provide its own implementations of the global Delete,
Undo, and Redo actions defined by the Workbench and accessible in the
Edit menu as well as through keyboard shortcuts.

Regardless of its particular function, each action class you implement
must be aware of its “target” editor instance; that is, the particular
editor with which it is to interact. The contributor is notified
whenever an editor instance becomes active in its setActiveEditor(IEditorPart)
method; thus, you propagate this notification to each action instance and
allow it to respond accordingly.

Using Operation History

Now that you have the right infrastructure in place, you can implement
each action class. The Add action is the simplest — all it needs to do
is solicit input from the user (for example, using the JFace
InputDialog) and add it to the active editor’s model, if appropriate.
Similarly, the Delete action needs to remove the elements currently
selected in the active editor. However, Delete should be disabled when
there are no selected elements. Thus, this action must be aware of the
active editor’s selection changes. You can accomplish that by exposing
the editor’s viewer selection via the ISelectionProvider
interface and registering the Delete action as a ISelectionChangedListener
on the active editor instance.

While you could implement the above actions to perform their functions
directly on the active editor’s model, you would run into a problem: how
would Undo and Redo be implemented? These two actions don’t provide
any model-specific functionality; all they need to do is undo the last
action, or redo the action that was last undone, whatever it may be.
Ideally, they would be able to undo and redo multiple actions, not just
one.

Because this is a frequently encountered problem in interactive
applications with editing capabilities, the Workbench provides IWorkbenchOperationSupport,
which is a service that allows the application to represent its user
invokable actions in a command-like pattern and use the IOperationHistory
to execute, undo, and redo them. This service is rather flexible and I
won’t go into much detail beyond what’s necessary for this exercise. In
short, the functionality of Add and Delete is encapsulated in the
respective IUndoableOperation, which knows how to execute,
undo, and redo itself. The actions simply execute the operation in the
editor’s undo context and allow the Undo and Redo actions to navigate
the operation history as required. As the last bit, you register the
editor itself as an operation history listener and mark it dirty
whenever there is an unsaved operation in its context.

Test-Running the Editor

To see the results, run the plug-in as an Eclipse Application. When
the runtime workbench appears, switch to the Resource Perspective
(though you can use any other perspective with a view that allows you to
manipulate your Workspace). Create a new, simple project, and in it a
simple file with extension “.list”. This will cause a new, empty file to
be created and opened in the List Editor we just developed. You should
now be able to add elements using the “Add List Element…” action in
the main Edit menu, and delete them (when selected) using the Del key.
You can also save and re-open the modified files.

One oddity you may notice is that the Content Outline view,
usually present in most perspectives by default, displays a message
stating that the outline is not available. As its name indicates, this
view is used to present a concise outline of the active editor’s
contents. In order to do this, the editor must provide a special adapter
whose job it is to display an editor-specific outline. We did not
develop one in this article, mostly because our content model cannot get
any simpler than a list, and thus the outline itself would have to be
nothing more than a list. For more information about this feature, see
class ContentOutline.

Conclusion

The Eclipse Workbench serves as a reusable foundation for developing
desktop applications. It consists mainly of views and editors, commonly
referred to as Workbench Parts. Views and editors expose their
functionality as actions, which they contribute to their respective
action bars. As an example, you were shown how to develop a simple editor for
manipulating a list of elements, with support for global actions and
undoable operations.

Resources

About the Author

Peter Nehrer is an independent software consultant living in Toronto, Ontario. He specializes in Eclipse-based enterprise solutions and J2EE applications. His professional interests include development tools, model-driven software development, and information sharing. He is the author and contributor to several Eclipse-related Open Source projects. Peter is an IBM Certified Solutons Developer for XML and related technologies. He holds an M.S. in Computer Science from the University of Massachusetts at Amherst, MA.

Latest Posts

Related Stories