Developer Notes

7. The NoteEditor classes

The Pliny NoteEditor is expressed in Eclipse as an Eclipse workbench EditorPart (note that, although the names are similar, the GEF EditPart and the workbench EditorPart are entirely different things!), but a few other classes participate in making the NoteEditor operate. Furthermore, the NoteEditor has to appear in the plugin's plugin.xml file so that the workbench as a whole knows about the editor and knows how to invoke and present it.

NoteEditor in plugin.xml

Here is one of the two fragments of the plugin.xml file that refers to the NoteEditor:

   <extension
         point="org.eclipse.ui.editors">
      <editor
            class="uk.ac.kcl.cch.jb.pliny.editors.NoteEditor"
            contributorClass="uk.ac.kcl.cch.jb.pliny.editors.NoteEditorActionBarContributor"
            default="false"
            icon="icons/noteIcon.gif"
            id="uk.ac.kcl.cch.jb.pliny.noteEditor"
            name="Note Editor"/>
      [...]

It is outside of the scope of this document to describe the details of this material too closely here. You should consult Eclipse documentation for further details. However, a few basic points should be made:

  • The editor element for the NoteEditor appears in an extension element with point attribute org.eclipse.ui.editors. This tells Eclipse that it we are providing in the elements that follow information about editors contributed by this plugin.
  • The Editor is provided with an id of uk.ac.kcl.cch.jb.pliny.noteEditor which is used to refer to it from other parts of plugin.xml. It is also used in code when one wants to address this kind of editor -- if from code you want to ask for an instance of this editor to be opened, you identify the particular editor by giving its ID string. A name is also provided which Eclipse will use in its displays to identify the editor. An icon is identified that Eclipse should use whenever it wants to use an icon to refer to this kind of editor.
  • The Eclipse workbench is told what class to launch to start the editor with the class attribute. Here, the class is identified as uk.ac.kcl.cch.jb.pliny.editors.NoteEditor. There is more about this class below.
  • The workbench also requires the use of a ContributorClass to define menu and toolbar actions for an editor. The contributor class for this editor is defined via attribute contributorClass in the XML declaration which in this case points to class uk.ac.kcl.cch.jb.pliny.editors.NoteEditorActionBarContributor.

    This this writer's humble opinion, the ActionBarContributor idea and the presence of an action bar for the editor which is not visibly a part of the editor frame is a bad user interface idea. Although Pliny editors use this mechanism at present, it is possible that future changes in the code will move all the visible buttons inside the editing panel, and stop using the area provided by Eclipse altogether.

A further bit of plugin.xml that supports the note editor is:

   <extension
         point="org.eclipse.ui.elementFactories">
      <factory
            class="uk.ac.kcl.cch.jb.pliny.editors.NoteEditorInputFactory"
            id="uk.ac.kcl.cch.jb.pliny.noteEditorInputFactory"/>
      [...]

Eclipse's elementFactories are involved in supporting the ability Eclipse has of remembering what editors where open from one session to the next. Information about the open editors are saved at the time Eclipse was shutting down and this list is consulted next time Eclipse starts up so that they can be reopened again. At shutdown time, each open editor is given a chance to store state information about itself in an Eclipse org.eclipse.ui.IMemento object which is written to disk. When Eclipse restarts it recreates the IMemento from the disk file and then consults this IMemento object to see what editors were open and calls the appropriate factory class described in this bit of plugin.xml to take the data stored in the IMemento class for the editor and transform it back into information the Editor can work with.

The NoteEditorInput class

Editors have input objects that identify what is being edited. In much of standard Eclipse these are files and Eclipse provides a mechanism to allow the file to be specified that we won't address directly here. However, Eclipse's design recognises that editors might well be needed for things other than files and so provides a model for allowing other things than files to be provided as input to an editor. For the NoteEditor, the input is, of course, a Pliny uk.ac.kcl.cch.jb.pliny.model.NoteLucened resource, and the uk.ac.kcl.cch.jb.pliny.editors.NoteEditorInput class has been written to Eclipse's model to handle this.

NoteEditorInput implements org.eclipse.ui.IStorageEditorInput, which is the interface that allows it to act as an input object within Eclipse. When Eclipse starts up an instance of the Note Editor it expects to deliver to the editor an instance of this class that identifies what is to be edited -- for the Note Editor, this is a particular NoteLucened object. If you look at the code for NoteEditorInput you can see that it holds a particular NoteLucened object.

In Pliny a Note Editor can be opened in more than one way, for example:

  • by double-clicking on an instance of a note in the Resource Explorer
  • by requesting "open note" from a reference object displaying a note

In all cases a particular NoteLucened object has been identified as the one to open. The code that causes the open to happen must create a NoteEditorInput instance, put the NoteLucened object in it, and then ask Eclipse to open the appropriate editor -- with the editor identified by giving the editor's id (as defined in the plugin.xml file).

NoteEditorInput also implements org.eclipse.ui.IPersistableElement and org.eclipse.core.runtime.IAdaptable interfaces. These interfaces define methods that allow Eclipse to write information in the NoteEditorInput class to the IMemento mentioned earlier, so that the identification of the open class will persist from one Eclipse session to the next.

Finally, NoteEditorInput implements the equals() method. Eclipse uses this to establish whether an editor on a particular item is already open at the time it is asked to open one. If it finds -- through consulting the equals() method -- that one is indeed already open, then instead of opening a second instance, it moves the focus to the already open editor.

The NoteEditorInputFactory class

