Eclipse Corner Article

Editor Contributors - A Distinct Species

Why are Eclipse desktop editors using a distinct contribution mechanism instead of simply doing it directly from editors

Summary
Eclipse platform desktop offers various ways a plugin can contribute to menus and tool bars. The most natural way for views is to contribute directly (if you own the view, that is). However, editors are somewhat special - their contributors are registered alongside editors and contribution from editor instances is not allowed. This article explains why the difference and how editor contributors prevent renegade runtime footprint and improve user experience. We will also take a look at handling editor contributors in multi-page editors.
By Dejan Glozic



One of the primary mechanisms of interaction with an Eclipse platform application is by performing actions in the given context. Whether you use menus, tool bars, accelerator keys etc. to perform an action is less important. Consequently, one of the most important considerations for Eclipse plugins is how to expose these actions to the user.

A standalone application developer has a relatively easy task since he owns all of the action vectors (menu bars, tool bars, pop-up menus etc.). A plugin developer must view its task as a contribution to the whole. Its contribution will 'rub elbows' with those from other plugins, and seamless integration becomes very important.

Desktop integration of actions is relatively easy for view developers. Eclipse views are fairly encapsulated desktop parts. They have a local tool bar and a pull-down menu. Because of this encapsulation, adding a view to the desktop is a relatively easy affair.

Content editor is a completely different animal. Editors normally require a large number of actions that are applicable in their context. Embedding these actions into the desktop part is not possible in all but a very few simple cases. Instead, editors add their actions to the shared desktop areas: the main menu and tool bars. This creates a few problems. There can be potentially many editors opened at once. They cannot all add their menus and tool bars into the shared area - it would be a disaster. It would not be right either, because only one editor can be active at a time. A context switching mechanism is needed to make sure that only actions of the active editor are visible at any given point in time.

Context switching ensures that visibility of the editor and its actions in the desktop's shared areas are in sync. This requirement cannot be left to editor developers - it is too important. Instead, editors are asked to create a contribution and this contribution is used to add and remove actions on active editor switching.

Contribution Memento

The desktop implements context switching by using a feature of JFace contribution managers called modification bracketing. If you add actions into a contribution manager in its normal state, they will end up in its managed shared area of the desktop. However, it is possible to go into recording mode by calling a method beginModification. From that moment on, all contributions made to this manager are going into a separate record called contribution memento. Once we are done contributing, we call endModification. This method will restore the normal state of the manager and also return an ContributionMemento object. We can use this object to activate or deactivate it when needed. Activating a memento means adding all of the recorded actions to the originating manager. Similarly, deactivation of the memento removes the actions.

Usage of mementos is transparent to editor developers. Eclipse desktop employs mementos to implement context switching, and ISVs are only required to do the contribution part. The only thing for developers to remember is that some kind of recording takes place, and that it is done on editor creation. Editors will not be able to modify their contribution afterwards - it will simply be switched in and out, as shown in Figure 1.
 
 

(a) 
(b)
Figure 1: Eclipse desktop menu and tool bar with no editors (a) and with HTML editor opened (b). Note how there are more menus and buttons in picture (b). These additions are coming from the contribution associated with the editor.

The problem of flashing and run-time memory waste

If you are writing a desktop view, contributing to the local tool bar is fairly simple - you simply ask for the local tool bar manager and add your actions. Similar scenario could be envisioned for contributing from the content editors, but it is not used, and for a reason. Consider the following: desktop views are unique. There cannot be multiple instances of the same view showing up in the desktop (if you try to reopen a view, the currently opened instance simply pops up and regains focus). However, there can be many instances of the same editor type opened at the same time, one for each file. If we contribute actions from the editor instance itself, that would mean a huge waste of time and memory.

For example: if we open 10 HTML files with a Web Tooling editor, and it has 40 actions to contribute, we will have 400 action instances in memory. Only 10% of these instances will be used at any point in time because only one of these editors can be active. The remaining 90% will be switched out.

There is also a problem of menu and tool bar updates. In a naive implementation, every editor switch would mean context switching for these shared areas: actions of the deactivating editor will be removed, while those of the activating editor will be added. But switching between editors of the same type means that we are removing and adding instances of the same actions. Nothing will change in the desktop except the unavoidable pause and flashing.

The Eclipse solution

The Eclipse desktop solution for this problem is to move editor contribution from editor instances to editor types. The desktop extension point for registering new editors accepts not only the Java class name for the editor, but also an optional class name for the editor contributor:

interface IEditorActionBarContributor extends IActionBarContributor {
   public void editorChanged(IEditorPart editor);
}

This contributor extends action bar contributor interface and adds a method to inform you about the editor change. This method is called only when editors of your editor type are switched, so you can safely cast it into your editor class. The implementors of this interface would use its required methods (contributeToMenu, contributeToToolBar etc.) to add actions, submenus etc. in the usual way. The key difference here is that these actions must be designed without any specific editor instance in mind. They have to work on the currently active instance, which means that they need to recalculate their enable state every time there is editor change.

When a file is opened for editing, and the editor instance is the first of that editor type to be opened, the contributor will be created as well. The contributor will create a memento and keep it. When editors are switched, it will use the memento to switch actions in or out. When another editor instance of the same type is opened, no new actions will be created. The running contributor will call editorChanged to allow actions to update. Contributor and its actions will be removed and disposed only when the last instance of this editor type is closed.

The key difference here is that context switching will occur only when two editors of the different type are switched. On switching editors of the same type, only editorChanged will be called in the contributor. This means that editor changes will be smooth, with no flashes and pauses.

Multi-page editors

The situation complicates somewhat if the content editor if of a multi-page variety (for example, Web tooling HTML editor with its Design, Source and Preview pages). In this case, actions need to update their availability not only on an editor-to-editor basis, but also on page switches within the same editor, as shown in Figure 2. Since editor instance do not have access to editor actions (actions are normally created as fields of the class that implements contributor interface), the following solution may work well.

First, create a page listener interface:

interface IEditorPageListener {
   public void pageChanged(WorkbookPage page);
}
In your multi-page editor class, add two methods to register the listener:
public void addEditorPageListener(IEditorPageListener listener);
public void removeEditorPageListener(IEditorPageListener listener);
When page within the editor changes, notify the listeners by calling pageChanged with the newly active page.

In the implementation of editor contributor, connect on each editor switch:

public MultiPageContributor implements IEditorActionBarContributor,
                                       IEditorPageListener {
   MyMultiPageEditor currentEditor=null;

   public void editorChanged(IEditorPart part) {
      MyMyltiPageEditor editor = (MyMultiPageEditor)part;
      if (currentEditor!=null) currentEditor.removeEditorPageListener(this);
      currentEditor = editor;
      currentEditor.addEditorPageListener(this);
      updateActions();
   }
   public void pageChanged(WorkbookPage page) {
      updateActions(page);
   }
}

The behavior of this class is straightforward: when editor becomes active, the contributor disconnects from the old editor and connects to the new one. While the current editor is active, page changes within it will cause the contributor to update the action state. The two overloaded methods for updating actions not shown here simply update the state of the actions according to the state of the current editor and the currently selected page in the editor.
 
 
 

(a) 
(b)
Figure 2: Eclipse desktop menu and tool bar for the Source (a) and Design view  (b) of the HTML editor. The same actions are present for  both pages, but some of them are disabled for the Source view.

 

Conclusion

This article has shown how contribution to menus and tool bars is 'special' in Eclipse desktop. We described the use of JFace contribution mementos. The article has also shown how the problem of tool bar flashing and action duplication is solved by removing contributors from editor instances. Finally, a tip on handling multi-page editors is presented.