Monday, April 29, 2013

Eclipse Modeling Framework - UI E4

I want to learn Eclipse 4 or e4 in short and I want to learn about Xtend as well. Then I figured, why not take the existing generated Eclipse 3.x EMF Editor and migrate to e4. It likely needs to adapt the templates which generate the Editor, so here I can try and use Xtend.

Get the result (Sofar)!
  • Grab E4MF here  (It's not done,  please log a bug on github for enhancements). 
If you wonder what the Eclipse EMF Editor is, then you should read about it here, and I will tell as little as it allows you to generated a fully functional editor, for whatever defined EMF model.

The Conclusion

Migrating a fully functional multipage editor which has interacts with other views to e4 is not a trivial task. First of all it's about clean-up. Dependency Injection is very powerful the new e4 plartform provides a very usefull implementation for these concepts.

The Approach:

The e4 platform is really 'different' to say the least. It's been out for a couple of years now, and there is sufficient information around to learn from and try to actually achieve my goal, but still there are a some fundamentally different concepts.

I list a few of them here:
  1. An e4 UI application is first constructed as an application model. So here is an Application.e4xmi with it's own editor to define the application structure. 
  2. Dependency Injection is all over. Parts are POJO's so do not (or limit) extending or implementing interfaces. If services are needed, these will be injected. There are plenty of Examples.
  3. The application ui model and actual renderering is de-coupled. This means, that the rendering can be exchanged, from i.e. SWT to JavaFX or Swing or whatever. 
  4. all org.eclipse.ui.* plugins are deprecated as 3.x platform plugins. This is an important fact to consider in the migration. 

My approach is to take the EMF extended library example, generate the editor plugins. there is typically three of them.

org.eclipse.emf.examples.library.edit
org.eclipse.emf.examples.library.editor
org.eclipse.emf.examples.library.test

...and then make a copy of these, and make them working e4 editor. This will form the target to generate, so the templates which generate from the EMF .genmodel files.

.genmodel options

Now this is a bit tricky. The .genmodel file,  as various options, which will add/remove functionality from the .edit and .editor plugins. In my approach, I will use the default, and worry about the optional features later. I know, this 'could' get me in trouble, but I want to get started, and don't feel like figuring out, what is the most complete set to make the most functional EMF editor.

The only option I set here is to generate an RCP version of the editor.
This has some consequences as we will see later on.

Finding dependencies

We know for a pure e4 product, org.eclipse.ui plugins should not be used anymore . So we need an overview or map of where these dependencies exist. What I am going to do is remove these dependencies, see what breaks and replace it with the e4 alternative.

The generated editor has the following dependencies.

org.eclipse.emf.examples.library.editor
=> org.eclipse.core.runtime
=> org.eclipse.emf.examples.library.edit
=> org.eclipse.emf.ecore.xmi
=> org.eclipse.emf.edit.ui

The first two will need some rework, the latter two are not UI plugins so these are OK.

org.eclipse.emf.examples.library.edit
=> org.eclipse.core.runtime
=> org.eclipse.emf.edit
=> org.eclipse.emf.examples.library

After inspecting the dependencies, of org.eclipse.emf.edit, I am surprised to see there are no UI dependencies. The .edit plugin there for requires no adaptation, although it provides the base implementations for the command and adaptation patterns used in EMF.

org.eclipse.emf.edit.ui
=> org.eclipse.core.runtime
=> org.eclipse.ui.views
=> org.eclipse.ui.workbench
=> org.eclipse.emf.edit
=> org.eclipse.emf.common.ui
=> org.eclipse.core.resources (Optional)
=> org.eclipse.ui.ide (Optional)
=> org.eclipse.jface.text (Optional) 

So what needs to be adapted, as we stated earlier, the org.eclipse.ui.* plugins have been replaced in e4. In our case the following plugin dependencies are affected:
=> org.eclipse.ui.views
=> org.eclipse.ui.workbench
=> org.eclipse.ui.ide (Optional)

Also the EMF common.ui plugin as dependencies on the Eclipse 3.x UI framework.

org.eclipse.emf.common.ui 
=> org.eclipse.core.runtime
=> org.eclipse.ui
=> org.eclipse.emf.common
=> org.eclipse.core.resources (Optional)
=> org.eclipse.ui.ide (Optional)
=> org.eclipse.jface.text (Optional)

Here only org.eclipse.ui is a dependency we haven't seen before, so this will migrate as well.  
 
I conclude, that the base UI plugin from EMF will need to be reworked. I decide to clone EMF, rename this plugins (As they will need to co-exist if the EMF team wishes to pull from me). The new name is:

org.eclipse.e4mf.edit.ui
org.eclipse.e4mf.common.ui 

Now my instinct, would tell me to break the 3.x  dependencies here, and fix them with the e4 alternative, so I decide to do so, but before I go into, this I would like a basic EMF e4 editor working
so I start here:


org.eclipse.emf.examples.library.editor

The approach is to drill down top down, the application structure. So we start with the Application, then the Perspective, then editors and views, then Actions etc....

Step 1. Fix the dependencies, which have changed with the e4 versions of

org.eclipse.e4mf.edit.ui
org.eclipse.e4mf.common.ui

Step 2. Create an e4 Application Model.

File -> New -> Eclipse 4 -> Model -> New Application Model
This creates a file named Application.e4xmi

[TRICK] You can instruct e4 to load the application model from a specific location, with the following property in the Application extension definition:

 <property
               name="applicationXMI"
               value="org.eclipse.emf.examples.library.e4editor/xmi/Application.e4xmi">
</property>

Here we tell e4 to load the model from a subdir 'xmi' , this is also where we keep e4 fragments.

Step 3.  Populating Application Model

Here we migrate the generated EMF editor functionality into the application model.
In e4 We need a pespective stack and the actual perspective. 

PerspectiveStack => ID: org.eclipse.emf.examples.library.e4editor.perspectivestack.0 (Generated)
Perspective => ID: org.eclipse.emf.examples.library.e4editor.perspective.0

Now in the EMF editor, we have the actual model editor on the left, and the outline and properties editor on the right. So we need PartSashContainers, PartStacks and Parts for this. Here are the ID. 

PartSashContainer => ID: org.eclipse.emf.examples.library.e4editor.partsashcontainer
      PartStack => ID: org.eclipse.emf.examples.library.e4editor.partstack.editor
      PartSashContainer => ID: org.eclipse.emf.examples.library.e4editor.partsashcontainer.1
           PartStack => ID: org.eclipse.emf.examples.library.e4editor.partstack.0
                   Part => ID: org.eclipse.emf.examples.library.e4editor.part.1
           PartStack => ID: org.eclipse.emf.examples.library.e4editor.partstack.1
                   Part => ID:  org.eclipse.emf.examples.library.e4editor.part.2


As we will see later on, we would actually like a PartDescriptor for opening the editor, unfortunately as of writing , this is not supported.

Maximize and Minimize

The base functionality, doesn't have the maximize and minimize buttons for a PartStack.
Adding the plugin:  org.eclipse.e4.ui.workbench.addons.swt to the launch config. will make some additional e4 addons available. One of them is the MinMax addon.

[TRICK]
With e4 it's possible to show the runtime application model editor. This is especially usefull when
working with fragments and processors. (Application model contributions).

To do this, include the following plugins in the launch config or product or other build system.
  • org.eclipse.e4.tools.emf.liveeditor 
  • org.eclipse.e4.tools.emf.ui 
  • org.eclipse.e4.tools.emf.ui.script.js
  • (Additional required)
When available, the following key combo can be used ALT-SHIFT-F9 to bring up the liveeditor.
[BUG] on Macosx it's not working, with current (Kepler M6)
https://bugs.eclipse.org/bugs/show_bug.cgi?id=394503

Step 4. The EMF Editor

In e4, an editor is a POJO, so doesn't extend EditorPart, nor a MultiPageEditor Part.
The EMF editor has several functionalities. In order to rebuild them one by one, we first disable:
  • Doesn't extend MultiPageEditorPart 
  • Overriding methods. (We are a POJO!).
  • Implement various e4 concepts.
Constructing the UI

In e4 we can designate any method and add e4 lifecycle annotations like @PostConstruct which will call the method when reaching the time in a part or other UIElement lifecycle. On top we can add method arguments, which will be injected (if available) by the context.

For our EMF Editor it starts with the init method.

simply doing this, gives us a parent composite to add our views to.

@PostConstruct
public void init(Composite parent, ......[more arguments to follow]){
   createPages()
}

We call createPages(), this is normally called by our MultiPageEditorPart, as there is no equivalent for MPEP , we settle adapting the method createPages() to only create one page.

ViewerPane

Viewer panes have a title bar and functionality to maximize/restore the viewerpane.
The concept is however very tight to IWorkbenchPart and IWorkbenchPage. The widget is actually placed inside a so-called ViewerForm which allows control of layout and margins etc...
One other feature will loose from ViewerPane, is that the title is updated with the selected object
from the view pane.

For now we decide, not to migrate this concept and create the viewers directly under the parent composite.
 

Init the Editor input

the init(IEditorSite site, IEditorInput input) method for a 3.x should find it's equivalent
in e4.  The 3.x definiton in the plugin.xml starts with <extension point="org.eclipse.ui.editors">. It allows us to specify the implementation, an icon and a contributor to the class and more...

The EMF Editor in 3.x generates a content type parser and a model specific content type
which are used to respectively parse the content and associate a file type by extension with the EMF generated editor.

Unfortunately e4 currently doesn't have a part descriptor which resembles the 3.x equivalent. There is an MInputPart and it can be put in the Application Model, but it will be static. What we want is to query the framework for editor descriptors which match a criteria like a file name or protocol. This concept is recognized and named EditorPartDescriptor. An extension to the e4 Application model to support this is available in the simple ID demo (Tom Shindle).

Some learnings from the demo:
  • It shows how an input from one MPart (Navigator) is set to the context with context.set(IFile.class, f);  and later inject in a Handler's @Execute method arguments. The context in this case is the IEclipseContext. 
  • The contributed EditorPartDescriptors for .xml .text and .java editors are checked for their supported extensions. If a match is found the e4 CommandService is use to fire a command, which will involen the OpenEditor Handler, which then creates an MInputPart and sets the contributionURI according to the EditorPartDescriptor to make sure the correct editor is contributed and instantiated.
  •  It demonstrates how the input URI of the MInputPart is adapted to an IDocument which the editor can consume. The adaptation is done with a so-called ContextFunction. The adapter implements org.eclipse.e4.core.contexts.IContextFunction which is offered as an OSGI service.

[DECISION] We do not implement this concept, as it requires an extension to the e4 Application model. See further the "Open Handler" for the actual chosen implementation.

Modularity and Contributions in e4

We could add a model fragment, but this will also be static. It is intended to provide extensibility but does not substitute for the old extension point *.editors.

The best possible solution for now is:

1. Implement an Open Handler
2. Create an MInputPart Programmaticly
3. Set the Contribution URI to our own EMF Editor
4. Set the Input URI for the MInputPart
5. Activate the part with the EPartService

Dealing with Dialogs -getShell()

In 3.x we need do getSite().getShell() to get the active shell for the part. In the EMF editor this happens as well. The alternative in e4 is this.

    @Inject
    @Named(IServiceConstants.ACTIVE_SHELL)
    private Shell activeShell;

So replace all invokations of getSite().getShell() with activeShell

Dirtyness

In 3.x an EditPart implements ISaveablePart which is the interface for marking the editor dirty (Dirty meaning it's been edited and should be saved, the user is notified with an * next to the title of the part). In order for the workbench to know about a dirty editor, we had to call firePropertyChange(IEditorPart.PROP_DIRTY);

In e4 we have the this alternative. 

@Inject
private MDirtyable dirtyable;

Whenever a part is dirty we have to call

dirtyable.setDirty(false/true);

In the case of the EMF Editor, this happens on the CommandStack Listener and when the editor is saved.

Saving is done by implementing the @Persist annotation to several methods:

doSave(IProgressMonitor monitor)
doSaveAs() [TODO, how would this work? It needs to be bind to an action, perhaps needs @Execute instead of @Persist]


Adapters

The adapter concept is also supported by e4. In classical 3.x a part is consulted (adapted) for certain interfaces and returns an implementation if supported. For the EMF Editor the following is adapted:

  • IContentOutlinePage.class
  • IPropertySheetPage.class
Now we have a bit of an issue here as both these classes are not available as e4 implementations.
However considering the problem these classes try to solve. Like dealing with selection etc...It is very different in e4.

The editor also listens for part change, and activates the EMF editor whener the property page or outline becomes active and is related to the EMF Editor.

Properties

The Properties concept is not implemented in e4, as we aim for pure e4 (Not compat layer).
See https://bugs.eclipse.org/bugs/show_bug.cgi?id=404884

[DECISION] Defined MPart placeholder, for keybinding to work.

Outline

The outline concept is implemented pure e4 in the simpleIDE demo (Tom Shindl).

[TODO] consider adopting this concept.

[DECISION] Defined MPart placeholder, for keybinding to work. 

Step 5. Actionsets and Actions.

The 3.x. EMF editor generates two action sets. One for the editor and one for the model.

ActionsSet.1 
  • About
  • Open URI
  • Open
ActionSet.2
  • Model (New)

ActionSets in 3.x are used to group actions which belong to a certain task usually represented in a certain perspective. Binding of ActionSets and Perspectives is done with the extension org.eclipse.ui.actionSetPartAssociations

Now besides the fact that ActionSets are even deprecated in the 3.x platform (Use Commands and Handlers instead), the generated EMF Editor actually only has one perspective as an RCP app and doesn't bind the action sets to the defined perspective.

Other Actions

The EMF Editor also creates various menus programmatically and adds global actions to the 3.x ActionBarAdvisor. This is:

File Menu

File -> [FILE_START]
            New -> [MB_ADDITIONS]*
            ----- ID: org.eclipse.emf.examples.library.e4editor.menuseparator.file.additions
            [MB_ADDITIONS]
            -----
            Close
            Close All
            -----
            Save
            Save As
            Save All
            ------
            Quit
            [FILE_END]

* The contributions between brackets [...] are markers for dynamic insertion in 3.x. For e4, the insertion points are ID's of other items. Menu separators can be pre-inserted in the Application. Fragments can then contribute to these.

Example: In our case we define a menu separator:  
org.eclipse.emf.examples.library.e4editor.menuseparator.file.additions

Model fragments use these ID's to contribute 'before' or 'after' as we will see later on. 

Creating the structure in e4 Commands, Handlers

The equivalent for e4 is to simply add Commands, Handlers and Key bindings to the Application Model for most actions and inserting the Application Model contributions in the right place using ID's of UIModel Elements.

Command ID's

[BUG?] In the documentation, it is stated that commonly used commands should use the ID's as known in IWorkbenchCommandConstants. However using this in some cases, causes the a menu or toolbar not being shown. For example:

For the About command, we should use: "org.eclipse.ui.help.aboutAction". This causes the menu entry 'About' not to show.

Handlers

Note that the Handler implementations are interresting, as these would use DI to get relevant objects like the workbench, or the Active part etc....

Most of the Handlers are straight forward. We discuss here some of the specific ones.

OpenHandler

The Open handler re-uses the functionality already in the 3.x EMF Editor. Like the methods to open a dialog and select a file based on extensions.

The function is hower exposed to dependency injection with a HandlerSupport services made available with OSGI. The service provides facilities to open an Editor, open a File Dialog, respecting the EMF Model file extension and more. See HandlerSupport

[TODO] Considering the dynamic nature the e4 Application model, we can close the partstack holding the EMF editor. So fix the Open/New handlers to cope with an un-existing partstack.

Key Bindings

An initial e4 Application model doesn't have any binding context associated.
A default binding context hierarchy will be defined as:

Binding Context - Window and Dialog (Applies to both)
             |
              - Binding Context Window
              - Binding Context Dialog

Binding Tables

Here we bind specific keys with commands for a Window or Dialog contex. See further which keys are associated with a context (via the Binding Table).
 

The following key-bindings are specified in EMF Editor

M1 Ctrl Command
M2 Shift Shift
M3 Alt Alt
M4 Undefined Ctrl

Declarative:

M1+U => Open URI command
M1+O => Open command

Declarative through EABC (Implict by use of Platform Actions).

M1+W => Close
M1+M2+W => Close All
M1+S => Save
M1+M2+S=> Save All
M1+Q=>Quit

Dynamic Contributions, the IEditorActionBarContributor

The 3.x EMF Editor mimics the Menu and Toolbar structure of the Eclipse IDE. Actions which are specific for the IDE (New, Open, Open URI and all edit actions), are contributed by respectively in a declarative manner in plugin.xml and by an IEditorActionBarContributor.

The solution for e4 requires some rework, as the contribution paradigm is different for e4.

Contributing the equivalent of 3.x Actions in plugin.xml is done by creating  fragments, which is added to our application. There will be two fragments. One for the EMF Editor, and one contributed by org.eclipse.e4mf.edit.ui. The reason is that these actions could be contributed to an IDE instead of an RCP Application.

[TODO] The contribution however is hardcoded in the fragment to specific. 

Notes on position in list: (I state it here, as it was not documented in most tutorials).

first
index:$theindex$
before:$theotherelementsid$
after:$theotherelementsid$
 
For the Editor Contributor, as there is no EditorPartDescriptor with associated contributor, we need to mimic the functionality with dynamic contributions.

The contribution occurs only when the editor is active, for e4 our designated MPart for the editor is active. The contribution is part of both the generated editor and emf.edit.ui

To achieve this, we hook into the e4 Event system. The Editor will check if an activated part's ID is the EMF Editor. If so, it will set a context variable.

[BUG]Unfortunately there is a bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=400217

Edit Menu

The Edit menu structure is

Edit
Undo   => M1+Z
Redo   => M1+M2+Z
----
Cut      => M1+X
Copy   => M1+C
Paste   => M1+V
----
Delete => Del
Select All => M1+A
[ADD_EXT]
[EDIT_END]
[MB_ADDITIONS]


Model Menu

EXTLibrary Editor
----[settings]
----[actions]
New Child -> [Containment children for selection]
New Sibblings -> [Sibblings for selection] 
Validate
Control...
---- [additions] 
Load Resource...
--- [additions_end]Refresh
Show Properties
--- [ui-actions]

Migrating the Actions

Most of the actions are part of org.eclipse.e4mf.edit.ui and build on JFace IAction. The IAction and Action implementation as such are not incompatible to e4. However the e4 workbench doesn't accept IAction's. [There is the Compat layer, with MRenderedMenu, but we go for pure e4 here]

EXTLibraryModelWizard

Fixing the wizard is required, although wizards are JFace only implementation, which is not exactly true. The generated Model Wizard for the EMF Editor also requires the implementation of an INewWizard.

Now why is this in the first place? Well this is really to contribute to the wizard to the Workspace
when running in IDE mode.

[DECISION] Remove 'implement INewWizard'

As a consequence, we also need to fix how the editor will be openend, but as we refactored this for the OpenHandler into a HandlerSupport Service, we simply call this method and we have no dependency on the 3.x IWorkbench.

Additionally, we would like out Model Wizard to be part of the injection context.
to achieve this, we simply add the following annotation to the class definition.

@Creatable

Now, a new instance will be created by the e4 DI, whenever we refer to the EXTLibraryModelWizard class in our Handler constructor for the 'New' command.

org.eclipse.e4mf.common.ui 

Step 1. Renaming the packages.
Step 2. I remove the dependency on org.eclipse.ui and org.eclipse.ui.ide, which of course breaks a lot of stuff. 

org.eclipse.ui
=> org.eclipse.swt
=> org.eclipse.jface
=> org.eclipse.ui.workbench

.swt and .jface are re-exported, which causes the common.ui to break.
Now, as rendering is decoupled from the UI model and .swt and .jface on top, it makes sense to let the current common.ui plugin to be just one of this rendering implementation. Later on, we could have a common plugin, which is alternative to SWT.  For sake of not over-complicating, I don't separate the UI model part from the rendering (SWT) just yet, but it would be required. 

So, I add the following dependencies:
=> org.eclipse.swt
=> org.eclipse.jface

Looking I wat is not resolved, this is more or less what I expect.
Things like EditorPart, Memento, PlatformUI, AbstractUIPlugin etc... These 'services' are all done differently in e4, so I get to work on these one by one :-)

Step 3. EclipseUIPlugin =>

Now this extends the AbstractUIPlugin, for which services in e4 offer similar functionality for Preferences, Dialog settings and accessing Resources like images.

[DECISION] For now it's best to let EclipseUIPlugin extend Plugin instead of AbstractUIPlugin. This means afore mentioned services will need to be provided the e4 way.

Step 4. Diagnostic Component =>

This class requires the Shared images normally available from the PlatformUI. We don't have the PlatformUI in e4, so this should be migrated by using an inject resource service.

After some research, the 3.x SharedImages are not Exposed as resources with the IResourcePool concept of e4. (part of tools.services). For EMF, I decide to create such an Resource Provider, or more explicitly a provider for the workbench images.

Note that for 3.x the images are registered on an ImageRegistery with the class WorkbenchImages.
The actual images are stored in org.eclipse.ui.

See this bug for the solution. : https://bugs.eclipse.org/bugs/show_bug.cgi?id=404727


Workspace Stuff =>

One of the functionalities of EMF Editors, is the ability to interact with the workspace. The workspace is

.....TODO Continue migration of emf.common.ui


org.eclipse.e4mf.edit.ui



Step 1. Renaming the packages
Step 2. Extended Image Registry

Fix the fall back to PlatformUI for getting images.

No comments:

Post a Comment