As was mentioned above, the uk.ac.kcl.cch.jb.pliny.editors.NoteEditorInputFactory class supports the Eclipse workbench during its startup in the task of restarting Editors that were open when it was last shut down.

As described earlier, during shutdown the NoteEditorInput object for each open Note Editor is consulted by the workbench and given a chance to write data to a IMemento object to identify what is open. All that needs to be written for a NoteEditorInput to uniquely identify which note it is presenting is the DB key which identifies uniquely the particular instance of NoteLucened that this editor is working on. When the Eclipse workbench is restarted, then, the IMemento object contains the DB key for the particular NoteLucened object. This factory class takes the key, uses it to fetch the appropriate NoteLucened object, and packages it up in a NoteEditorInput object.

The NoteEditor class

The uk.ac.kcl.cch.jb.pliny.editors.NoteEditor class is the class that really implements the editor. To be an editor in Eclipse it needs to implement Eclipse's org.eclipse.ui.IEditorPart which in the case of the NoteEditor it does by defining itself as an extension of org.eclipse.ui.part.EditorPart. Then, when Eclipse needs to start a new Note Editor it creates a new instance of this class and passes it an instance of uk.ac.kcl.cch.jb.pliny.editors.NoteEditorInput (which tells it which NoteLucened object to open) by calling the new instance's init() method. The following are a few notes about the NoteEditor class.

NoteEditor's createPartControl method

The Eclipse workbench calls the method createPartControl to create the GUI component of the editor. As you might recall from what the NoteEditor looks like, it contains two parts:

  • To the left is the area where the Note's title and content can be edited. This is done by means of textual fields, and uses standard SWT and JFace techniques. The area is defined in the method defineNoteArea() which createPartControl() calls.
  • To the right is the area for the Note's reference area. Reference areas are maintained by GEF and use GEF methods to draw materials on the screen. The section Annotation/Reference area classes provides some details about how this is done. GEF is given control of the right area on the display in method createGraphicalViewer() which, in turn sets up GEF's object ScrollingGraphicalViewer to display the data, and handle the interactions.

Actions for the NoteEditor

Most mouse interactions within the reference area such as dragging or selecting are handled by GEF and use its policies/command approach described elsewhere. However, certain interactions make use of the Eclipse workbench's Action model. Actions in editors are rather complex -- in Eclipse menu and main toolbar actions attached to editors are managed by a org.eclipse.gef.ui.actions.ActionBarContributor, which for the NoteEditor is uk.ac.kcl.cch.jb.pliny.editors.NoteEditorActionBarContributor (described further below). The editor instance itself manages actions displayed in its main graphical area, and also manages the updating of what Eclipse calls Global actions. This action work is done in NoteEditor's method createActions().

Method createActions() does the following tasks:

  • It uses a GEF construct called the ActionRegistry (org.eclipse.gef.ui.actions.ActionRegistry) to identify the global actions (which include undo, redo, delete, cut, copy, paste and selectAll) and register them so that GEF knows what action to launch if it is asked to do so by a user.
  • It then uses Eclipse's setGlobalActionHandler method to notify Eclipse that whenever this instance of the editor is active it is to use the Action instances defined here to handle them.

Actions and GEF Commands

As was mentioned above, GEF uses a Command concept as a way transform user interactions on the screen into changes in the underlaying data model. Commands can be (and for Pliny commands always are) defined as undoable and redoable, so support for undo and redo is also provided.

Because the user will not separate actions in the reference area (managed by GEF) from actions in the note content are (managed by SWT and JFace) and will expect undo to work for both, I have applied GEF's Command approach to changes within the SWT and JFace components of the NoteEditor as well as the GEF ones, and both GEF and SWT/JFace environments share a single GEF CommandStack. Thus, all actions that need to be undoable within the NoteEditor -- whether or not they come from GEF or SWT/JFace are handled through Commands and a single CommandStack, and can, therefore, use the same undo or redo action to request undo or redo.

The NoteEditorActionBarContributor class

Eclipse editors do not have their own menubar area, and share space for the main Eclipse menu bar with other editors. For this reason, Actions that should appear on the menu bar need to be created and managed through an instance of a class called an ActionBarContributor (org.eclipse.gef.ui.actions.ActionBarContributor). Only one instance of an action bar contributor is created for all instances of a particular class of Editor -- all open instances of the NoteEditor, then, share a single instance of the NoteEditor's action bar contributor uk.ac.kcl.cch.jb.pliny.editors.NoteEditorActionBarContributor.

NoteEditorActionBarContributor provides several methods that Eclipse calls when necessary:

  • buildActions(): is called when the contributor is first set up and builds a single instance for each actions that the editors of this class will need and share. Note in this contributor that the action for delete, undo and redo can simply be standard versions provided by GEF. Special NoteEditor actions (create new Note, etc), are also defined here. Note the call to method addGlobalActionKey() which identifies to Eclipse that actions objects for these global actions are provided by the editor instance, and Eclipse must take on the job of keeping them synchronised as the user switches between editors of the same class.
  • contributeToToolBar(): is called when Eclipse is creating the menubar for this class of editor, and identifes which actions should appear on it.
  • declareGlobalActionKeys(): is called to define keyboard shortcuts and link actions to them. Apparently nothing is needed for the NoteEditor.
  • setActiveEditor(): is invoked by Eclipse each time the user switches from one editor instance to another. In NoteEditorActionBarContributor most of the work is done by the private method updateEditor() which, in turn notifies the various actions that they must shift their attention from one editor to another.


John Bradley
Center for Computing in the Humanities
King's College London